diff options
author | Jiyoung Yun <jy910.yun@samsung.com> | 2016-11-23 19:09:09 +0900 |
---|---|---|
committer | Jiyoung Yun <jy910.yun@samsung.com> | 2016-11-23 19:09:09 +0900 |
commit | 4b4aad7217d3292650e77eec2cf4c198ea9c3b4b (patch) | |
tree | 98110734c91668dfdbb126fcc0e15ddbd93738ca /src/vm/excep.cpp | |
parent | fa45f57ed55137c75ac870356a1b8f76c84b229c (diff) | |
download | coreclr-4b4aad7217d3292650e77eec2cf4c198ea9c3b4b.tar.gz coreclr-4b4aad7217d3292650e77eec2cf4c198ea9c3b4b.tar.bz2 coreclr-4b4aad7217d3292650e77eec2cf4c198ea9c3b4b.zip |
Imported Upstream version 1.1.0upstream/1.1.0
Diffstat (limited to 'src/vm/excep.cpp')
-rw-r--r-- | src/vm/excep.cpp | 14119 |
1 files changed, 14119 insertions, 0 deletions
diff --git a/src/vm/excep.cpp b/src/vm/excep.cpp new file mode 100644 index 0000000000..672f315fcd --- /dev/null +++ b/src/vm/excep.cpp @@ -0,0 +1,14119 @@ +// 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. +// + +// + +/* EXCEP.CPP: + * + */ + +#include "common.h" + +#include "frames.h" +#include "threads.h" +#include "excep.h" +#include "object.h" +#include "field.h" +#include "dbginterface.h" +#include "cgensys.h" +#include "comutilnative.h" +#include "siginfo.hpp" +#include "gc.h" +#include "eedbginterfaceimpl.h" //so we can clearexception in RealCOMPlusThrow +#include "perfcounters.h" +#include "dllimportcallback.h" +#include "stackwalk.h" //for CrawlFrame, in SetIPFromSrcToDst +#include "shimload.h" +#include "eeconfig.h" +#include "virtualcallstub.h" + +#ifndef FEATURE_PAL +#include "dwreport.h" +#endif // !FEATURE_PAL + +#include "eventreporter.h" + +#ifdef FEATURE_COMINTEROP +#include<roerrorapi.h> +#endif +#ifdef WIN64EXCEPTIONS +#include "exceptionhandling.h" +#endif + +#include <errorrep.h> +#ifndef FEATURE_PAL +// Include definition of GenericModeBlock +#include <msodw.h> +#endif // FEATURE_PAL + +#ifdef FEATURE_UEF_CHAINMANAGER +// This is required to register our UEF callback with the UEF chain manager +#include <mscoruefwrapper.h> +// The global UEFManager reference for use in the VM +IUEFManager * g_pUEFManager = NULL; +#endif // FEATURE_UEF_CHAINMANAGER + +// Support for extracting MethodDesc of a delegate. +#include "comdelegate.h" + +#if defined(FEATURE_APPX_BINDER) && !defined(DACCESS_COMPILE) +// For determining if we have a framework assembly trying to handle a corrupted state exception +#include "policy.h" +#endif // FEATURE_APPX && !DACCESS_COMPILE + +#ifndef FEATURE_PAL +// Windows uses 64kB as the null-reference area +#define NULL_AREA_SIZE (64 * 1024) +#else // !FEATURE_PAL +#define NULL_AREA_SIZE OS_PAGE_SIZE +#endif // !FEATURE_PAL + +#ifndef CROSSGEN_COMPILE + +BOOL IsIPInEE(void *ip); + +//---------------------------------------------------------------------------- +// +// IsExceptionFromManagedCode - determine if pExceptionRecord points to a managed exception +// +// Arguments: +// pExceptionRecord - pointer to exception record +// +// Return Value: +// TRUE or FALSE +// +//---------------------------------------------------------------------------- +BOOL IsExceptionFromManagedCode(const EXCEPTION_RECORD * pExceptionRecord) +{ + CONTRACTL { + NOTHROW; + GC_NOTRIGGER; + SO_TOLERANT; + SUPPORTS_DAC; + PRECONDITION(CheckPointer(pExceptionRecord)); + } CONTRACTL_END; + + if (pExceptionRecord == NULL) + { + return FALSE; + } + + DACCOP_IGNORE(FieldAccess, "EXCEPTION_RECORD is a OS structure, and ExceptionAddress is actually a target address here."); + UINT_PTR address = reinterpret_cast<UINT_PTR>(pExceptionRecord->ExceptionAddress); + + // An exception code of EXCEPTION_COMPLUS indicates a managed exception + // has occurred (most likely due to executing a "throw" instruction). + // + // Also, a hardware level exception may not have an exception code of + // EXCEPTION_COMPLUS. In this case, an exception address that resides in + // managed code indicates a managed exception has occurred. + return (IsComPlusException(pExceptionRecord) || + (ExecutionManager::IsManagedCode((PCODE)address))); +} + + +#ifndef DACCESS_COMPILE + +//---------------------------------------------------------------------------- +// +// IsExceptionFromManagedCodeCallback - a wrapper for IsExceptionFromManagedCode +// +// Arguments: +// pExceptionRecord - pointer to exception record +// +// Return Value: +// TRUE or FALSE +// +//---------------------------------------------------------------------------- +BOOL __stdcall IsExceptionFromManagedCodeCallback(EXCEPTION_RECORD * pExceptionRecord) +{ + CONTRACTL { + NOTHROW; + GC_NOTRIGGER; + SO_TOLERANT; + SUPPORTS_DAC; + PRECONDITION(CheckPointer(pExceptionRecord)); + PRECONDITION(!RunningOnWin7()); + } CONTRACTL_END; + + // If we can't enter the EE, done. + if (g_fForbidEnterEE) + { + return FALSE; + } + + return IsExceptionFromManagedCode(pExceptionRecord); +} + + +#define SZ_UNHANDLED_EXCEPTION W("Unhandled Exception:") +#define SZ_UNHANDLED_EXCEPTION_CHARLEN ((sizeof(SZ_UNHANDLED_EXCEPTION) / sizeof(WCHAR))) + + +typedef struct { + OBJECTREF pThrowable; + STRINGREF s1; + OBJECTREF pTmpThrowable; +} ProtectArgsStruct; + +PEXCEPTION_REGISTRATION_RECORD GetCurrentSEHRecord(); +BOOL IsUnmanagedToManagedSEHHandler(EXCEPTION_REGISTRATION_RECORD*); + +VOID DECLSPEC_NORETURN RealCOMPlusThrow(OBJECTREF throwable, BOOL rethrow +#ifdef FEATURE_CORRUPTING_EXCEPTIONS + , CorruptionSeverity severity = NotCorrupting +#endif // FEATURE_CORRUPTING_EXCEPTIONS + ); + +//------------------------------------------------------------------------------- +// Basically, this asks whether the exception is a managed exception thrown by +// this instance of the CLR. +// +// The way the result is used, however, is to decide whether this instance is the +// one to throw up the Watson box. +//------------------------------------------------------------------------------- +BOOL ShouldOurUEFDisplayUI(PEXCEPTION_POINTERS pExceptionInfo) +{ + STATIC_CONTRACT_NOTHROW; + STATIC_CONTRACT_GC_NOTRIGGER; + STATIC_CONTRACT_FORBID_FAULT; + + // Test first for the canned SO EXCEPTION_POINTERS structure as it has a NULL context record and will break the code below. + extern EXCEPTION_POINTERS g_SOExceptionPointers; + if (pExceptionInfo == &g_SOExceptionPointers) + { + return TRUE; + } + return IsComPlusException(pExceptionInfo->ExceptionRecord) || ExecutionManager::IsManagedCode(GetIP(pExceptionInfo->ContextRecord)); +} + +BOOL NotifyAppDomainsOfUnhandledException( + PEXCEPTION_POINTERS pExceptionPointers, + OBJECTREF *pThrowableIn, + BOOL useLastThrownObject, + BOOL isTerminating); + +VOID SetManagedUnhandledExceptionBit( + BOOL useLastThrownObject); + + +void COMPlusThrowBoot(HRESULT hr) +{ + STATIC_CONTRACT_THROWS; + + _ASSERTE(g_fEEShutDown >= ShutDown_Finalize2 || !"This should not be called unless we are in the last phase of shutdown!"); + ULONG_PTR arg = hr; + RaiseException(BOOTUP_EXCEPTION_COMPLUS, EXCEPTION_NONCONTINUABLE, 1, &arg); +} + + +//------------------------------------------------------------------------------- +// This simply tests to see if the exception object is a subclass of +// the descriminating class specified in the exception clause. +//------------------------------------------------------------------------------- +BOOL ExceptionIsOfRightType(TypeHandle clauseType, TypeHandle thrownType) +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + MODE_ANY; + FORBID_FAULT; + } + CONTRACTL_END; + + // if not resolved to, then it wasn't loaded and couldn't have been thrown + if (clauseType.IsNull()) + return FALSE; + + if (clauseType == thrownType) + return TRUE; + + // now look for parent match + TypeHandle superType = thrownType; + while (!superType.IsNull()) { + if (superType == clauseType) { + break; + } + superType = superType.GetParent(); + } + + return !superType.IsNull(); +} + +//=========================================================================== +// Gets the message text from an exception +//=========================================================================== +ULONG GetExceptionMessage(OBJECTREF throwable, + __inout_ecount(bufferLength) LPWSTR buffer, + ULONG bufferLength) +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_COOPERATIVE; + INJECT_FAULT(ThrowOutOfMemory()); + } + CONTRACTL_END; + + // Prefast buffer sanity check. Don't call the API with a zero length buffer. + if (bufferLength == 0) + { + _ASSERTE(bufferLength > 0); + return 0; + } + + StackSString result; + GetExceptionMessage(throwable, result); + + ULONG length = result.GetCount(); + LPCWSTR chars = result.GetUnicode(); + + if (length < bufferLength) + { + wcsncpy_s(buffer, bufferLength, chars, length); + } + else + { + wcsncpy_s(buffer, bufferLength, chars, bufferLength-1); + } + + return length; +} + +//----------------------------------------------------------------------------- +// Given an object, get the "message" from it. If the object is an Exception +// call Exception.InternalToString, otherwise, call Object.ToString +//----------------------------------------------------------------------------- +void GetExceptionMessage(OBJECTREF throwable, SString &result) +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_COOPERATIVE; + INJECT_FAULT(ThrowOutOfMemory()); + } + CONTRACTL_END; + + STRINGREF pString = GetExceptionMessage(throwable); + + // If call returned NULL (not empty), oh well, no message. + if (pString != NULL) + pString->GetSString(result); +} // void GetExceptionMessage() + +#if FEATURE_COMINTEROP +// This method returns IRestrictedErrorInfo associated with the ErrorObject. +// It checks whether the given managed exception object has __HasRestrictedLanguageErrorObject set +// in which case it returns the IRestrictedErrorInfo associated with the __RestrictedErrorObject. +IRestrictedErrorInfo* GetRestrictedErrorInfoFromErrorObject(OBJECTREF throwable) +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_COOPERATIVE; + INJECT_FAULT(ThrowOutOfMemory()); + } + CONTRACTL_END; + + IRestrictedErrorInfo* pRestrictedErrorInfo = NULL; + + // If there is no object, there is no restricted error. + if (throwable == NULL) + return NULL; + + _ASSERTE(IsException(throwable->GetMethodTable())); // what is the pathway here? + if (!IsException(throwable->GetMethodTable())) + { + return NULL; + } + + struct _gc { + OBJECTREF Throwable; + OBJECTREF RestrictedErrorInfoObjRef; + } gc; + + ZeroMemory(&gc, sizeof(gc)); + GCPROTECT_BEGIN(gc); + + gc.Throwable = throwable; + + // Get the MethodDesc on which we'll call. + MethodDescCallSite getRestrictedLanguageErrorObject(METHOD__EXCEPTION__TRY_GET_RESTRICTED_LANGUAGE_ERROR_OBJECT, &gc.Throwable); + + // Make the call. + ARG_SLOT Args[] = + { + ObjToArgSlot(gc.Throwable), + PtrToArgSlot(&gc.RestrictedErrorInfoObjRef) + }; + + BOOL bHasLanguageRestrictedErrorObject = (BOOL)getRestrictedLanguageErrorObject.Call_RetBool(Args); + + if(bHasLanguageRestrictedErrorObject) + { + // The __RestrictedErrorObject represents IRestrictedErrorInfo RCW of a non-CLR platform. Lets get the corresponding IRestrictedErrorInfo for it. + pRestrictedErrorInfo = (IRestrictedErrorInfo *)GetComIPFromObjectRef(&gc.RestrictedErrorInfoObjRef, IID_IRestrictedErrorInfo); + } + + GCPROTECT_END(); + + return pRestrictedErrorInfo; +} +#endif + +STRINGREF GetExceptionMessage(OBJECTREF throwable) +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_COOPERATIVE; + INJECT_FAULT(ThrowOutOfMemory()); + } + CONTRACTL_END; + + // If there is no object, there is no message. + if (throwable == NULL) + return NULL; + + // Assume we're calling Exception.InternalToString() ... + BinderMethodID sigID = METHOD__EXCEPTION__INTERNAL_TO_STRING; + + // ... but if it isn't an exception, call Object.ToString(). + _ASSERTE(IsException(throwable->GetMethodTable())); // what is the pathway here? + if (!IsException(throwable->GetMethodTable())) + { + sigID = METHOD__OBJECT__TO_STRING; + } + + // Return value. + STRINGREF pString = NULL; + + GCPROTECT_BEGIN(throwable); + + // Get the MethodDesc on which we'll call. + MethodDescCallSite toString(sigID, &throwable); + + // Make the call. + ARG_SLOT arg[1] = {ObjToArgSlot(throwable)}; + pString = toString.Call_RetSTRINGREF(arg); + + GCPROTECT_END(); + + return pString; +} + +HRESULT GetExceptionHResult(OBJECTREF throwable) +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + MODE_COOPERATIVE; + SO_TOLERANT; + } + CONTRACTL_END; + + HRESULT hr = E_FAIL; + if (throwable == NULL) + return hr; + + // Since any object can be thrown in managed code, not only instances of System.Exception subclasses + // we need to check to see if we are dealing with an exception before attempting to retrieve + // the HRESULT field. If we are not dealing with an exception, then we will simply return E_FAIL. + _ASSERTE(IsException(throwable->GetMethodTable())); // what is the pathway here? + if (IsException(throwable->GetMethodTable())) + { + hr = ((EXCEPTIONREF)throwable)->GetHResult(); + } + + return hr; +} // HRESULT GetExceptionHResult() + +DWORD GetExceptionXCode(OBJECTREF throwable) +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + MODE_COOPERATIVE; + SO_TOLERANT; + } + CONTRACTL_END; + + HRESULT hr = E_FAIL; + if (throwable == NULL) + return hr; + + // Since any object can be thrown in managed code, not only instances of System.Exception subclasses + // we need to check to see if we are dealing with an exception before attempting to retrieve + // the HRESULT field. If we are not dealing with an exception, then we will simply return E_FAIL. + _ASSERTE(IsException(throwable->GetMethodTable())); // what is the pathway here? + if (IsException(throwable->GetMethodTable())) + { + hr = ((EXCEPTIONREF)throwable)->GetXCode(); + } + + return hr; +} // DWORD GetExceptionXCode() + +//------------------------------------------------------------------------------ +// This function will extract some information from an Access Violation SEH +// exception, and store it in the System.AccessViolationException object. +// - the faulting instruction's IP. +// - the target address of the faulting instruction. +// - a code indicating attempted read vs write +//------------------------------------------------------------------------------ +void SetExceptionAVParameters( // No return. + OBJECTREF throwable, // The object into which to set the values. + EXCEPTION_RECORD *pExceptionRecord) // The SEH exception information. +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_COOPERATIVE; + PRECONDITION(throwable != NULL); + } + CONTRACTL_END; + + GCPROTECT_BEGIN(throwable) + { + // This should only be called for AccessViolationException + _ASSERTE(MscorlibBinder::GetException(kAccessViolationException) == throwable->GetMethodTable()); + + FieldDesc *pFD_ip = MscorlibBinder::GetField(FIELD__ACCESS_VIOLATION_EXCEPTION__IP); + FieldDesc *pFD_target = MscorlibBinder::GetField(FIELD__ACCESS_VIOLATION_EXCEPTION__TARGET); + FieldDesc *pFD_access = MscorlibBinder::GetField(FIELD__ACCESS_VIOLATION_EXCEPTION__ACCESSTYPE); + + _ASSERTE(pFD_ip->GetFieldType() == ELEMENT_TYPE_I); + _ASSERTE(pFD_target->GetFieldType() == ELEMENT_TYPE_I); + _ASSERTE(pFD_access->GetFieldType() == ELEMENT_TYPE_I4); + + void *ip = pExceptionRecord->ExceptionAddress; + void *target = (void*)(pExceptionRecord->ExceptionInformation[1]); + DWORD access = (DWORD)(pExceptionRecord->ExceptionInformation[0]); + + pFD_ip->SetValuePtr(throwable, ip); + pFD_target->SetValuePtr(throwable, target); + pFD_access->SetValue32(throwable, access); + + } + GCPROTECT_END(); + +} // void SetExceptionAVParameters() + +//------------------------------------------------------------------------------ +// This will call InternalPreserveStackTrace (if the throwable derives from +// System.Exception), to copy the stack trace to the _remoteStackTraceString. +// Doing so allows the stack trace of an exception caught by the runtime, and +// rethrown with COMPlusThrow(OBJECTREF thowable), to be preserved. Otherwise +// the exception handling code may clear the stack trace. (Generally, we see +// the stack trace preserved on win32 and cleared on win64.) +//------------------------------------------------------------------------------ +void ExceptionPreserveStackTrace( // No return. + OBJECTREF throwable) // Object about to be thrown. +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_COOPERATIVE; + INJECT_FAULT(ThrowOutOfMemory()); + } + CONTRACTL_END; + + // If there is no object, there is no stack trace to save. + if (throwable == NULL) + return; + + GCPROTECT_BEGIN(throwable); + + // Make sure it is derived from System.Exception, that it is not one of the + // preallocated exception objects, and that it has a stack trace to save. + if (IsException(throwable->GetMethodTable()) && + !CLRException::IsPreallocatedExceptionObject(throwable)) + { + LOG((LF_EH, LL_INFO1000, "ExceptionPreserveStackTrace called\n")); + + // We're calling Exception.InternalPreserveStackTrace() ... + BinderMethodID sigID = METHOD__EXCEPTION__INTERNAL_PRESERVE_STACK_TRACE; + + + // Get the MethodDesc on which we'll call. + MethodDescCallSite preserveStackTrace(sigID, &throwable); + + // Make the call. + ARG_SLOT arg[1] = {ObjToArgSlot(throwable)}; + preserveStackTrace.Call(arg); + } + + GCPROTECT_END(); + +} // void ExceptionPreserveStackTrace() + + +// We have to cache the MethodTable and FieldDesc for wrapped non-compliant exceptions the first +// time we wrap, because we cannot tolerate a GC when it comes time to detect and unwrap one. + +static MethodTable *pMT_RuntimeWrappedException; +static FieldDesc *pFD_WrappedException; + +// Non-compliant exceptions are immediately wrapped in a RuntimeWrappedException instance. The entire +// exception system can now ignore the possibility of these cases except: +// +// 1) IL_Throw, which must wrap via this API +// 2) Calls to Filters & Catch handlers, which must unwrap based on whether the assembly is on the legacy +// plan. +// +void WrapNonCompliantException(OBJECTREF *ppThrowable) +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_COOPERATIVE; + PRECONDITION(IsProtectedByGCFrame(ppThrowable)); + } + CONTRACTL_END; + + _ASSERTE(!IsException((*ppThrowable)->GetMethodTable())); + + EX_TRY + { + // idempotent operations, so the race condition is okay. + if (pMT_RuntimeWrappedException == NULL) + pMT_RuntimeWrappedException = MscorlibBinder::GetException(kRuntimeWrappedException); + + if (pFD_WrappedException == NULL) + pFD_WrappedException = MscorlibBinder::GetField(FIELD__RUNTIME_WRAPPED_EXCEPTION__WRAPPED_EXCEPTION); + + OBJECTREF orWrapper = AllocateObject(MscorlibBinder::GetException(kRuntimeWrappedException)); + + GCPROTECT_BEGIN(orWrapper); + + MethodDescCallSite ctor(METHOD__RUNTIME_WRAPPED_EXCEPTION__OBJ_CTOR, &orWrapper); + + ARG_SLOT args[] = + { + ObjToArgSlot(orWrapper), + ObjToArgSlot(*ppThrowable) + }; + + ctor.Call(args); + + *ppThrowable = orWrapper; + + GCPROTECT_END(); + } + EX_CATCH + { + // If we took an exception while binding, or running the constructor of the RuntimeWrappedException + // instance, we know that this new exception is CLS compliant. In fact, it's likely to be + // OutOfMemoryException, StackOverflowException or ThreadAbortException. + OBJECTREF orReplacement = GET_THROWABLE(); + + _ASSERTE(IsException(orReplacement->GetMethodTable())); + + *ppThrowable = orReplacement; + + } EX_END_CATCH(SwallowAllExceptions); +} + +// Before presenting an exception object to a handler (filter or catch, not finally or fault), it +// may be necessary to turn it back into a non-compliant exception. This is conditioned on an +// assembly level setting. +OBJECTREF PossiblyUnwrapThrowable(OBJECTREF throwable, Assembly *pAssembly) +{ + // Check if we are required to compute the RuntimeWrapExceptions status. + BOOL fIsRuntimeWrappedException = ((throwable != NULL) && (throwable->GetMethodTable() == pMT_RuntimeWrappedException)); + BOOL fRequiresComputingRuntimeWrapExceptionsStatus = (fIsRuntimeWrappedException && + (!(pAssembly->GetManifestModule()->IsRuntimeWrapExceptionsStatusComputed()))); + + CONTRACTL + { + THROWS; + // If we are required to compute the status of RuntimeWrapExceptions, then the operation could trigger a GC. + // Thus, conditionally setup the contract. + if (fRequiresComputingRuntimeWrapExceptionsStatus) GC_TRIGGERS; else GC_NOTRIGGER; + MODE_COOPERATIVE; + PRECONDITION(CheckPointer(pAssembly)); + } + CONTRACTL_END; + + if (fIsRuntimeWrappedException && (!pAssembly->GetManifestModule()->IsRuntimeWrapExceptions())) + { + // We already created the instance, fetched the field. We know it is + // not marshal by ref, or any of the other cases that might trigger GC. + ENABLE_FORBID_GC_LOADER_USE_IN_THIS_SCOPE(); + + throwable = pFD_WrappedException->GetRefValue(throwable); + } + + return throwable; +} + + +// This is used by a holder in CreateTypeInitializationExceptionObject to +// reset the state as appropriate. +void ResetTypeInitializationExceptionState(BOOL isAlreadyCreating) +{ + LIMITED_METHOD_CONTRACT; + if (!isAlreadyCreating) + GetThread()->ResetIsCreatingTypeInitException(); +} + +void CreateTypeInitializationExceptionObject(LPCWSTR pTypeThatFailed, + OBJECTREF *pInnerException, + OBJECTREF *pInitException, + OBJECTREF *pThrowable) +{ + CONTRACTL { + NOTHROW; + GC_TRIGGERS; + MODE_COOPERATIVE; + PRECONDITION(CheckPointer(pInnerException, NULL_OK)); + PRECONDITION(CheckPointer(pInitException)); + PRECONDITION(CheckPointer(pThrowable)); + PRECONDITION(IsProtectedByGCFrame(pInnerException)); + PRECONDITION(IsProtectedByGCFrame(pInitException)); + PRECONDITION(IsProtectedByGCFrame(pThrowable)); + PRECONDITION(CheckPointer(GetThread())); + } CONTRACTL_END; + + Thread *pThread = GetThread(); + *pThrowable = NULL; + + // This will make sure to put the thread back to its original state if something + // throws out of this function (like an OOM exception or something) + Holder< BOOL, DoNothing< BOOL >, ResetTypeInitializationExceptionState, FALSE, NoNull< BOOL > > + isAlreadyCreating(pThread->IsCreatingTypeInitException()); + + EX_TRY { + // This will contain the type of exception we want to create. Read comment below + // on why we'd want to create an exception other than TypeInitException + MethodTable *pMT; + BinderMethodID methodID; + + // If we are already in the midst of creating a TypeInitializationException object, + // and we get here, it means there was an exception thrown while initializing the + // TypeInitializationException type itself, or one of the types used by its class + // constructor. In this case, we're going to back down and use a SystemException + // object in its place. It is *KNOWN* that both these exception types have identical + // .ctor sigs "void instance (string, exception)" so both can be used interchangeably + // in the code that follows. + if (!isAlreadyCreating.GetValue()) { + pThread->SetIsCreatingTypeInitException(); + pMT = MscorlibBinder::GetException(kTypeInitializationException); + methodID = METHOD__TYPE_INIT_EXCEPTION__STR_EX_CTOR; + } + else { + // If we ever hit one of these asserts, then it is bad + // because we do not know what exception to return then. + _ASSERTE(pInnerException != NULL); + _ASSERTE(*pInnerException != NULL); + *pThrowable = *pInnerException; + *pInitException = *pInnerException; + goto ErrExit; + } + + // Allocate the exception object + *pThrowable = AllocateObject(pMT); + + MethodDescCallSite ctor(methodID, pThrowable); + + // Since the inner exception object in the .ctor is of type Exception, make sure + // that the object we're passed in derives from Exception. If not, pass NULL. + BOOL isException = FALSE; + if (pInnerException != NULL) + isException = IsException((*pInnerException)->GetMethodTable()); + + _ASSERTE(isException); // What pathway can give us non-compliant exceptions? + + STRINGREF sType = StringObject::NewString(pTypeThatFailed); + + // If the inner object derives from exception, set it as the third argument. + ARG_SLOT args[] = { ObjToArgSlot(*pThrowable), + ObjToArgSlot(sType), + ObjToArgSlot(isException ? *pInnerException : NULL) }; + + // Call the .ctor + ctor.Call(args); + + // On success, set the init exception. + *pInitException = *pThrowable; + } + EX_CATCH { + // If calling the constructor fails, then we'll call ourselves again, and this time + // through we will try and create an EEException object. If that fails, then the + // else block of this will be executed. + if (!isAlreadyCreating.GetValue()) { + CreateTypeInitializationExceptionObject(pTypeThatFailed, pInnerException, pInitException, pThrowable); + } + + // If we were already in the middle of creating a type init + // exception when we were called, we would have tried to create an EEException instead + // of a TypeInitException. + else { + // If we're recursing, then we should be calling ourselves from DoRunClassInitThrowing, + // in which case we're guaranteed that we're passing in all three arguments. + *pInitException = pInnerException ? *pInnerException : NULL; + *pThrowable = GET_THROWABLE(); + } + } EX_END_CATCH(SwallowAllExceptions); + + CONSISTENCY_CHECK(*pInitException != NULL || !pInnerException); + + ErrExit: + ; +} + +// ========================================================================== +// ComputeEnclosingHandlerNestingLevel +// +// This is code factored out of COMPlusThrowCallback to figure out +// what the number of nested exception handlers is. +// ========================================================================== +DWORD ComputeEnclosingHandlerNestingLevel(IJitManager *pIJM, + const METHODTOKEN& mdTok, + SIZE_T offsNat) +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + MODE_ANY; + FORBID_FAULT; + } + CONTRACTL_END; + + // Determine the nesting level of EHClause. Just walk the table + // again, and find out how many handlers enclose it + DWORD nestingLevel = 0; + EH_CLAUSE_ENUMERATOR pEnumState; + unsigned EHCount = pIJM->InitializeEHEnumeration(mdTok, &pEnumState); + + for (unsigned j=0; j<EHCount; j++) + { + EE_ILEXCEPTION_CLAUSE EHClause; + + pIJM->GetNextEHClause(&pEnumState,&EHClause); + _ASSERTE(EHClause.HandlerEndPC != (DWORD) -1); // <TODO> remove, only protects against a deprecated convention</TODO> + + if ((offsNat > EHClause.HandlerStartPC) && + (offsNat < EHClause.HandlerEndPC)) + { + nestingLevel++; + } + } + + return nestingLevel; +} + +// ******************************* EHRangeTreeNode ************************** // +EHRangeTreeNode::EHRangeTreeNode(void) +{ + WRAPPER_NO_CONTRACT; + CommonCtor(0, false); +} + +EHRangeTreeNode::EHRangeTreeNode(DWORD offset, bool fIsRange /* = false */) +{ + WRAPPER_NO_CONTRACT; + CommonCtor(offset, fIsRange); +} + +void EHRangeTreeNode::CommonCtor(DWORD offset, bool fIsRange) +{ + LIMITED_METHOD_CONTRACT; + + m_pTree = NULL; + m_clause = NULL; + + m_pContainedBy = NULL; + + m_offset = offset; + m_fIsRange = fIsRange; + m_fIsRoot = false; // must set this flag explicitly +} + +inline bool EHRangeTreeNode::IsRange() +{ + // Please see the header file for an explanation of this assertion. + _ASSERTE(m_fIsRoot || m_clause != NULL || !m_fIsRange); + return m_fIsRange; +} + +void EHRangeTreeNode::MarkAsRange() +{ + m_offset = 0; + m_fIsRange = true; + m_fIsRoot = false; +} + +inline bool EHRangeTreeNode::IsRoot() +{ + // Please see the header file for an explanation of this assertion. + _ASSERTE(m_fIsRoot || m_clause != NULL || !m_fIsRange); + return m_fIsRoot; +} + +void EHRangeTreeNode::MarkAsRoot(DWORD offset) +{ + m_offset = offset; + m_fIsRange = true; + m_fIsRoot = true; +} + +inline DWORD EHRangeTreeNode::GetOffset() +{ + _ASSERTE(m_clause == NULL); + _ASSERTE(IsRoot() || !IsRange()); + return m_offset; +} + +inline DWORD EHRangeTreeNode::GetTryStart() +{ + _ASSERTE(IsRange()); + _ASSERTE(!IsRoot()); + if (IsRoot()) + { + return 0; + } + else + { + return m_clause->TryStartPC; + } +} + +inline DWORD EHRangeTreeNode::GetTryEnd() +{ + _ASSERTE(IsRange()); + _ASSERTE(!IsRoot()); + if (IsRoot()) + { + return GetOffset(); + } + else + { + return m_clause->TryEndPC; + } +} + +inline DWORD EHRangeTreeNode::GetHandlerStart() +{ + _ASSERTE(IsRange()); + _ASSERTE(!IsRoot()); + if (IsRoot()) + { + return 0; + } + else + { + return m_clause->HandlerStartPC; + } +} + +inline DWORD EHRangeTreeNode::GetHandlerEnd() +{ + _ASSERTE(IsRange()); + _ASSERTE(!IsRoot()); + if (IsRoot()) + { + return GetOffset(); + } + else + { + return m_clause->HandlerEndPC; + } +} + +inline DWORD EHRangeTreeNode::GetFilterStart() +{ + _ASSERTE(IsRange()); + _ASSERTE(!IsRoot()); + if (IsRoot()) + { + return 0; + } + else + { + return m_clause->FilterOffset; + } +} + +// Get the end offset of the filter clause. This offset is exclusive. +inline DWORD EHRangeTreeNode::GetFilterEnd() +{ + _ASSERTE(IsRange()); + _ASSERTE(!IsRoot()); + if (IsRoot()) + { + // We should never get here if the "this" node is the root. + // By definition, the root contains everything. No checking is necessary. + return 0; + } + else + { + return m_FilterEndPC; + } +} + +bool EHRangeTreeNode::Contains(DWORD offset) +{ + WRAPPER_NO_CONTRACT; + + EHRangeTreeNode node(offset); + return Contains(&node); +} + +bool EHRangeTreeNode::TryContains(DWORD offset) +{ + WRAPPER_NO_CONTRACT; + + EHRangeTreeNode node(offset); + return TryContains(&node); +} + +bool EHRangeTreeNode::HandlerContains(DWORD offset) +{ + WRAPPER_NO_CONTRACT; + + EHRangeTreeNode node(offset); + return HandlerContains(&node); +} + +bool EHRangeTreeNode::FilterContains(DWORD offset) +{ + WRAPPER_NO_CONTRACT; + + EHRangeTreeNode node(offset); + return FilterContains(&node); +} + +bool EHRangeTreeNode::Contains(EHRangeTreeNode* pNode) +{ + LIMITED_METHOD_CONTRACT; + + // If we are checking a range of address, then we should check the end address inclusively. + if (pNode->IsRoot()) + { + // No node contains the root node. + return false; + } + else if (this->IsRoot()) + { + return (pNode->IsRange() ? + (pNode->GetTryEnd() <= this->GetOffset()) && (pNode->GetHandlerEnd() <= this->GetOffset()) + : (pNode->GetOffset() < this->GetOffset()) ); + } + else + { + return (this->TryContains(pNode) || this->HandlerContains(pNode) || this->FilterContains(pNode)); + } +} + +bool EHRangeTreeNode::TryContains(EHRangeTreeNode* pNode) +{ + LIMITED_METHOD_CONTRACT; + + _ASSERTE(this->IsRange()); + + if (pNode->IsRoot()) + { + // No node contains the root node. + return false; + } + else if (this->IsRoot()) + { + // We will only get here from GetTcf() to determine if an address is in a try clause. + // In this case we want to return false. + return false; + } + else + { + DWORD tryStart = this->GetTryStart(); + DWORD tryEnd = this->GetTryEnd(); + + // If we are checking a range of address, then we should check the end address inclusively. + if (pNode->IsRange()) + { + DWORD start = pNode->GetTryStart(); + DWORD end = pNode->GetTryEnd(); + + if (start == tryStart && end == tryEnd) + { + return false; + } + else if (start == end) + { + // This is effectively a single offset. + if ((tryStart <= start) && (end < tryEnd)) + { + return true; + } + } + else if ((tryStart <= start) && (end <= tryEnd)) + { + return true; + } + } + else + { + DWORD offset = pNode->GetOffset(); + if ((tryStart <= offset) && (offset < tryEnd)) + { + return true; + } + } + } + +#ifdef WIN64EXCEPTIONS + // If we are boot-strapping the tree, don't recurse down because the result could be unreliable. Note that + // even if we don't recurse, given a particular node, we can still always find its most specific container with + // the logic above, i.e. it's always safe to do one depth level of checking. + // + // To build the tree, all we need to know is the most specific container of a particular node. This can be + // done by just comparing the offsets of the try regions. However, funclets create a problem because even if + // a funclet is conceptually contained in a try region, we cannot determine this fact just by comparing the offsets. + // This is when we need to recurse the tree. Here is a classic example: + // try + // { + // try + // { + // } + // catch + // { + // // If the offset is here, then we need to recurse. + // } + // } + // catch + // { + // } + if (!m_pTree->m_fInitializing) + { + // Iterate all the contained clauses, and for the ones which are contained in the try region, + // ask if the requested range is contained by it. + USHORT i = 0; + USHORT numNodes = m_containees.Count(); + EHRangeTreeNode** ppNodes = NULL; + for (i = 0, ppNodes = m_containees.Table(); i < numNodes; i++, ppNodes++) + { + // This variable is purely used for readability. + EHRangeTreeNode* pNodeCur = *ppNodes; + + // it's possible for nested try blocks to have the same beginning and end offsets + if ( ( this->GetTryStart() <= pNodeCur->GetTryStart() ) && + ( pNodeCur->GetTryEnd() <= this->GetTryEnd() ) ) + { + if (pNodeCur->Contains(pNode)) + { + return true; + } + } + } + } +#endif // WIN64EXCEPTIONS + + return false; +} + +bool EHRangeTreeNode::HandlerContains(EHRangeTreeNode* pNode) +{ + LIMITED_METHOD_CONTRACT; + + _ASSERTE(this->IsRange()); + + if (pNode->IsRoot()) + { + // No node contains the root node. + return false; + } + else if (this->IsRoot()) + { + // We will only get here from GetTcf() to determine if an address is in a try clause. + // In this case we want to return false. + return false; + } + else + { + DWORD handlerStart = this->GetHandlerStart(); + DWORD handlerEnd = this->GetHandlerEnd(); + + // If we are checking a range of address, then we should check the end address inclusively. + if (pNode->IsRange()) + { + DWORD start = pNode->GetTryStart(); + DWORD end = pNode->GetTryEnd(); + + if (start == handlerStart && end == handlerEnd) + { + return false; + } + else if ((handlerStart <= start) && (end <= handlerEnd)) + { + return true; + } + } + else + { + DWORD offset = pNode->GetOffset(); + if ((handlerStart <= offset) && (offset < handlerEnd)) + { + return true; + } + } + } + +#ifdef WIN64EXCEPTIONS + // Refer to the comment in TryContains(). + if (!m_pTree->m_fInitializing) + { + // Iterate all the contained clauses, and for the ones which are contained in the try region, + // ask if the requested range is contained by it. + USHORT i = 0; + USHORT numNodes = m_containees.Count(); + EHRangeTreeNode** ppNodes = NULL; + for (i = 0, ppNodes = m_containees.Table(); i < numNodes; i++, ppNodes++) + { + // This variable is purely used for readability. + EHRangeTreeNode* pNodeCur = *ppNodes; + + if ( ( this->GetHandlerStart() <= pNodeCur->GetTryStart() ) && + ( pNodeCur->GetTryEnd() < this->GetHandlerEnd() ) ) + { + if (pNodeCur->Contains(pNode)) + { + return true; + } + } + } + } +#endif // WIN64EXCEPTIONS + + return false; +} + +bool EHRangeTreeNode::FilterContains(EHRangeTreeNode* pNode) +{ + LIMITED_METHOD_CONTRACT; + + _ASSERTE(this->IsRange()); + + if (pNode->IsRoot()) + { + // No node contains the root node. + return false; + } + else if (this->IsRoot() || !IsFilterHandler(this->m_clause)) + { + // We will only get here from GetTcf() to determine if an address is in a try clause. + // In this case we want to return false. + return false; + } + else + { + DWORD filterStart = this->GetFilterStart(); + DWORD filterEnd = this->GetFilterEnd(); + + // If we are checking a range of address, then we should check the end address inclusively. + if (pNode->IsRange()) + { + DWORD start = pNode->GetTryStart(); + DWORD end = pNode->GetTryEnd(); + + if (start == filterStart && end == filterEnd) + { + return false; + } + else if ((filterStart <= start) && (end <= filterEnd)) + { + return true; + } + } + else + { + DWORD offset = pNode->GetOffset(); + if ((filterStart <= offset) && (offset < filterEnd)) + { + return true; + } + } + } + +#ifdef WIN64EXCEPTIONS + // Refer to the comment in TryContains(). + if (!m_pTree->m_fInitializing) + { + // Iterate all the contained clauses, and for the ones which are contained in the try region, + // ask if the requested range is contained by it. + USHORT i = 0; + USHORT numNodes = m_containees.Count(); + EHRangeTreeNode** ppNodes = NULL; + for (i = 0, ppNodes = m_containees.Table(); i < numNodes; i++, ppNodes++) + { + // This variable is purely used for readability. + EHRangeTreeNode* pNodeCur = *ppNodes; + + if ( ( this->GetFilterStart() <= pNodeCur->GetTryStart() ) && + ( pNodeCur->GetTryEnd() < this->GetFilterEnd() ) ) + { + if (pNodeCur->Contains(pNode)) + { + return true; + } + } + } + } +#endif // WIN64EXCEPTIONS + + return false; +} + +EHRangeTreeNode* EHRangeTreeNode::GetContainer() +{ + return m_pContainedBy; +} + +HRESULT EHRangeTreeNode::AddNode(EHRangeTreeNode *pNode) +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + MODE_ANY; + INJECT_FAULT(return E_OUTOFMEMORY;); + PRECONDITION(pNode != NULL); + } + CONTRACTL_END; + + EHRangeTreeNode **ppEH = m_containees.Append(); + + if (ppEH == NULL) + return E_OUTOFMEMORY; + + (*ppEH) = pNode; + return S_OK; +} + +// ******************************* EHRangeTree ************************** // + +EHRangeTree::EHRangeTree(IJitManager* pIJM, + const METHODTOKEN& methodToken, + DWORD methodSize, + int cFunclet, + const DWORD * rgFunclet) +{ + LIMITED_METHOD_CONTRACT; + + LOG((LF_CORDB, LL_INFO10000, "EHRT::ERHT: already loaded!\n")); + + EH_CLAUSE_ENUMERATOR pEnumState; + m_EHCount = pIJM->InitializeEHEnumeration(methodToken, &pEnumState); + + _ASSERTE(m_EHCount != 0xFFFFFFFF); + + ULONG i = 0; + + m_rgClauses = NULL; + m_rgNodes = NULL; + m_root = NULL; + m_hrInit = S_OK; + m_fInitializing = true; + + if (m_EHCount > 0) + { + m_rgClauses = new (nothrow) EE_ILEXCEPTION_CLAUSE[m_EHCount]; + if (m_rgClauses == NULL) + { + m_hrInit = E_OUTOFMEMORY; + goto LError; + } + } + + LOG((LF_CORDB, LL_INFO10000, "EHRT::CC: m_ehcount:0x%x, m_rgClauses:0%x\n", + m_EHCount, m_rgClauses)); + + m_rgNodes = new (nothrow) EHRangeTreeNode[m_EHCount+1]; + if (m_rgNodes == NULL) + { + m_hrInit = E_OUTOFMEMORY; + goto LError; + } + + //this contains everything, even stuff on the last IP + m_root = &(m_rgNodes[m_EHCount]); + m_root->MarkAsRoot(methodSize + 1); + + LOG((LF_CORDB, LL_INFO10000, "EHRT::CC: rgNodes:0x%x\n", m_rgNodes)); + + if (m_EHCount ==0) + { + LOG((LF_CORDB, LL_INFO10000, "EHRT::CC: About to leave!\n")); + goto LSuccess; + } + + LOG((LF_CORDB, LL_INFO10000, "EHRT::CC: Sticking around!\n")); + + // First, load all the EH clauses into the object. + for (i = 0; i < m_EHCount; i++) + { + EE_ILEXCEPTION_CLAUSE * pEHClause = &(m_rgClauses[i]); + + LOG((LF_CORDB, LL_INFO10000, "EHRT::CC: i:0x%x!\n", i)); + + pIJM->GetNextEHClause(&pEnumState, pEHClause); + + LOG((LF_CORDB, LL_INFO10000, "EHRT::CC: EHRTT_JIT_MANAGER got clause\n", i)); + + LOG((LF_CORDB, LL_INFO10000, "EHRT::CC: clause 0x%x," + "addrof:0x%x\n", i, pEHClause )); + + _ASSERTE(pEHClause->HandlerEndPC != (DWORD) -1); // <TODO> remove, only protects against a deprecated convention</TODO> + + EHRangeTreeNode * pNodeCur = &(m_rgNodes[i]); + + pNodeCur->m_pTree = this; + pNodeCur->m_clause = pEHClause; + + if (pEHClause->Flags == COR_ILEXCEPTION_CLAUSE_FILTER) + { +#ifdef WIN64EXCEPTIONS + // Because of funclets, there is no way to guarantee the placement of a filter. + // Thus, we need to loop through the funclets to find the end offset. + for (int f = 0; f < cFunclet; f++) + { + // Check the start offset of the filter funclet. + if (pEHClause->FilterOffset == rgFunclet[f]) + { + if (f < (cFunclet - 1)) + { + // If it's NOT the last funclet, use the start offset of the next funclet. + pNodeCur->m_FilterEndPC = rgFunclet[f + 1]; + } + else + { + // If it's the last funclet, use the size of the method. + pNodeCur->m_FilterEndPC = methodSize; + } + break; + } + } +#else // WIN64EXCEPTIONS + // On x86, since the filter doesn't have an end FilterPC, the only way we can know the size + // of the filter is if it's located immediately prior to it's handler and immediately after + // its try region. We assume that this is, and if it isn't, we're so amazingly hosed that + // we can't continue. + if ((pEHClause->FilterOffset >= pEHClause->HandlerStartPC) || + (pEHClause->FilterOffset < pEHClause->TryEndPC)) + { + m_hrInit = CORDBG_E_SET_IP_IMPOSSIBLE; + goto LError; + } + pNodeCur->m_FilterEndPC = pEHClause->HandlerStartPC; +#endif // WIN64EXCEPTIONS + } + + pNodeCur->MarkAsRange(); + } + + LOG((LF_CORDB, LL_INFO10000, "EHRT::CC: about to do the second pass\n")); + + + // Second, for each EH, find it's most limited, containing clause + // On WIN64, we have duplicate clauses. There are two types of duplicate clauses. + // + // The first type is described in ExceptionHandling.cpp. This type doesn't add additional information to the + // EH tree structure. For example, if an offset is in the try region of a duplicate clause of this type, + // then some clause which comes before the duplicate clause should contain the offset in its handler region. + // Therefore, even though this type of duplicate clauses are added to the EH tree, they should never be used. + // + // The second type is what's called the protected clause. These clauses are used to mark the cloned finally + // region. They have an empty try region. Here's an example: + // + // // C# code + // try + // { + // A + // } + // finally + // { + // B + // } + // + // // jitted code + // parent + // ------- + // A + // B' + // ------- + // + // funclet + // ------- + // B + // ------- + // + // A protected clause covers the B' region in the parent method. In essence you can think of the method as + // having two try/finally regions, and that's exactly how protected clauses are handled in the EH tree. + // They are added to the EH tree just like any other EH clauses. + for (i = 0; i < m_EHCount; i++) + { + LOG((LF_CORDB, LL_INFO10000, "EHRT::CC: SP:0x%x\n", i)); + + EHRangeTreeNode * pNodeCur = &(m_rgNodes[i]); + + EHRangeTreeNode *pNodeCandidate = NULL; + pNodeCandidate = FindContainer(pNodeCur); + _ASSERTE(pNodeCandidate != NULL); + + pNodeCur->m_pContainedBy = pNodeCandidate; + + LOG((LF_CORDB, LL_INFO10000, "EHRT::CC: SP: about to add to tree\n")); + + HRESULT hr = pNodeCandidate->AddNode(pNodeCur); + if (FAILED(hr)) + { + m_hrInit = hr; + goto LError; + } + } + +LSuccess: + m_fInitializing = false; + return; + +LError: + LOG((LF_CORDB, LL_INFO10000, "EHRT::CC: LError - something went wrong!\n")); + + if (m_rgClauses != NULL) + { + delete [] m_rgClauses; + m_rgClauses = NULL; + } + + if (m_rgNodes != NULL) + { + delete [] m_rgNodes; + m_rgNodes = NULL; + } + + m_fInitializing = false; + + LOG((LF_CORDB, LL_INFO10000, "EHRT::CC: Falling off of LError!\n")); +} // Ctor Core + +EHRangeTree::~EHRangeTree() +{ + LIMITED_METHOD_CONTRACT; + + if (m_rgNodes != NULL) + delete [] m_rgNodes; + + if (m_rgClauses != NULL) + delete [] m_rgClauses; +} //Dtor + +EHRangeTreeNode *EHRangeTree::FindContainer(EHRangeTreeNode *pNodeSearch) +{ + LIMITED_METHOD_CONTRACT; + + EHRangeTreeNode *pNodeCandidate = NULL; + + // Examine the root, too. + for (ULONG iInner = 0; iInner < m_EHCount+1; iInner++) + { + EHRangeTreeNode *pNodeCur = &(m_rgNodes[iInner]); + + // Check if the current node contains the node we are searching for. + if ((pNodeSearch != pNodeCur) && + pNodeCur->Contains(pNodeSearch)) + { + // Update the candidate node if it is NULL or if it contains the current node + // (i.e. the current node is more specific than the candidate node). + if ((pNodeCandidate == NULL) || + pNodeCandidate->Contains(pNodeCur)) + { + pNodeCandidate = pNodeCur; + } + } + } + + return pNodeCandidate; +} + +EHRangeTreeNode *EHRangeTree::FindMostSpecificContainer(DWORD addr) +{ + WRAPPER_NO_CONTRACT; + + EHRangeTreeNode node(addr); + return FindContainer(&node); +} + +EHRangeTreeNode *EHRangeTree::FindNextMostSpecificContainer(EHRangeTreeNode *pNodeSearch, DWORD addr) +{ + WRAPPER_NO_CONTRACT; + + _ASSERTE(!m_fInitializing); + + EHRangeTreeNode **rgpNodes = pNodeSearch->m_containees.Table(); + + if (NULL == rgpNodes) + return pNodeSearch; + + // It's possible that no subrange contains the desired address, so + // keep a reasonable default around. + EHRangeTreeNode *pNodeCandidate = pNodeSearch; + + USHORT cSubRanges = pNodeSearch->m_containees.Count(); + EHRangeTreeNode **ppNodeCur = pNodeSearch->m_containees.Table(); + + for (int i = 0; i < cSubRanges; i++, ppNodeCur++) + { + if ((*ppNodeCur)->Contains(addr) && + pNodeCandidate->Contains((*ppNodeCur))) + { + pNodeCandidate = (*ppNodeCur); + } + } + + return pNodeCandidate; +} + +BOOL EHRangeTree::isAtStartOfCatch(DWORD offset) +{ + LIMITED_METHOD_CONTRACT; + + if (NULL != m_rgNodes && m_EHCount != 0) + { + for(unsigned i = 0; i < m_EHCount;i++) + { + if (m_rgNodes[i].m_clause->HandlerStartPC == offset && + (!IsFilterHandler(m_rgNodes[i].m_clause) && !IsFaultOrFinally(m_rgNodes[i].m_clause))) + return TRUE; + } + } + + return FALSE; +} + +enum TRY_CATCH_FINALLY +{ + TCF_NONE= 0, + TCF_TRY, + TCF_FILTER, + TCF_CATCH, + TCF_FINALLY, + TCF_COUNT, //count of all elements, not an element itself +}; + +#ifdef LOGGING +const char *TCFStringFromConst(TRY_CATCH_FINALLY tcf) +{ + LIMITED_METHOD_CONTRACT; + + switch( tcf ) + { + case TCF_NONE: + return "TCFS_NONE"; + break; + case TCF_TRY: + return "TCFS_TRY"; + break; + case TCF_FILTER: + return "TCF_FILTER"; + break; + case TCF_CATCH: + return "TCFS_CATCH"; + break; + case TCF_FINALLY: + return "TCFS_FINALLY"; + break; + case TCF_COUNT: + return "TCFS_COUNT"; + break; + default: + return "INVALID TCFS VALUE"; + break; + } +} +#endif //LOGGING + +// We're unwinding if we'll return to the EE's code. Otherwise +// we'll return to someplace in the current code. Anywhere outside +// this function is "EE code". +bool FinallyIsUnwinding(EHRangeTreeNode *pNode, + ICodeManager* pEECM, + PREGDISPLAY pReg, + SLOT addrStart) +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + MODE_ANY; + FORBID_FAULT; + } + CONTRACTL_END; + + const BYTE *pbRetAddr = pEECM->GetFinallyReturnAddr(pReg); + + if (pbRetAddr < (const BYTE *)addrStart) + return true; + + DWORD offset = (DWORD)(size_t)(pbRetAddr - addrStart); + EHRangeTreeNode *pRoot = pNode->m_pTree->m_root; + + if (!pRoot->Contains(offset)) + return true; + else + return false; +} + +#ifndef WIN64EXCEPTIONS +BOOL LeaveCatch(ICodeManager* pEECM, + Thread *pThread, + CONTEXT *pCtx, + void *methodInfoPtr, + unsigned offset) +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_ANY; + } + CONTRACTL_END; + + // We can assert these things here, and skip a call + // to COMPlusCheckForAbort later. + + // If no abort has been requested, + _ASSERTE((pThread->GetThrowable() != NULL) || + // or if there is a pending exception. + (!pThread->IsAbortRequested()) ); + + LPVOID esp = COMPlusEndCatchWorker(pThread); + + PopNestedExceptionRecords(esp, pCtx, pThread->GetExceptionListPtr()); + + // Do JIT-specific work + pEECM->LeaveCatch(methodInfoPtr, offset, pCtx); + + SetSP(pCtx, (UINT_PTR)esp); + return TRUE; +} +#endif // WIN64EXCEPTIONS + +TRY_CATCH_FINALLY GetTcf(EHRangeTreeNode *pNode, + unsigned offset) +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + MODE_ANY; + FORBID_FAULT; + } + CONTRACTL_END; + + _ASSERTE(pNode->IsRange() && !pNode->IsRoot()); + + TRY_CATCH_FINALLY tcf; + + if (!pNode->Contains(offset)) + { + tcf = TCF_NONE; + } + else if (pNode->TryContains(offset)) + { + tcf = TCF_TRY; + } + else if (pNode->FilterContains(offset)) + { + tcf = TCF_FILTER; + } + else + { + _ASSERTE(pNode->HandlerContains(offset)); + if (IsFaultOrFinally(pNode->m_clause)) + tcf = TCF_FINALLY; + else + tcf = TCF_CATCH; + } + + return tcf; +} + +const DWORD bEnter = 0x01; +const DWORD bLeave = 0x02; + +HRESULT IsLegalTransition(Thread *pThread, + bool fCanSetIPOnly, + DWORD fEnter, + EHRangeTreeNode *pNode, + DWORD offFrom, + DWORD offTo, + ICodeManager* pEECM, + PREGDISPLAY pReg, + SLOT addrStart, + void *methodInfoPtr, + PCONTEXT pCtx) +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_ANY; + } + CONTRACTL_END; + +#ifdef _DEBUG + if (fEnter & bEnter) + { + _ASSERTE(pNode->Contains(offTo)); + } + if (fEnter & bLeave) + { + _ASSERTE(pNode->Contains(offFrom)); + } +#endif //_DEBUG + + // First, figure out where we're coming from/going to + TRY_CATCH_FINALLY tcfFrom = GetTcf(pNode, + offFrom); + + TRY_CATCH_FINALLY tcfTo = GetTcf(pNode, + offTo); + + LOG((LF_CORDB, LL_INFO10000, "ILT: from %s to %s\n", + TCFStringFromConst(tcfFrom), + TCFStringFromConst(tcfTo))); + + // Now we'll consider, case-by-case, the various permutations that + // can arise + switch(tcfFrom) + { + case TCF_NONE: + case TCF_TRY: + { + switch(tcfTo) + { + case TCF_NONE: + case TCF_TRY: + { + return S_OK; + break; + } + + case TCF_FILTER: + { + return CORDBG_E_CANT_SETIP_INTO_OR_OUT_OF_FILTER; + break; + } + + case TCF_CATCH: + { + return CORDBG_E_CANT_SET_IP_INTO_CATCH; + break; + } + + case TCF_FINALLY: + { + return CORDBG_E_CANT_SET_IP_INTO_FINALLY; + break; + } + default: + break; + } + break; + } + + case TCF_FILTER: + { + switch(tcfTo) + { + case TCF_NONE: + case TCF_TRY: + case TCF_CATCH: + case TCF_FINALLY: + { + return CORDBG_E_CANT_SETIP_INTO_OR_OUT_OF_FILTER; + break; + } + case TCF_FILTER: + { + return S_OK; + break; + } + default: + break; + + } + break; + } + + case TCF_CATCH: + { + switch(tcfTo) + { + case TCF_NONE: + case TCF_TRY: + { +#if !defined(WIN64EXCEPTIONS) + CONTEXT *pFilterCtx = pThread->GetFilterContext(); + if (pFilterCtx == NULL) + return CORDBG_E_SET_IP_IMPOSSIBLE; + + if (!fCanSetIPOnly) + { + if (!LeaveCatch(pEECM, + pThread, + pFilterCtx, + methodInfoPtr, + offFrom)) + return E_FAIL; + } + return S_OK; +#else // WIN64EXCEPTIONS + // <NOTE> + // Setting IP out of a catch clause is not supported for WIN64EXCEPTIONS because of funclets. + // This scenario is disabled with approval from VS because it's not considered to + // be a common user scenario. + // </NOTE> + return CORDBG_E_CANT_SET_IP_OUT_OF_CATCH_ON_WIN64; +#endif // !WIN64EXCEPTIONS + break; + } + + case TCF_FILTER: + { + return CORDBG_E_CANT_SETIP_INTO_OR_OUT_OF_FILTER; + break; + } + + case TCF_CATCH: + { + return S_OK; + break; + } + + case TCF_FINALLY: + { + return CORDBG_E_CANT_SET_IP_INTO_FINALLY; + break; + } + default: + break; + } + break; + } + + case TCF_FINALLY: + { + switch(tcfTo) + { + case TCF_NONE: + case TCF_TRY: + { +#if !defined(WIN64EXCEPTIONS) + if (!FinallyIsUnwinding(pNode, pEECM, pReg, addrStart)) + { + CONTEXT *pFilterCtx = pThread->GetFilterContext(); + if (pFilterCtx == NULL) + return CORDBG_E_SET_IP_IMPOSSIBLE; + + if (!fCanSetIPOnly) + { + if (!pEECM->LeaveFinally(methodInfoPtr, + offFrom, + pFilterCtx)) + return E_FAIL; + } + return S_OK; + } + else + { + return CORDBG_E_CANT_SET_IP_OUT_OF_FINALLY; + } +#else // _WIN64 + // <NOTE> + // Setting IP out of a non-unwinding finally clause is not supported on WIN64EXCEPTIONS because of funclets. + // This scenario is disabled with approval from VS because it's not considered to be a common user + // scenario. + // </NOTE> + return CORDBG_E_CANT_SET_IP_OUT_OF_FINALLY_ON_WIN64; +#endif // _WIN64 + + break; + } + + case TCF_FILTER: + { + return CORDBG_E_CANT_SETIP_INTO_OR_OUT_OF_FILTER; + break; + } + + case TCF_CATCH: + { + return CORDBG_E_CANT_SET_IP_INTO_CATCH; + break; + } + + case TCF_FINALLY: + { + return S_OK; + break; + } + default: + break; + } + break; + } + break; + default: + break; + } + + _ASSERTE( !"IsLegalTransition: We should never reach this point!" ); + + return CORDBG_E_SET_IP_IMPOSSIBLE; +} + +// We need this to determine what +// to do based on whether the stack in general is empty +HRESULT DestinationIsValid(void *pDjiToken, + DWORD offTo, + EHRangeTree *pEHRT) +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + MODE_ANY; + FORBID_FAULT; + } + CONTRACTL_END; + + // We'll add a call to the DebugInterface that takes this + // & tells us if the destination is a stack empty point. +// DebuggerJitInfo *pDji = (DebuggerJitInfo *)pDjiToken; + + if (pEHRT->isAtStartOfCatch(offTo)) + return CORDBG_S_BAD_START_SEQUENCE_POINT; + else + return S_OK; +} // HRESULT DestinationIsValid() + +// We want to keep the 'worst' HRESULT - if one has failed (..._E_...) & the +// other hasn't, take the failing one. If they've both/neither failed, then +// it doesn't matter which we take. +// Note that this macro favors retaining the first argument +#define WORST_HR(hr1,hr2) (FAILED(hr1)?hr1:hr2) +HRESULT SetIPFromSrcToDst(Thread *pThread, + SLOT addrStart, // base address of method + DWORD offFrom, // native offset + DWORD offTo, // native offset + bool fCanSetIPOnly, // if true, don't do any real work + PREGDISPLAY pReg, + PCONTEXT pCtx, + void *pDji, + EHRangeTree *pEHRT) +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_ANY; + INJECT_FAULT(return E_OUTOFMEMORY;); + } + CONTRACTL_END; + + HRESULT hr = S_OK; + HRESULT hrReturn = S_OK; + bool fCheckOnly = true; + + EECodeInfo codeInfo((TADDR)(addrStart)); + + ICodeManager * pEECM = codeInfo.GetCodeManager(); + LPVOID methodInfoPtr = codeInfo.GetGCInfo(); + + // Do both checks here so compiler doesn't complain about skipping + // initialization b/c of goto. + if (fCanSetIPOnly && !pEECM->IsGcSafe(&codeInfo, offFrom)) + { + hrReturn = WORST_HR(hrReturn, CORDBG_E_SET_IP_IMPOSSIBLE); + } + + if (fCanSetIPOnly && !pEECM->IsGcSafe(&codeInfo, offTo)) + { + hrReturn = WORST_HR(hrReturn, CORDBG_E_SET_IP_IMPOSSIBLE); + } + + if ((hr = DestinationIsValid(pDji, offTo, pEHRT)) != S_OK + && fCanSetIPOnly) + { + hrReturn = WORST_HR(hrReturn,hr); + } + + // The basic approach is this: We'll start with the most specific (smallest) + // EHClause that contains the starting address. We'll 'back out', to larger + // and larger ranges, until we either find an EHClause that contains both + // the from and to addresses, or until we reach the root EHRangeTreeNode, + // which contains all addresses within it. At each step, we check/do work + // that the various transitions (from inside to outside a catch, etc). + // At that point, we do the reverse process - we go from the EHClause that + // encompasses both from and to, and narrow down to the smallest EHClause that + // encompasses the to point. We use our nifty data structure to manage + // the tree structure inherent in this process. + // + // NOTE: We do this process twice, once to check that we're not doing an + // overall illegal transition, such as ultimately set the IP into + // a catch, which is never allowed. We're doing this because VS + // calls SetIP without calling CanSetIP first, and so we should be able + // to return an error code and have the stack in the same condition + // as the start of the call, and so we shouldn't back out of clauses + // or move into them until we're sure that can be done. + +retryForCommit: + + EHRangeTreeNode *node; + EHRangeTreeNode *nodeNext; + node = pEHRT->FindMostSpecificContainer(offFrom); + + while (!node->Contains(offTo)) + { + hr = IsLegalTransition(pThread, + fCheckOnly, + bLeave, + node, + offFrom, + offTo, + pEECM, + pReg, + addrStart, + methodInfoPtr, + pCtx); + + if (FAILED(hr)) + { + hrReturn = WORST_HR(hrReturn,hr); + } + + node = node->GetContainer(); + // m_root prevents node from ever being NULL. + } + + if (node != pEHRT->m_root) + { + hr = IsLegalTransition(pThread, + fCheckOnly, + bEnter|bLeave, + node, + offFrom, + offTo, + pEECM, + pReg, + addrStart, + methodInfoPtr, + pCtx); + + if (FAILED(hr)) + { + hrReturn = WORST_HR(hrReturn,hr); + } + } + + nodeNext = pEHRT->FindNextMostSpecificContainer(node, + offTo); + + while(nodeNext != node) + { + hr = IsLegalTransition(pThread, + fCheckOnly, + bEnter, + nodeNext, + offFrom, + offTo, + pEECM, + pReg, + addrStart, + methodInfoPtr, + pCtx); + + if (FAILED(hr)) + { + hrReturn = WORST_HR(hrReturn, hr); + } + + node = nodeNext; + nodeNext = pEHRT->FindNextMostSpecificContainer(node, + offTo); + } + + // If it was the intention to actually set the IP and the above transition checks succeeded, + // then go back and do it all again but this time widen and narrow the thread's actual scope + if (!fCanSetIPOnly && fCheckOnly && SUCCEEDED(hrReturn)) + { + fCheckOnly = false; + goto retryForCommit; + } + + return hrReturn; +} // HRESULT SetIPFromSrcToDst() + +// This function should only be called if the thread is suspended and sitting in jitted code +BOOL IsInFirstFrameOfHandler(Thread *pThread, IJitManager *pJitManager, const METHODTOKEN& MethodToken, DWORD offset) +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + MODE_ANY; + FORBID_FAULT; + } + CONTRACTL_END; + + // if don't have a throwable the aren't processing an exception + if (IsHandleNullUnchecked(pThread->GetThrowableAsHandle())) + return FALSE; + + EH_CLAUSE_ENUMERATOR pEnumState; + unsigned EHCount = pJitManager->InitializeEHEnumeration(MethodToken, &pEnumState); + + for(ULONG i=0; i < EHCount; i++) + { + EE_ILEXCEPTION_CLAUSE EHClause; + pJitManager->GetNextEHClause(&pEnumState, &EHClause); + _ASSERTE(IsValidClause(&EHClause)); + + if ( offset >= EHClause.HandlerStartPC && offset < EHClause.HandlerEndPC) + return TRUE; + + // check if it's in the filter itself if we're not in the handler + if (IsFilterHandler(&EHClause) && offset >= EHClause.FilterOffset && offset < EHClause.HandlerStartPC) + return TRUE; + } + return FALSE; +} // BOOL IsInFirstFrameOfHandler() + + +#if !defined(WIN64EXCEPTIONS) + +//****************************************************************************** +// LookForHandler -- search for a function that will handle the exception. +//****************************************************************************** +LFH LookForHandler( // LFH return types + const EXCEPTION_POINTERS *pExceptionPointers, // The ExceptionRecord and ExceptionContext + Thread *pThread, // Thread on which to look (always current?) + ThrowCallbackType *tct) // Structure to pass back to callback functions. +{ + // We don't want to use a runtime contract here since this codepath is used during + // the processing of a hard SO. Contracts use a significant amount of stack + // which we can't afford for those cases. + STATIC_CONTRACT_THROWS; + STATIC_CONTRACT_GC_TRIGGERS; + STATIC_CONTRACT_MODE_COOPERATIVE; + + // go through to find if anyone handles the exception + StackWalkAction action = pThread->StackWalkFrames((PSTACKWALKFRAMESCALLBACK)COMPlusThrowCallback, + tct, + 0, //can't use FUNCTIONSONLY because the callback uses non-function frames to stop the walk + tct->pBottomFrame); + + // If someone handles it, the action will be SWA_ABORT with pFunc and dHandler indicating the + // function and handler that is handling the exception. Debugger can put a hook in here. + if (action == SWA_ABORT && tct->pFunc != NULL) + return LFH_FOUND; + + // nobody is handling it + return LFH_NOT_FOUND; +} // LFH LookForHandler() + +StackWalkAction COMPlusUnwindCallback (CrawlFrame *pCf, ThrowCallbackType *pData); + +//****************************************************************************** +// UnwindFrames +//****************************************************************************** +void UnwindFrames( // No return value. + Thread *pThread, // Thread to unwind. + ThrowCallbackType *tct) // Structure to pass back to callback function. +{ + STATIC_CONTRACT_THROWS; + STATIC_CONTRACT_GC_NOTRIGGER; + STATIC_CONTRACT_MODE_COOPERATIVE; + + if (pThread->IsExceptionInProgress()) + { + pThread->GetExceptionState()->GetFlags()->SetUnwindHasStarted(); + } + + #ifdef DEBUGGING_SUPPORTED + // + // If a debugger is attached, notify it that unwinding is going on. + // + if (CORDebuggerAttached()) + { + g_pDebugInterface->ManagedExceptionUnwindBegin(pThread); + } + #endif // DEBUGGING_SUPPORTED + + LOG((LF_EH, LL_INFO1000, "UnwindFrames: going to: pFunc:%#X, pStack:%#X\n", + tct->pFunc, tct->pStack)); + + pThread->StackWalkFrames((PSTACKWALKFRAMESCALLBACK)COMPlusUnwindCallback, + tct, + POPFRAMES, + tct->pBottomFrame); +} // void UnwindFrames() + +#endif // !defined(WIN64EXCEPTIONS) + +void StackTraceInfo::SaveStackTrace(BOOL bAllowAllocMem, OBJECTHANDLE hThrowable, BOOL bReplaceStack, BOOL bSkipLastElement) +{ + CONTRACTL + { + NOTHROW; + GC_TRIGGERS; + MODE_COOPERATIVE; + } + CONTRACTL_END; + + // Do not save stacktrace to preallocated exception. These are shared. + if (CLRException::IsPreallocatedExceptionHandle(hThrowable)) + { +#if defined(FEATURE_EXCEPTIONDISPATCHINFO) + // Preallocated exceptions will never have this flag set. However, its possible + // that after this flag is set for a regular exception but before we throw, we have an async + // exception like a RudeThreadAbort, which will replace the exception + // containing the restored stack trace. + // + // In such a case, we should clear the flag as the throwable representing the + // preallocated exception will not have the restored (or any) stack trace. + PTR_ThreadExceptionState pCurTES = GetThread()->GetExceptionState(); + pCurTES->ResetRaisingForeignException(); +#endif // defined(FEATURE_EXCEPTIONDISPATCHINFO) + + return; + } + + LOG((LF_EH, LL_INFO1000, "StackTraceInfo::SaveStackTrace (%p), alloc = %d, replace = %d, skiplast = %d\n", this, bAllowAllocMem, bReplaceStack, bSkipLastElement)); + + // if have bSkipLastElement, must also keep the stack + _ASSERTE(! bSkipLastElement || ! bReplaceStack); + + bool fSuccess = false; + MethodTable* pMT = ObjectFromHandle(hThrowable)->GetTrueMethodTable(); + +#if defined(FEATURE_EXCEPTIONDISPATCHINFO) + // Check if the flag indicating foreign exception raise has been setup or not, + // and then reset it so that subsequent processing of managed frames proceeds + // normally. + PTR_ThreadExceptionState pCurTES = GetThread()->GetExceptionState(); + BOOL fRaisingForeignException = pCurTES->IsRaisingForeignException(); + pCurTES->ResetRaisingForeignException(); +#endif // defined(FEATURE_EXCEPTIONDISPATCHINFO) + + if (bAllowAllocMem && m_dFrameCount != 0) + { + EX_TRY + { + // Only save stack trace info on exceptions + _ASSERTE(IsException(pMT)); // what is the pathway here? + if (!IsException(pMT)) + { + fSuccess = true; + } + else + { + // If the stack trace contains DynamicMethodDescs, we need to save the corrosponding + // System.Resolver objects in the Exception._dynamicMethods field. Failing to do that + // will cause an AV in the runtime when we try to visit those MethodDescs in the + // Exception._stackTrace field, because they have been recycled or destroyed. + unsigned iNumDynamics = 0; + + // How many DynamicMethodDescs do we need to keep alive? + for (unsigned iElement=0; iElement < m_dFrameCount; iElement++) + { + MethodDesc *pMethod = m_pStackTrace[iElement].pFunc; + _ASSERTE(pMethod); + + if (pMethod->IsLCGMethod()) + { + // Increment the number of new dynamic methods we have found + iNumDynamics++; + } + else + if (pMethod->GetMethodTable()->Collectible()) + { + iNumDynamics++; + } + } + + struct _gc + { + StackTraceArray stackTrace; +#if defined(FEATURE_EXCEPTIONDISPATCHINFO) + StackTraceArray stackTraceTemp; + PTRARRAYREF dynamicMethodsArrayTemp; +#endif // defined(FEATURE_EXCEPTIONDISPATCHINFO) + PTRARRAYREF dynamicMethodsArray; // Object array of Managed Resolvers + PTRARRAYREF pOrigDynamicArray; + + _gc() + : stackTrace() +#if defined(FEATURE_EXCEPTIONDISPATCHINFO) + , stackTraceTemp() + , dynamicMethodsArrayTemp(static_cast<PTRArray *>(NULL)) +#endif // defined(FEATURE_EXCEPTIONDISPATCHINFO) + , dynamicMethodsArray(static_cast<PTRArray *>(NULL)) + , pOrigDynamicArray(static_cast<PTRArray *>(NULL)) + {} + }; + + _gc gc; + GCPROTECT_BEGIN(gc); + +#if defined(FEATURE_EXCEPTIONDISPATCHINFO) + // If the flag indicating foreign exception raise has been setup, then check + // if the exception object has stacktrace or not. If we have an async non-preallocated + // exception after setting this flag but before we throw, then the new + // exception will not have any stack trace set and thus, we should behave as if + // the flag was not setup. + if (fRaisingForeignException) + { + // Get the reference to stack trace and reset our flag if applicable. + ((EXCEPTIONREF)ObjectFromHandle(hThrowable))->GetStackTrace(gc.stackTraceTemp); + if (gc.stackTraceTemp.Size() == 0) + { + fRaisingForeignException = FALSE; + } + } +#endif // defined(FEATURE_EXCEPTIONDISPATCHINFO) + + // Replace stack (i.e. build a new stack trace) only if we are not raising a foreign exception. + // If we are, then we will continue to extend the existing stack trace. + if (bReplaceStack +#if defined(FEATURE_EXCEPTIONDISPATCHINFO) + && (!fRaisingForeignException) +#endif // defined(FEATURE_EXCEPTIONDISPATCHINFO) + ) + { + // Cleanup previous info + gc.stackTrace.Append(m_pStackTrace, m_pStackTrace + m_dFrameCount); + + if (iNumDynamics) + { + // Adjust the allocation size of the array, if required + if (iNumDynamics > m_cDynamicMethodItems) + { + S_UINT32 cNewSize = S_UINT32(2) * S_UINT32(iNumDynamics); + if (cNewSize.IsOverflow()) + { + // Overflow here implies we cannot allocate memory anymore + LOG((LF_EH, LL_INFO100, "StackTraceInfo::SaveStackTrace - Cannot calculate initial resolver array size due to overflow!\n")); + COMPlusThrowOM(); + } + + m_cDynamicMethodItems = cNewSize.Value(); + } + + gc.dynamicMethodsArray = (PTRARRAYREF)AllocateObjectArray(m_cDynamicMethodItems, g_pObjectClass); + LOG((LF_EH, LL_INFO100, "StackTraceInfo::SaveStackTrace - allocated dynamic array for first frame of size %lu\n", + m_cDynamicMethodItems)); + } + + m_dCurrentDynamicIndex = 0; + } + else + { + // Fetch the stacktrace and the dynamic method array + ((EXCEPTIONREF)ObjectFromHandle(hThrowable))->GetStackTrace(gc.stackTrace, &gc.pOrigDynamicArray); + +#if defined(FEATURE_EXCEPTIONDISPATCHINFO) + if (fRaisingForeignException) + { + // Just before we append to the stack trace, mark the last recorded frame to be from + // the foreign thread so that we can insert an annotation indicating so when building + // the stack trace string. + size_t numCurrentFrames = gc.stackTrace.Size(); + if (numCurrentFrames > 0) + { + // "numCurrentFrames" can be zero if the user created an EDI using + // an unthrown exception. + StackTraceElement & refLastElementFromForeignStackTrace = gc.stackTrace[numCurrentFrames - 1]; + refLastElementFromForeignStackTrace.fIsLastFrameFromForeignStackTrace = TRUE; + } + } +#endif // defined(FEATURE_EXCEPTIONDISPATCHINFO) + + if (bSkipLastElement && gc.stackTrace.Size() != 0) + gc.stackTrace.AppendSkipLast(m_pStackTrace, m_pStackTrace + m_dFrameCount); + else + gc.stackTrace.Append(m_pStackTrace, m_pStackTrace + m_dFrameCount); + + ////////////////////////////// + + unsigned cOrigDynamic = 0; // number of objects in the old array + if (gc.pOrigDynamicArray != NULL) + { + cOrigDynamic = gc.pOrigDynamicArray->GetNumComponents(); + } + else + { + // Since there is no dynamic method array, reset the corresponding state variables + m_dCurrentDynamicIndex = 0; + m_cDynamicMethodItems = 0; + } + + if ((gc.pOrigDynamicArray != NULL) +#if defined(FEATURE_EXCEPTIONDISPATCHINFO) + || (fRaisingForeignException) +#endif // defined(FEATURE_EXCEPTIONDISPATCHINFO) + ) + { + // Since we have just restored the dynamic method array as well, + // calculate the dynamic array index which would be the total + // number of dynamic methods present in the stack trace. + // + // In addition to the ForeignException scenario, we need to reset these + // values incase the exception object in question is being thrown by + // multiple threads in parallel and thus, could have potentially different + // dynamic method array contents/size as opposed to the current state of + // StackTraceInfo. + + unsigned iStackTraceElements = (unsigned)gc.stackTrace.Size(); + m_dCurrentDynamicIndex = 0; + for (unsigned iIndex = 0; iIndex < iStackTraceElements; iIndex++) + { + MethodDesc *pMethod = gc.stackTrace[iIndex].pFunc; + if (pMethod) + { + if ((pMethod->IsLCGMethod()) || (pMethod->GetMethodTable()->Collectible())) + { + // Increment the number of new dynamic methods we have found + m_dCurrentDynamicIndex++; + } + } + } + + // Total number of elements in the dynamic method array should also be + // reset based upon the restored array size. + m_cDynamicMethodItems = cOrigDynamic; + } + + // Make the dynamic Array field reference the original array we got from the + // Exception object. If, below, we have to add new entries, we will add it to the + // array if it is allocated, or else, we will allocate it before doing so. + gc.dynamicMethodsArray = gc.pOrigDynamicArray; + + // Create an object array if we have new dynamic method entries AND + // if we are at the (or went past) the current size limit + if (iNumDynamics > 0) + { + // Reallocate the array if we are at the (or went past) the current size limit + unsigned cTotalDynamicMethodCount = m_dCurrentDynamicIndex; + + S_UINT32 cNewSum = S_UINT32(cTotalDynamicMethodCount) + S_UINT32(iNumDynamics); + if (cNewSum.IsOverflow()) + { + // If the current size is already the UINT32 max size, then we + // cannot go further. Overflow here implies we cannot allocate memory anymore. + LOG((LF_EH, LL_INFO100, "StackTraceInfo::SaveStackTrace - Cannot calculate resolver array size due to overflow!\n")); + COMPlusThrowOM(); + } + + cTotalDynamicMethodCount = cNewSum.Value(); + + if (cTotalDynamicMethodCount > m_cDynamicMethodItems) + { + // Double the current limit of the array. + S_UINT32 cNewSize = S_UINT32(2) * S_UINT32(cTotalDynamicMethodCount); + if (cNewSize.IsOverflow()) + { + // Overflow here implies that we cannot allocate any more memory + LOG((LF_EH, LL_INFO100, "StackTraceInfo::SaveStackTrace - Cannot resize resolver array beyond max size due to overflow!\n")); + COMPlusThrowOM(); + } + + m_cDynamicMethodItems = cNewSize.Value(); + gc.dynamicMethodsArray = (PTRARRAYREF)AllocateObjectArray(m_cDynamicMethodItems, + g_pObjectClass); + + _ASSERTE(!(cOrigDynamic && !gc.pOrigDynamicArray)); + + LOG((LF_EH, LL_INFO100, "StackTraceInfo::SaveStackTrace - resized dynamic array to size %lu\n", + m_cDynamicMethodItems)); + + // Copy previous entries if there are any, and update iCurDynamic to point + // to the following index. + if (cOrigDynamic && (gc.pOrigDynamicArray != NULL)) + { + memmoveGCRefs(gc.dynamicMethodsArray->GetDataPtr(), + gc.pOrigDynamicArray->GetDataPtr(), + cOrigDynamic * sizeof(Object *)); + + // m_dCurrentDynamicIndex is already referring to the correct index + // at which the next resolver object will be saved + } + } + else + { + // We are adding objects to the existing array. + // + // We have new dynamic method entries for which + // resolver objects need to be saved. Ensure + // that we have the array to store them + if (gc.dynamicMethodsArray == NULL) + { + _ASSERTE(m_cDynamicMethodItems > 0); + + gc.dynamicMethodsArray = (PTRARRAYREF)AllocateObjectArray(m_cDynamicMethodItems, + g_pObjectClass); + m_dCurrentDynamicIndex = 0; + LOG((LF_EH, LL_INFO100, "StackTraceInfo::SaveStackTrace - allocated dynamic array of size %lu\n", + m_cDynamicMethodItems)); + } + else + { + // The array exists for storing resolver objects. + // Simply set the index at which the next resolver + // will be stored in it. + } + } + } + } + + // Update _dynamicMethods field + if (iNumDynamics) + { + // At this point, we should be having a valid array for storage + _ASSERTE(gc.dynamicMethodsArray != NULL); + + // Assert that we are in valid range of the array in which resolver objects will be saved. + // We subtract 1 below since storage will start from m_dCurrentDynamicIndex onwards and not + // from (m_dCurrentDynamicIndex + 1). + _ASSERTE((m_dCurrentDynamicIndex + iNumDynamics - 1) < gc.dynamicMethodsArray->GetNumComponents()); + + for (unsigned i=0; i < m_dFrameCount; i++) + { + MethodDesc *pMethod = m_pStackTrace[i].pFunc; + _ASSERTE(pMethod); + + if (pMethod->IsLCGMethod()) + { + // We need to append the corresponding System.Resolver for + // this DynamicMethodDesc to keep it alive. + DynamicMethodDesc *pDMD = (DynamicMethodDesc *) pMethod; + OBJECTREF pResolver = pDMD->GetLCGMethodResolver()->GetManagedResolver(); + + _ASSERTE(pResolver != NULL); + + // Store Resolver information in the array + gc.dynamicMethodsArray->SetAt(m_dCurrentDynamicIndex++, pResolver); + } + else + if (pMethod->GetMethodTable()->Collectible()) + { + OBJECTREF pLoaderAllocator = pMethod->GetMethodTable()->GetLoaderAllocator()->GetExposedObject(); + _ASSERTE(pLoaderAllocator != NULL); + gc.dynamicMethodsArray->SetAt (m_dCurrentDynamicIndex++, pLoaderAllocator); + } + } + } + + ((EXCEPTIONREF)ObjectFromHandle(hThrowable))->SetStackTrace(gc.stackTrace, gc.dynamicMethodsArray); + + // Update _stackTraceString field. + ((EXCEPTIONREF)ObjectFromHandle(hThrowable))->SetStackTraceString(NULL); + fSuccess = true; + + GCPROTECT_END(); // gc + } + } + EX_CATCH + { + } + EX_END_CATCH(SwallowAllExceptions) + } + + ClearStackTrace(); + + if (!fSuccess) + { + EX_TRY + { + _ASSERTE(IsException(pMT)); // what is the pathway here? + if (bReplaceStack && IsException(pMT)) + ((EXCEPTIONREF)ObjectFromHandle(hThrowable))->ClearStackTraceForThrow(); + } + EX_CATCH + { + // Do nothing + } + EX_END_CATCH(SwallowAllExceptions); + } +} + +// Copy a context record, being careful about whether or not the target +// is large enough to support CONTEXT_EXTENDED_REGISTERS. +// +// NOTE: this function can ONLY be used when a filter function will return +// EXCEPTION_CONTINUE_EXECUTION. On AMD64, replacing the CONTEXT in any other +// situation may break exception unwinding. +// +// NOTE: this function MUST be used on AMD64. During exception handling, +// parts of the CONTEXT struct must not be modified. + + +// High 2 bytes are machine type. Low 2 bytes are register subset. +#define CONTEXT_EXTENDED_BIT (CONTEXT_EXTENDED_REGISTERS & 0xffff) + +VOID +ReplaceExceptionContextRecord(CONTEXT *pTarget, CONTEXT *pSource) +{ + LIMITED_METHOD_CONTRACT; + + _ASSERTE(pTarget); + _ASSERTE(pSource); + +#if defined(_TARGET_X86_) + //<TODO> + // @TODO IA64: CONTEXT_DEBUG_REGISTERS not defined on IA64, may need updated SDK + //</TODO> + + // Want CONTROL, INTEGER, SEGMENTS. If we have Floating Point, fine. + _ASSERTE((pSource->ContextFlags & CONTEXT_FULL) == CONTEXT_FULL); +#endif // _TARGET_X86_ + +#ifdef CONTEXT_EXTENDED_REGISTERS + + if (pSource->ContextFlags & CONTEXT_EXTENDED_BIT) + { + if (pTarget->ContextFlags & CONTEXT_EXTENDED_BIT) + { // Source and Target have EXTENDED bit set. + *pTarget = *pSource; + } + else + { // Source has but Target doesn't have EXTENDED bit set. (Target is shorter than Source.) + // Copy non-extended part of the struct, and reset the bit on the Target, as it was. + memcpy(pTarget, pSource, offsetof(CONTEXT, ExtendedRegisters)); + pTarget->ContextFlags &= ~CONTEXT_EXTENDED_BIT; // Target was short. Reset the extended bit. + } + } + else + { // Source does not have EXTENDED bit. Copy only non-extended part of the struct. + memcpy(pTarget, pSource, offsetof(CONTEXT, ExtendedRegisters)); + } + STRESS_LOG3(LF_SYNC, LL_INFO1000, "ReSet thread context EIP = %p ESP = %p EBP = %p\n", + GetIP((CONTEXT*)pTarget), GetSP((CONTEXT*)pTarget), GetFP((CONTEXT*)pTarget)); + +#else // !CONTEXT_EXTENDED_REGISTERS + + // Everything that's left + *pTarget = *pSource; + +#endif // !CONTEXT_EXTENDED_REGISTERS +} + +VOID FixupOnRethrow(Thread* pCurThread, EXCEPTION_POINTERS* pExceptionPointers) +{ + WRAPPER_NO_CONTRACT; + + ThreadExceptionState* pExState = pCurThread->GetExceptionState(); + +#ifdef FEATURE_INTERPRETER + // Abort if we don't have any state from the original exception. + if (!pExState->IsExceptionInProgress()) + { + return; + } +#endif // FEATURE_INTERPRETER + + // Don't allow rethrow of a STATUS_STACK_OVERFLOW -- it's a new throw of the COM+ exception. + if (pExState->GetExceptionCode() == STATUS_STACK_OVERFLOW) + { + return; + } + + // For COMPLUS exceptions, we don't need the original context for our rethrow. + if (!(pExState->IsComPlusException())) + { + _ASSERTE(pExState->GetExceptionRecord()); + + // don't copy parm args as have already supplied them on the throw + memcpy((void*)pExceptionPointers->ExceptionRecord, + (void*)pExState->GetExceptionRecord(), + offsetof(EXCEPTION_RECORD, ExceptionInformation)); + +// Replacing the exception context breaks unwinding on AMD64. It also breaks exception dispatch on IA64. +// The info saved by pExState will be given to exception filters. +#ifndef WIN64EXCEPTIONS + // Restore original context if available. + if (pExState->GetContextRecord()) + { + ReplaceExceptionContextRecord(pExceptionPointers->ContextRecord, + pExState->GetContextRecord()); + } +#endif // !WIN64EXCEPTIONS + } + + pExState->GetFlags()->SetIsRethrown(); +} + +struct RaiseExceptionFilterParam +{ + BOOL isRethrown; +}; + +LONG RaiseExceptionFilter(EXCEPTION_POINTERS* ep, LPVOID pv) +{ + STATIC_CONTRACT_NOTHROW; + STATIC_CONTRACT_GC_NOTRIGGER; + STATIC_CONTRACT_MODE_ANY; + + RaiseExceptionFilterParam *pParam = (RaiseExceptionFilterParam *) pv; + + if (1 == pParam->isRethrown) + { + // need to reset the EH info back to the original thrown exception + FixupOnRethrow(GetThread(), ep); +#ifdef WIN64EXCEPTIONS + // only do this once + pParam->isRethrown++; +#endif // WIN64EXCEPTIONS + } + else + { + CONSISTENCY_CHECK((2 == pParam->isRethrown) || (0 == pParam->isRethrown)); + } + + return EXCEPTION_CONTINUE_SEARCH; +} + +//========================================================================== +// Throw an object. +//========================================================================== +VOID DECLSPEC_NORETURN RaiseTheException(OBJECTREF throwable, BOOL rethrow +#ifdef FEATURE_CORRUPTING_EXCEPTIONS + , CorruptionSeverity severity +#endif // FEATURE_CORRUPTING_EXCEPTIONS + ) +{ + STATIC_CONTRACT_THROWS; + STATIC_CONTRACT_GC_TRIGGERS; + STATIC_CONTRACT_MODE_COOPERATIVE; + + LOG((LF_EH, LL_INFO100, "RealCOMPlusThrow throwing %s\n", + throwable->GetTrueMethodTable()->GetDebugClassName())); + + if (throwable == NULL) + { + _ASSERTE(!"RealCOMPlusThrow(OBJECTREF) called with NULL argument. Somebody forgot to post an exception!"); + EEPOLICY_HANDLE_FATAL_ERROR(COR_E_EXECUTIONENGINE); + } + + if (g_CLRPolicyRequested && + throwable->GetMethodTable() == g_pOutOfMemoryExceptionClass) + { + // We depends on UNINSTALL_UNWIND_AND_CONTINUE_HANDLER to handle out of memory escalation. + // We should throw c++ exception instead. + ThrowOutOfMemory(); + } +#ifdef FEATURE_STACK_PROBE + else if (throwable == CLRException::GetPreallocatedStackOverflowException()) + { + ThrowStackOverflow(); + } +#else + _ASSERTE(throwable != CLRException::GetPreallocatedStackOverflowException()); +#endif + +#ifdef FEATURE_CORRUPTING_EXCEPTIONS + if (!g_pConfig->LegacyCorruptedStateExceptionsPolicy()) + { + // This is Scenario 3 described in clrex.h around the definition of SET_CE_RETHROW_FLAG_FOR_EX_CATCH macro. + // + // We are here because the VM is attempting to throw a managed exception. It is posssible this exception + // may not be seen by CLR's exception handler for managed code (e.g. there maybe an EX_CATCH up the stack + // that will swallow or rethrow this exception). In the following scenario: + // + // [VM1 - RethrowCSE] -> [VM2 - RethrowCSE] -> [VM3 - RethrowCSE] -> <managed code> + // + // When managed code throws a CSE (e.g. TargetInvocationException flagged as CSE), [VM3] will rethrow it and we will + // enter EX_CATCH in VM2 which is supposed to rethrow it as well. Two things can happen: + // + // 1) The implementation of EX_CATCH in VM2 throws a new managed exception *before* rethrow policy is applied and control + // will reach EX_CATCH in VM1, OR + // + // 2) EX_CATCH in VM2 swallows the exception, comes out of the catch block and later throws a new managed exception that + // will be caught by EX_CATCH in VM1. + // + // In either of the cases, rethrow in VM1 should be on the basis of the new managed exception's corruption severity. + // + // To support this scenario, we set corruption severity of the managed exception VM is throwing. If its a rethrow, + // it implies we are rethrowing the last exception that was seen by CLR's managed code exception handler. In such a case, + // we will copy over the corruption severity of that exception. + + // If throwable indicates corrupted state, forcibly set the severity. + if (CEHelper::IsProcessCorruptedStateException(throwable)) + { + severity = ProcessCorrupting; + } + + // No one should have passed us an invalid severity. + _ASSERTE(severity > NotSet); + + if (severity == NotSet) + { + severity = NotCorrupting; + } + + // Update the corruption severity of the exception being thrown by the VM. + GetThread()->GetExceptionState()->SetLastActiveExceptionCorruptionSeverity(severity); + + // Exception's corruption severity should be reused in reraise if this exception leaks out from the VM + // into managed code + CEHelper::MarkLastActiveExceptionCorruptionSeverityForReraiseReuse(); + + LOG((LF_EH, LL_INFO100, "RaiseTheException - Set VM thrown managed exception severity to %d.\n", severity)); + } + +#endif // FEATURE_CORRUPTING_EXCEPTIONS + + RaiseTheExceptionInternalOnly(throwable,rethrow); +} + +HRESULT GetHRFromThrowable(OBJECTREF throwable) +{ + STATIC_CONTRACT_THROWS; + STATIC_CONTRACT_GC_TRIGGERS; + STATIC_CONTRACT_MODE_ANY; + + HRESULT hr = E_FAIL; + MethodTable *pMT = throwable->GetTrueMethodTable(); + + // Only Exception objects have a HResult field + // So don't fetch the field unless we have an exception + + _ASSERTE(IsException(pMT)); // what is the pathway here? + if (IsException(pMT)) + { + hr = ((EXCEPTIONREF)throwable)->GetHResult(); + } + + return hr; +} + + +VOID DECLSPEC_NORETURN RaiseTheExceptionInternalOnly(OBJECTREF throwable, BOOL rethrow, BOOL fForStackOverflow) +{ + STATIC_CONTRACT_THROWS; + STATIC_CONTRACT_GC_TRIGGERS; + STATIC_CONTRACT_MODE_COOPERATIVE; + + STRESS_LOG3(LF_EH, LL_INFO100, "******* MANAGED EXCEPTION THROWN: Object thrown: %p MT %pT rethrow %d\n", + OBJECTREFToObject(throwable), (throwable!=0)?throwable->GetMethodTable():0, rethrow); + +#ifdef STRESS_LOG + // Any object could have been thrown, but System.Exception objects have useful information for the stress log + if (!NingenEnabled() && throwable == CLRException::GetPreallocatedStackOverflowException()) + { + // if are handling an SO, don't try to get all that other goop. It isn't there anyway, + // and it could cause us to take another SO. + STRESS_LOG1(LF_EH, LL_INFO100, "Exception HRESULT = 0x%x \n", COR_E_STACKOVERFLOW); + } + else if (throwable != 0) + { + _ASSERTE(IsException(throwable->GetMethodTable())); + + int hr = ((EXCEPTIONREF)throwable)->GetHResult(); + STRINGREF message = ((EXCEPTIONREF)throwable)->GetMessage(); + OBJECTREF innerEH = ((EXCEPTIONREF)throwable)->GetInnerException(); + + STRESS_LOG4(LF_EH, LL_INFO100, "Exception HRESULT = 0x%x Message String 0x%p (db will display) InnerException %p MT %pT\n", + hr, OBJECTREFToObject(message), OBJECTREFToObject(innerEH), (innerEH!=0)?innerEH->GetMethodTable():0); + } +#endif + + struct Param : RaiseExceptionFilterParam + { + OBJECTREF throwable; + BOOL fForStackOverflow; + ULONG_PTR exceptionArgs[INSTANCE_TAGGED_SEH_PARAM_ARRAY_SIZE]; + Thread *pThread; + ThreadExceptionState* pExState; + } param; + param.isRethrown = rethrow ? 1 : 0; // normalize because we use it as a count in RaiseExceptionFilter + param.throwable = throwable; + param.fForStackOverflow = fForStackOverflow; + param.pThread = GetThread(); + + _ASSERTE(param.pThread); + param.pExState = param.pThread->GetExceptionState(); + + // Make sure that the object being thrown belongs in the current appdomain. + #if defined(_DEBUG) && CHECK_APP_DOMAIN_LEAKS + if (param.throwable != NULL) + { + GCPROTECT_BEGIN(param.throwable); + if (!CLRException::IsPreallocatedExceptionObject(param.throwable)) + _ASSERTE(param.throwable->CheckAppDomain(GetAppDomain())); + GCPROTECT_END(); + } + else + { // throwable is NULL -- that shouldn't happen + _ASSERTE(NingenEnabled() || param.throwable != NULL); + } + #endif + + if (param.pThread->IsRudeAbortInitiated()) + { + // Nobody should be able to swallow rude thread abort. + param.throwable = CLRException::GetPreallocatedRudeThreadAbortException(); + } + +#if 0 + // TODO: enable this after we change RealCOMPlusThrow +#ifdef _DEBUG + // If ThreadAbort exception is thrown, the thread should be marked with AbortRequest. + // If not, we may see unhandled exception. + if (param.throwable->GetTrueMethodTable() == g_pThreadAbortExceptionClass) + { + _ASSERTE(GetThread()->IsAbortRequested() +#ifdef _TARGET_X86_ + || + GetFirstCOMPlusSEHRecord(this) == EXCEPTION_CHAIN_END +#endif + ); + } +#endif +#endif + + // raise + PAL_TRY(Param *, pParam, ¶m) + { + //_ASSERTE(! pParam->isRethrown || pParam->pExState->m_pExceptionRecord); + ULONG_PTR *args = NULL; + ULONG argCount = 0; + ULONG flags = 0; + ULONG code = 0; + + // Always save the current object in the handle so on rethrow we can reuse it. This is important as it + // contains stack trace info. + // + // Note: we use SafeSetLastThrownObject, which will try to set the throwable and if there are any problems, + // it will set the throwable to something appropiate (like OOM exception) and return the new + // exception. Thus, the user's exception object can be replaced here. + pParam->throwable = NingenEnabled() ? NULL : pParam->pThread->SafeSetLastThrownObject(pParam->throwable); + + if (!pParam->isRethrown || +#ifdef FEATURE_INTERPRETER + !pParam->pExState->IsExceptionInProgress() || +#endif // FEATURE_INTERPRETER + pParam->pExState->IsComPlusException() || + (pParam->pExState->GetExceptionCode() == STATUS_STACK_OVERFLOW)) + { + ULONG_PTR hr = NingenEnabled() ? E_FAIL : GetHRFromThrowable(pParam->throwable); + + args = pParam->exceptionArgs; + argCount = MarkAsThrownByUs(args, hr); + flags = EXCEPTION_NONCONTINUABLE; + code = EXCEPTION_COMPLUS; + } + else + { + // Exception code should be consistent. + _ASSERTE((DWORD)(pParam->pExState->GetExceptionRecord()->ExceptionCode) == pParam->pExState->GetExceptionCode()); + + args = pParam->pExState->GetExceptionRecord()->ExceptionInformation; + argCount = pParam->pExState->GetExceptionRecord()->NumberParameters; + flags = pParam->pExState->GetExceptionRecord()->ExceptionFlags; + code = pParam->pExState->GetExceptionRecord()->ExceptionCode; + } + + if (pParam->pThread->IsAbortInitiated () && IsExceptionOfType(kThreadAbortException,&pParam->throwable)) + { + pParam->pThread->ResetPreparingAbort(); + + if (pParam->pThread->GetFrame() == FRAME_TOP) + { + // There is no more managed code on stack. + pParam->pThread->EEResetAbort(Thread::TAR_ALL); + } + } + + // Can't access the exception object when are in pre-emptive, so find out before + // if its an SO. + BOOL fIsStackOverflow = IsExceptionOfType(kStackOverflowException, &pParam->throwable); + + if (fIsStackOverflow || pParam->fForStackOverflow) + { + // Don't probe if we're already handling an SO. Just throw the exception. + RaiseException(code, flags, argCount, args); + } + + // Probe for sufficient stack. + PUSH_STACK_PROBE_FOR_THROW(pParam->pThread); + +#ifndef STACK_GUARDS_DEBUG + // This needs to be both here and inside the handler below + // enable preemptive mode before call into OS + GCX_PREEMP_NO_DTOR(); + + // In non-debug, we can just raise the exception once we've probed. + RaiseException(code, flags, argCount, args); + +#else + // In a debug build, we need to unwind our probe structure off the stack. + BaseStackGuard *pThrowGuard = NULL; + // Stach away the address of the guard we just pushed above in PUSH_STACK_PROBE_FOR_THROW + SAVE_ADDRESS_OF_STACK_PROBE_FOR_THROW(pThrowGuard); + + // Add the stack guard reference to the structure below so that it can be accessed within + // PAL_TRY as well + struct ParamInner + { + ULONG code; + ULONG flags; + ULONG argCount; + ULONG_PTR *args; + BaseStackGuard *pGuard; + } param; + param.code = code; + param.flags = flags; + param.argCount = argCount; + param.args = args; + param.pGuard = pThrowGuard; + + PAL_TRY(ParamInner *, pParam, ¶m) + { + // enable preemptive mode before call into OS + GCX_PREEMP_NO_DTOR(); + + RaiseException(pParam->code, pParam->flags, pParam->argCount, pParam->args); + + // We never return from RaiseException, so shouldn't have to call SetNoException. + // However, in the debugger we can, and if we don't call SetNoException we get + // a short-circuit return assert. + RESET_EXCEPTION_FROM_STACK_PROBE_FOR_THROW(pParam->pGuard); + } + PAL_FINALLY + { + // pop the guard that we pushed above in PUSH_STACK_PROBE_FOR_THROW + POP_STACK_PROBE_FOR_THROW(pThrowGuard); + } + PAL_ENDTRY +#endif + } + PAL_EXCEPT_FILTER (RaiseExceptionFilter) + { + } + PAL_ENDTRY + _ASSERTE(!"Cannot continue after COM+ exception"); // Debugger can bring you here. + // For example, + // Debugger breaks in due to second chance exception (unhandled) + // User hits 'g' + // Then debugger can bring us here. + EEPOLICY_HANDLE_FATAL_ERROR(COR_E_EXECUTIONENGINE); +} + + +// INSTALL_COMPLUS_EXCEPTION_HANDLER has a filter, so must put the call in a separate fcn +static VOID DECLSPEC_NORETURN RealCOMPlusThrowWorker(OBJECTREF throwable, BOOL rethrow +#ifdef FEATURE_CORRUPTING_EXCEPTIONS + , CorruptionSeverity severity +#endif // FEATURE_CORRUPTING_EXCEPTIONS +) { + STATIC_CONTRACT_THROWS; + STATIC_CONTRACT_GC_TRIGGERS; + STATIC_CONTRACT_MODE_ANY; + + // RaiseTheException will throw C++ OOM and SO, so that our escalation policy can kick in. + // Unfortunately, COMPlusFrameHandler installed here, will try to create managed exception object. + // We may hit a recursion. + + if (g_CLRPolicyRequested && + throwable->GetMethodTable() == g_pOutOfMemoryExceptionClass) + { + // We depends on UNINSTALL_UNWIND_AND_CONTINUE_HANDLER to handle out of memory escalation. + // We should throw c++ exception instead. + ThrowOutOfMemory(); + } +#ifdef FEATURE_STACK_PROBE + else if (throwable == CLRException::GetPreallocatedStackOverflowException()) + { + ThrowStackOverflow(); + } +#else + _ASSERTE(throwable != CLRException::GetPreallocatedStackOverflowException()); +#endif + + // TODO: Do we need to install COMPlusFrameHandler here? + INSTALL_COMPLUS_EXCEPTION_HANDLER(); + RaiseTheException(throwable, rethrow +#ifdef FEATURE_CORRUPTING_EXCEPTIONS + , severity +#endif // FEATURE_CORRUPTING_EXCEPTIONS + ); + UNINSTALL_COMPLUS_EXCEPTION_HANDLER(); +} + + +VOID DECLSPEC_NORETURN RealCOMPlusThrow(OBJECTREF throwable, BOOL rethrow +#ifdef FEATURE_CORRUPTING_EXCEPTIONS + , CorruptionSeverity severity +#endif // FEATURE_CORRUPTING_EXCEPTIONS +) { + STATIC_CONTRACT_THROWS; + STATIC_CONTRACT_GC_TRIGGERS; + STATIC_CONTRACT_MODE_ANY; + GCPROTECT_BEGIN(throwable); + + _ASSERTE(IsException(throwable->GetMethodTable())); + + // This may look a bit odd, but there is an explaination. The rethrow boolean + // means that an actual RaiseException(EXCEPTION_COMPLUS,...) is being re-thrown, + // and that the exception context saved on the Thread object should replace + // the exception context from the upcoming RaiseException(). There is logic + // in the stack trace code to preserve MOST of the stack trace, but to drop the + // last element of the stack trace (has to do with having the address of the rethrow + // instead of the address of the original call in the stack trace. That is + // controversial itself, but we won't get into that here.) + // However, if this is not re-raising that original exception, but rather a new + // os exception for what may be an existing exception object, it is generally + // a good thing to preserve the stack trace. + if (!rethrow) + { + ExceptionPreserveStackTrace(throwable); + } + + RealCOMPlusThrowWorker(throwable, rethrow +#ifdef FEATURE_CORRUPTING_EXCEPTIONS + , severity +#endif // FEATURE_CORRUPTING_EXCEPTIONS + ); + + GCPROTECT_END(); +} + +VOID DECLSPEC_NORETURN RealCOMPlusThrow(OBJECTREF throwable +#ifdef FEATURE_CORRUPTING_EXCEPTIONS + , CorruptionSeverity severity +#endif // FEATURE_CORRUPTING_EXCEPTIONS + ) +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_COOPERATIVE; + } + CONTRACTL_END; + + RealCOMPlusThrow(throwable, FALSE +#ifdef FEATURE_CORRUPTING_EXCEPTIONS + , severity +#endif // FEATURE_CORRUPTING_EXCEPTIONS + ); +} + +// this function finds the managed callback to get a resource +// string from the then current local domain and calls it +// this could be a lot of work +STRINGREF GetResourceStringFromManaged(STRINGREF key) +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_COOPERATIVE; + PRECONDITION(key != NULL); + } + CONTRACTL_END; + + struct xx { + STRINGREF key; + STRINGREF ret; + } gc; + + gc.key = key; + gc.ret = NULL; + + // The standard probe isn't good enough here. It's possible that we only have ~14 pages of stack + // left. By the time we transition to the default domain and start fetching this resource string, + // another 12 page probe could fail. + // This failing probe would cause us to unload the default appdomain, which would cause us + // to take down the process. + + // Instead, let's probe for a lots more stack to make sure that doesn' happen. + + // We need to have enough stack to survive 2 more probes... the original entrypoint back + // into mscorwks after we go into managed code, and a "large" probe that protects the GC + + INTERIOR_STACK_PROBE_FOR(GetThread(), DEFAULT_ENTRY_PROBE_AMOUNT * 2); + GCPROTECT_BEGIN(gc); + + MethodDescCallSite getResourceStringLocal(METHOD__ENVIRONMENT__GET_RESOURCE_STRING_LOCAL); + + // Call Environment::GetResourceStringLocal(String name). Returns String value (or maybe null) + + ENTER_DOMAIN_PTR(SystemDomain::System()->DefaultDomain(),ADV_DEFAULTAD); + + // Don't need to GCPROTECT pArgs, since it's not used after the function call. + + ARG_SLOT pArgs[1] = { ObjToArgSlot(gc.key) }; + gc.ret = getResourceStringLocal.Call_RetSTRINGREF(pArgs); + + END_DOMAIN_TRANSITION; + + GCPROTECT_END(); + + END_INTERIOR_STACK_PROBE; + + + return gc.ret; +} + +// This function does poentially a LOT of work (loading possibly 50 classes). +// The return value is an un-GC-protected string ref, or possibly NULL. +void ResMgrGetString(LPCWSTR wszResourceName, STRINGREF * ppMessage) +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_COOPERATIVE; + } + CONTRACTL_END; + + _ASSERTE(ppMessage != NULL); + + if (wszResourceName == NULL || *wszResourceName == W('\0')) + { + ppMessage = NULL; + return; + } + + // this function never looks at name again after + // calling the helper so no need to GCPROTECT it + STRINGREF name = StringObject::NewString(wszResourceName); + + if (wszResourceName != NULL) + { + STRINGREF value = GetResourceStringFromManaged(name); + + _ASSERTE(value!=NULL || !"Resource string lookup failed - possible misspelling or .resources missing or out of date?"); + *ppMessage = value; + } +} + +// GetResourceFromDefault +// transition to the default domain and get a resource there +FCIMPL1(Object*, GetResourceFromDefault, StringObject* keyUnsafe) +{ + FCALL_CONTRACT; + + STRINGREF ret = NULL; + STRINGREF key = (STRINGREF)keyUnsafe; + + HELPER_METHOD_FRAME_BEGIN_RET_2(ret, key); + + ret = GetResourceStringFromManaged(key); + + HELPER_METHOD_FRAME_END(); + + return OBJECTREFToObject(ret); +} +FCIMPLEND + +void FreeExceptionData(ExceptionData *pedata) +{ + CONTRACTL + { + NOTHROW; + GC_TRIGGERS; + SO_TOLERANT; + } + CONTRACTL_END; + + _ASSERTE(pedata != NULL); + + // <TODO>@NICE: At one point, we had the comment: + // (DM) Remove this when shutdown works better.</TODO> + // This test may no longer be necessary. Remove at own peril. + Thread *pThread = GetThread(); + if (!pThread) + return; + + if (pedata->bstrSource) + SysFreeString(pedata->bstrSource); + if (pedata->bstrDescription) + SysFreeString(pedata->bstrDescription); + if (pedata->bstrHelpFile) + SysFreeString(pedata->bstrHelpFile); +#ifdef FEATURE_COMINTEROP + if (pedata->bstrRestrictedError) + SysFreeString(pedata->bstrRestrictedError); + if (pedata->bstrReference) + SysFreeString(pedata->bstrReference); + if (pedata->bstrCapabilitySid) + SysFreeString(pedata->bstrCapabilitySid); + if (pedata->pRestrictedErrorInfo) + { + ULONG cbRef = SafeRelease(pedata->pRestrictedErrorInfo); + LogInteropRelease(pedata->pRestrictedErrorInfo, cbRef, "IRestrictedErrorInfo"); + } +#endif // FEATURE_COMINTEROP +} + +void GetExceptionForHR(HRESULT hr, IErrorInfo* pErrInfo, bool fUseCOMException, OBJECTREF* pProtectedThrowable, IRestrictedErrorInfo *pResErrorInfo, BOOL bHasLangRestrictedErrInfo) +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_COOPERATIVE; + PRECONDITION(IsProtectedByGCFrame(pProtectedThrowable)); + } + CONTRACTL_END; + + // Initialize + *pProtectedThrowable = NULL; + +#if defined(FEATURE_COMINTEROP) && !defined(CROSSGEN_COMPILE) + if (pErrInfo != NULL) + { + // If this represents a managed object... + // ...then get the managed exception object and also check if it is a __ComObject... + if (IsManagedObject(pErrInfo)) + { + GetObjectRefFromComIP(pProtectedThrowable, pErrInfo); + if ((*pProtectedThrowable) != NULL) + { + // ...if it is, then we'll just default to an exception based on the IErrorInfo. + if ((*pProtectedThrowable)->GetMethodTable()->IsComObjectType()) + { + (*pProtectedThrowable) = NULL; + } + else + { + // We have created an exception. Release the IErrorInfo + ULONG cbRef = SafeRelease(pErrInfo); + LogInteropRelease(pErrInfo, cbRef, "IErrorInfo release"); + return; + } + } + } + + // If we got here and we don't have an exception object, we have a native IErrorInfo or + // a managed __ComObject based IErrorInfo, so we'll just create an exception based on + // the native IErrorInfo. + if ((*pProtectedThrowable) == NULL) + { + EECOMException ex(hr, pErrInfo, fUseCOMException, pResErrorInfo, bHasLangRestrictedErrInfo COMMA_INDEBUG(FALSE)); + (*pProtectedThrowable) = ex.GetThrowable(); + } + } +#endif // defined(FEATURE_COMINTEROP) && !defined(CROSSGEN_COMPILE) + + // If we made it here and we don't have an exception object, we didn't have a valid IErrorInfo + // so we'll create an exception based solely on the hresult. + if ((*pProtectedThrowable) == NULL) + { + EEMessageException ex(hr, fUseCOMException); + (*pProtectedThrowable) = ex.GetThrowable(); + } +} + +void GetExceptionForHR(HRESULT hr, IErrorInfo* pErrInfo, OBJECTREF* pProtectedThrowable) +{ + WRAPPER_NO_CONTRACT; + + GetExceptionForHR(hr, pErrInfo, true, pProtectedThrowable); +} + +void GetExceptionForHR(HRESULT hr, OBJECTREF* pProtectedThrowable) +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; // because of IErrorInfo + MODE_ANY; + } + CONTRACTL_END; + + // Get an IErrorInfo if one is available. + IErrorInfo *pErrInfo = NULL; +#ifndef CROSSGEN_COMPILE + if (SafeGetErrorInfo(&pErrInfo) != S_OK) + pErrInfo = NULL; +#endif + + GetExceptionForHR(hr, pErrInfo, true, pProtectedThrowable); +} + + +// +// Maps a Win32 fault to a COM+ Exception enumeration code +// +DWORD MapWin32FaultToCOMPlusException(EXCEPTION_RECORD *pExceptionRecord) +{ + WRAPPER_NO_CONTRACT; + + switch (pExceptionRecord->ExceptionCode) + { + case STATUS_FLOAT_INEXACT_RESULT: + case STATUS_FLOAT_INVALID_OPERATION: + case STATUS_FLOAT_STACK_CHECK: + case STATUS_FLOAT_UNDERFLOW: + return (DWORD) kArithmeticException; + case STATUS_FLOAT_OVERFLOW: + case STATUS_INTEGER_OVERFLOW: + return (DWORD) kOverflowException; + + case STATUS_FLOAT_DIVIDE_BY_ZERO: + case STATUS_INTEGER_DIVIDE_BY_ZERO: + return (DWORD) kDivideByZeroException; + + case STATUS_FLOAT_DENORMAL_OPERAND: + return (DWORD) kFormatException; + + case STATUS_ACCESS_VIOLATION: + { + // We have a config key, InsecurelyTreatAVsAsNullReference, that ensures we always translate to + // NullReferenceException instead of doing the new AV translation logic. + if ((g_pConfig != NULL) && !g_pConfig->LegacyNullReferenceExceptionPolicy() && + !GetCompatibilityFlag(compatNullReferenceExceptionOnAV) ) + { +#if defined(FEATURE_HIJACK) && !defined(PLATFORM_UNIX) + // If we got the exception on a redirect function it means the original exception happened in managed code: + if (Thread::IsAddrOfRedirectFunc(pExceptionRecord->ExceptionAddress)) + return (DWORD) kNullReferenceException; + + if (pExceptionRecord->ExceptionAddress == (LPVOID)GetEEFuncEntryPoint(THROW_CONTROL_FOR_THREAD_FUNCTION)) + { + return (DWORD) kNullReferenceException; + } +#endif // FEATURE_HIJACK && !PLATFORM_UNIX + + // If the IP of the AV is not in managed code, then its an AccessViolationException. + if (!ExecutionManager::IsManagedCode((PCODE)pExceptionRecord->ExceptionAddress)) + { + return (DWORD) kAccessViolationException; + } + + // If the address accessed is above 64k (Windows) or page size (PAL), then its an AccessViolationException. + // Note: Win9x is a little different... it never gives you the proper address of the read or write that caused + // the fault. It always gives -1, so we can't use it as part of the decision... just give + // NullReferenceException instead. + if (pExceptionRecord->ExceptionInformation[1] >= NULL_AREA_SIZE) + { + return (DWORD) kAccessViolationException; + } + } + + return (DWORD) kNullReferenceException; + } + + case STATUS_ARRAY_BOUNDS_EXCEEDED: + return (DWORD) kIndexOutOfRangeException; + + case STATUS_NO_MEMORY: + return (DWORD) kOutOfMemoryException; + + case STATUS_STACK_OVERFLOW: + return (DWORD) kStackOverflowException; + +#ifdef ALIGN_ACCESS + case STATUS_DATATYPE_MISALIGNMENT: + return (DWORD) kDataMisalignedException; +#endif // ALIGN_ACCESS + + default: + return kSEHException; + } +} + +#ifdef _DEBUG +#ifndef WIN64EXCEPTIONS +// check if anyone has written to the stack above the handler which would wipe out the EH registration +void CheckStackBarrier(EXCEPTION_REGISTRATION_RECORD *exRecord) +{ + LIMITED_METHOD_CONTRACT; + + if (exRecord->Handler != (PEXCEPTION_ROUTINE)COMPlusFrameHandler) + return; + + DWORD *stackOverwriteBarrier = (DWORD *)((BYTE*)exRecord - offsetof(FrameHandlerExRecordWithBarrier, m_ExRecord)); + for (int i =0; i < STACK_OVERWRITE_BARRIER_SIZE; i++) { + if (*(stackOverwriteBarrier+i) != STACK_OVERWRITE_BARRIER_VALUE) { + // to debug this error, you must determine who erroneously overwrote the stack + _ASSERTE(!"Fatal error: the stack has been overwritten"); + } + } +} +#endif // WIN64EXCEPTIONS +#endif // _DEBUG + +//------------------------------------------------------------------------- +// A marker for JIT -> EE transition when we know we're in preemptive +// gc mode. As we leave the EE, we fix a few things: +// +// - the gc state must be set back to preemptive-operative +// - the COM+ frame chain must be rewound to what it was on entry +// - ExInfo()->m_pSearchBoundary must be adjusted +// if we popped the frame that is identified as begnning the next +// crawl. +//------------------------------------------------------------------------- + +void COMPlusCooperativeTransitionHandler(Frame* pFrame) +{ + CONTRACTL + { + NOTHROW; + GC_TRIGGERS; + MODE_ANY; + } + CONTRACTL_END; + + LOG((LF_EH, LL_INFO1000, "COMPlusCooprativeTransitionHandler unwinding\n")); + + { + Thread* pThread = GetThread(); + + // Restore us to cooperative gc mode. + GCX_COOP(); + + // Pop the frame chain. + UnwindFrameChain(pThread, pFrame); + CONSISTENCY_CHECK(pFrame == pThread->GetFrame()); + +#ifndef WIN64EXCEPTIONS + // An exception is being thrown through here. The COM+ exception + // info keeps a pointer to a frame that is used by the next + // COM+ Exception Handler as the starting point of its crawl. + // We may have popped this marker -- in which case, we need to + // update it to the current frame. + // + ThreadExceptionState* pExState = pThread->GetExceptionState(); + Frame* pSearchBoundary = NULL; + + if (pThread->IsExceptionInProgress()) + { + pSearchBoundary = pExState->m_currentExInfo.m_pSearchBoundary; + } + + if (pSearchBoundary && pSearchBoundary < pFrame) + { + LOG((LF_EH, LL_INFO1000, "\tpExInfo->m_pSearchBoundary = %08x\n", (void*)pFrame)); + pExState->m_currentExInfo.m_pSearchBoundary = pFrame; + } +#endif // WIN64EXCEPTIONS +} + + // Restore us to preemptive gc mode. + GCX_PREEMP_NO_DTOR(); +} + + + +void StackTraceInfo::Init() +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + MODE_ANY; + FORBID_FAULT; + SO_TOLERANT; + } + CONTRACTL_END; + + LOG((LF_EH, LL_INFO10000, "StackTraceInfo::Init (%p)\n", this)); + + m_pStackTrace = NULL; + m_cStackTrace = 0; + m_dFrameCount = 0; + m_cDynamicMethodItems = 0; + m_dCurrentDynamicIndex = 0; +} + +void StackTraceInfo::FreeStackTrace() +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + MODE_ANY; + FORBID_FAULT; + SO_TOLERANT; + } + CONTRACTL_END; + + if (m_pStackTrace) + { + delete [] m_pStackTrace; + m_pStackTrace = NULL; + m_cStackTrace = 0; + m_dFrameCount = 0; + m_cDynamicMethodItems = 0; + m_dCurrentDynamicIndex = 0; + } +} + +BOOL StackTraceInfo::IsEmpty() +{ + LIMITED_METHOD_CONTRACT; + + return 0 == m_dFrameCount; +} + +void StackTraceInfo::ClearStackTrace() +{ + LIMITED_METHOD_CONTRACT; + + LOG((LF_EH, LL_INFO1000, "StackTraceInfo::ClearStackTrace (%p)\n", this)); + m_dFrameCount = 0; +} + +// allocate stack trace info. As each function is found in the stack crawl, it will be added +// to this list. If the list is too small, it is reallocated. +void StackTraceInfo::AllocateStackTrace() +{ + STATIC_CONTRACT_NOTHROW; + STATIC_CONTRACT_GC_NOTRIGGER; + STATIC_CONTRACT_MODE_ANY; + STATIC_CONTRACT_FORBID_FAULT; + + LOG((LF_EH, LL_INFO1000, "StackTraceInfo::AllocateStackTrace (%p)\n", this)); + + if (!m_pStackTrace) + { +#ifdef _DEBUG + unsigned int allocSize = 2; // make small to exercise realloc +#else + unsigned int allocSize = 30; +#endif + + SCAN_IGNORE_FAULT; // A fault of new is okay here. The rest of the system is cool if we don't have enough + // memory to remember the stack as we run our first pass. + m_pStackTrace = new (nothrow) StackTraceElement[allocSize]; + + if (m_pStackTrace != NULL) + { + // Remember how much we allocated. + m_cStackTrace = allocSize; + m_cDynamicMethodItems = allocSize; + } + else + { + m_cStackTrace = 0; + m_cDynamicMethodItems = 0; + } + } +} + +// +// Returns true if it appended the element, false otherwise. +// +BOOL StackTraceInfo::AppendElement(BOOL bAllowAllocMem, UINT_PTR currentIP, UINT_PTR currentSP, MethodDesc* pFunc, CrawlFrame* pCf) +{ + CONTRACTL + { + GC_TRIGGERS; + NOTHROW; + } + CONTRACTL_END + + LOG((LF_EH, LL_INFO10000, "StackTraceInfo::AppendElement (%p), IP = %p, SP = %p, %s::%s\n", this, currentIP, currentSP, pFunc ? pFunc->m_pszDebugClassName : "", pFunc ? pFunc->m_pszDebugMethodName : "" )); + BOOL bRetVal = FALSE; + + if (pFunc != NULL && pFunc->IsILStub()) + return FALSE; + + // Save this function in the stack trace array, which we only build on the first pass. We'll try to expand the + // stack trace array if we don't have enough room. Note that we only try to expand if we're allowed to allocate + // memory (bAllowAllocMem). + if (bAllowAllocMem && (m_dFrameCount >= m_cStackTrace)) + { + StackTraceElement* pTempElement = new (nothrow) StackTraceElement[m_cStackTrace*2]; + + if (pTempElement != NULL) + { + memcpy(pTempElement, m_pStackTrace, m_cStackTrace * sizeof(StackTraceElement)); + delete [] m_pStackTrace; + m_pStackTrace = pTempElement; + m_cStackTrace *= 2; + } + } + + // Add the function to the stack trace array if there's room. + if (m_dFrameCount < m_cStackTrace) + { + StackTraceElement* pStackTraceElem; + + // If we get in here, we'd better have a stack trace array. + CONSISTENCY_CHECK(m_pStackTrace != NULL); + + pStackTraceElem = &(m_pStackTrace[m_dFrameCount]); + + pStackTraceElem->pFunc = pFunc; + + pStackTraceElem->ip = currentIP; + pStackTraceElem->sp = currentSP; + +#if defined(FEATURE_EXCEPTIONDISPATCHINFO) + // When we are building stack trace as we encounter managed frames during exception dispatch, + // then none of those frames represent a stack trace from a foreign exception (as they represent + // the current exception). Hence, set the corresponding flag to FALSE. + pStackTraceElem->fIsLastFrameFromForeignStackTrace = FALSE; +#endif // defined(FEATURE_EXCEPTIONDISPATCHINFO) + + // This is a workaround to fix the generation of stack traces from exception objects so that + // they point to the line that actually generated the exception instead of the line + // following. + if (!(pCf->HasFaulted() || pCf->IsIPadjusted()) && pStackTraceElem->ip != 0) + { + pStackTraceElem->ip -= 1; + } + + ++m_dFrameCount; + bRetVal = TRUE; + COUNTER_ONLY(GetPerfCounters().m_Excep.cThrowToCatchStackDepth++); + } + +#ifndef FEATURE_PAL // Watson is supported on Windows only + Thread *pThread = GetThread(); + _ASSERTE(pThread); + + if (pThread && (currentIP != 0)) + { + // Setup the watson bucketing details for the initial throw + // callback only if we dont already have them. + ThreadExceptionState *pExState = pThread->GetExceptionState(); + if (!pExState->GetFlags()->GotWatsonBucketDetails()) + { + // Adjust the IP if necessary. + UINT_PTR adjustedIp = currentIP; + // This is a workaround copied from above. + if (!(pCf->HasFaulted() || pCf->IsIPadjusted()) && adjustedIp != 0) + { + adjustedIp -= 1; + } + + // Setup the bucketing details for the initial throw + SetupInitialThrowBucketDetails(adjustedIp); + } + } +#endif // !FEATURE_PAL + + return bRetVal; +} + +void StackTraceInfo::GetLeafFrameInfo(StackTraceElement* pStackTraceElement) +{ + LIMITED_METHOD_CONTRACT; + + if (NULL == m_pStackTrace) + { + return; + } + _ASSERTE(NULL != pStackTraceElement); + + *pStackTraceElement = m_pStackTrace[0]; +} + + +void UnwindFrameChain(Thread* pThread, LPVOID pvLimitSP) +{ + CONTRACTL + { + NOTHROW; + DISABLED(GC_TRIGGERS); // some Frames' ExceptionUnwind methods trigger :( + MODE_ANY; + SO_TOLERANT; + } + CONTRACTL_END; + + // @todo - Remove this and add a hard SO probe as can't throw from here. + CONTRACT_VIOLATION(SOToleranceViolation); + + Frame* pFrame = pThread->m_pFrame; + if (pFrame < pvLimitSP) + { + GCX_COOP_THREAD_EXISTS(pThread); + + // + // call ExceptionUnwind with the Frame chain intact + // + pFrame = pThread->NotifyFrameChainOfExceptionUnwind(pFrame, pvLimitSP); + + // + // now pop the frames off by trimming the Frame chain + // + pThread->SetFrame(pFrame); + } +} + +BOOL IsExceptionOfType(RuntimeExceptionKind reKind, Exception *pException) +{ + STATIC_CONTRACT_NOTHROW; + STATIC_CONTRACT_GC_TRIGGERS; + STATIC_CONTRACT_MODE_ANY; + STATIC_CONTRACT_FORBID_FAULT; + + if (pException->IsType(reKind)) + return TRUE; + + if (pException->IsType(CLRException::GetType())) + { + // Since we're going to be holding onto the Throwable object we + // need to be in COOPERATIVE. + GCX_COOP(); + + OBJECTREF Throwable=((CLRException*)pException)->GetThrowable(); + + GCX_FORBID(); + if (IsExceptionOfType(reKind, &Throwable)) + return TRUE; + } + return FALSE; +} + +BOOL IsExceptionOfType(RuntimeExceptionKind reKind, OBJECTREF *pThrowable) +{ + STATIC_CONTRACT_NOTHROW; + STATIC_CONTRACT_GC_NOTRIGGER; + STATIC_CONTRACT_MODE_COOPERATIVE; + STATIC_CONTRACT_FORBID_FAULT; + + _ASSERTE(pThrowable != NULL); + + if (*pThrowable == NULL) + return FALSE; + + MethodTable *pThrowableMT = (*pThrowable)->GetTrueMethodTable(); + + // IsExceptionOfType is supported for mscorlib exception types only + _ASSERTE(reKind <= kLastExceptionInMscorlib); + return MscorlibBinder::IsException(pThrowableMT, reKind); +} + +BOOL IsAsyncThreadException(OBJECTREF *pThrowable) { + STATIC_CONTRACT_NOTHROW; + STATIC_CONTRACT_GC_NOTRIGGER; + STATIC_CONTRACT_MODE_COOPERATIVE; + STATIC_CONTRACT_FORBID_FAULT; + + if ( (GetThread() && GetThread()->IsRudeAbort() && GetThread()->IsRudeAbortInitiated()) + ||IsExceptionOfType(kThreadAbortException, pThrowable) + ||IsExceptionOfType(kThreadInterruptedException, pThrowable)) { + return TRUE; + } else { + return FALSE; + } +} + +BOOL IsUncatchable(OBJECTREF *pThrowable) +{ + CONTRACTL { + SO_TOLERANT; + NOTHROW; + GC_NOTRIGGER; + MODE_COOPERATIVE; + FORBID_FAULT; + } CONTRACTL_END; + + _ASSERTE(pThrowable != NULL); + + Thread *pThread = GetThread(); + + if (pThread) + { + if (pThread->IsAbortInitiated()) + return TRUE; + + if (OBJECTREFToObject(*pThrowable)->GetMethodTable() == g_pExecutionEngineExceptionClass) + return TRUE; + +#ifdef FEATURE_CORRUPTING_EXCEPTIONS + // Corrupting exceptions are also uncatchable + if (CEHelper::IsProcessCorruptedStateException(*pThrowable)) + { + return TRUE; + } +#endif //FEATURE_CORRUPTING_EXCEPTIONS + } + + return FALSE; +} + +BOOL IsStackOverflowException(Thread* pThread, EXCEPTION_RECORD* pExceptionRecord) +{ + if (IsSOExceptionCode(pExceptionRecord->ExceptionCode)) + { + return true; + } + + if (IsComPlusException(pExceptionRecord) && + pThread->IsLastThrownObjectStackOverflowException()) + { + return true; + } + + return false; +} + + +#ifdef _DEBUG +BOOL IsValidClause(EE_ILEXCEPTION_CLAUSE *EHClause) +{ + LIMITED_METHOD_CONTRACT; + +#if 0 + DWORD valid = COR_ILEXCEPTION_CLAUSE_FILTER | COR_ILEXCEPTION_CLAUSE_FINALLY | + COR_ILEXCEPTION_CLAUSE_FAULT | COR_ILEXCEPTION_CLAUSE_CACHED_CLASS; + + // <TODO>@NICE: enable this when VC stops generatng a bogus 0x8000.</TODO> + if (EHClause->Flags & ~valid) + return FALSE; +#endif + if (EHClause->TryStartPC > EHClause->TryEndPC) + return FALSE; + return TRUE; +} +#endif + + +#ifdef DEBUGGING_SUPPORTED +LONG NotifyDebuggerLastChance(Thread *pThread, + EXCEPTION_POINTERS *pExceptionInfo, + BOOL jitAttachRequested) +{ + STATIC_CONTRACT_NOTHROW; + STATIC_CONTRACT_GC_TRIGGERS; + STATIC_CONTRACT_MODE_ANY; + + LONG retval = EXCEPTION_CONTINUE_SEARCH; + + // Debugger does func-evals inside this call, which may take nested exceptions. We need a nested exception + // handler to allow this. + INSTALL_NESTED_EXCEPTION_HANDLER(pThread->GetFrame()); + + EXCEPTION_POINTERS dummy; + dummy.ExceptionRecord = NULL; + dummy.ContextRecord = NULL; + + if (NULL == pExceptionInfo) + { + pExceptionInfo = &dummy; + } + else if (NULL != pExceptionInfo->ExceptionRecord && NULL == pExceptionInfo->ContextRecord) + { + // In a soft stack overflow, we have an exception record but not a context record. + // Debugger::LastChanceManagedException requires that both ExceptionRecord and + // ContextRecord be valid or both be NULL. + pExceptionInfo = &dummy; + } + + if (g_pDebugInterface && g_pDebugInterface->LastChanceManagedException(pExceptionInfo, + pThread, + jitAttachRequested) == ExceptionContinueExecution) + { + retval = EXCEPTION_CONTINUE_EXECUTION; + } + + UNINSTALL_NESTED_EXCEPTION_HANDLER(); + + EX_TRY + { + // if the debugger wants to intercept the unhandled exception then we immediately unwind without returning + // If there is a problem with this function unwinding here it could be separated out however + // we need to be very careful. Previously we had the opposite problem in that we notified the debugger + // of an unhandled exception and then either: + // a) never gave the debugger a chance to intercept later, or + // b) code changed more process state unaware that the debugger would be handling the exception + if ((pThread->IsExceptionInProgress()) && pThread->GetExceptionState()->GetFlags()->DebuggerInterceptInfo()) + { + // The debugger wants to intercept this exception. It may return in a failure case, in which case we want + // to continue thru this path. + ClrDebuggerDoUnwindAndIntercept(X86_FIRST_ARG(EXCEPTION_CHAIN_END) pExceptionInfo->ExceptionRecord); + } + } + EX_CATCH // if we fail to intercept just continue as is + { + } + EX_END_CATCH(SwallowAllExceptions); + + return retval; +} + +#ifndef FEATURE_PAL +//---------------------------------------------------------------------------- +// +// DoReportFault - wrapper for ReportFault in FaultRep.dll, which also handles +// debugger launch synchronization if the user chooses to launch +// a debugger +// +// Arguments: +// pExceptionInfo - pointer to exception info +// +// Return Value: +// The returned EFaultRepRetVal value from ReportFault +// +// Note: +// +//---------------------------------------------------------------------------- +EFaultRepRetVal DoReportFault(EXCEPTION_POINTERS * pExceptionInfo) +{ + LIMITED_METHOD_CONTRACT; + + HINSTANCE hmod = WszLoadLibrary(W("FaultRep.dll")); + EFaultRepRetVal r = frrvErr; + if (hmod) + { + pfn_REPORTFAULT pfnReportFault = (pfn_REPORTFAULT)GetProcAddress(hmod, "ReportFault"); + if (pfnReportFault) + { + r = pfnReportFault(pExceptionInfo, 0); + } + FreeLibrary(hmod); + } + + if (r == frrvLaunchDebugger) + { + // Wait until the pending managed debugger attach is completed + if (g_pDebugInterface != NULL) + { + g_pDebugInterface->WaitForDebuggerAttach(); + } + } + return r; +} + +//---------------------------------------------------------------------------- +// +// DisableOSWatson - Set error mode to disable OS Watson +// +// Arguments: +// None +// +// Return Value: +// None +// +// Note: SetErrorMode changes the process wide error mode, which can be overridden by other threads +// in a race. The solution is to use new Win7 per thread error mode APIs, which take precedence +// over process wide error mode. However, we shall not use per thread error mode if the runtime +// is being hosted because with per thread error mode being used the OS will ignore the process +// wide error mode set by the host. +// +//---------------------------------------------------------------------------- +void DisableOSWatson(void) +{ + LIMITED_METHOD_CONTRACT; + + // When a debugger is attached (or will be attaching), we need to disable the OS GPF dialog. + // If we don't, an unhandled managed exception will launch the OS watson dialog even when + // the debugger is attached. + const UINT lastErrorMode = SetErrorMode(0); + SetErrorMode(lastErrorMode | SEM_NOGPFAULTERRORBOX); + LOG((LF_EH, LL_INFO100, "DisableOSWatson: SetErrorMode = 0x%x\n", lastErrorMode | SEM_NOGPFAULTERRORBOX)); + +#ifndef FEATURE_CORECLR + // CoreCLR is always hosted and so this condition is always false + if (RunningOnWin7() && !CLRHosted()) + { + typedef DWORD (WINAPI * GetThreadErrorModeFnPtr)(void); + typedef BOOL (WINAPI * SetThreadErrorModeFnPtr)(DWORD, LPDWORD); + GetThreadErrorModeFnPtr pFnGetThreadErrorMode; + SetThreadErrorModeFnPtr pFnSetThreadErrorMode; + + HINSTANCE hKernel32 = WszGetModuleHandle(WINDOWS_KERNEL32_DLLNAME_W); + if (hKernel32 != NULL) + { + pFnGetThreadErrorMode = (GetThreadErrorModeFnPtr)GetProcAddress(hKernel32, "GetThreadErrorMode"); + pFnSetThreadErrorMode = (SetThreadErrorModeFnPtr)GetProcAddress(hKernel32, "SetThreadErrorMode"); + + // GetThreadErrorMode and SetThreadErrorMode should be available on Win7. + _ASSERTE((pFnGetThreadErrorMode != NULL) && (pFnSetThreadErrorMode != NULL)); + if ((pFnGetThreadErrorMode != NULL) && (pFnSetThreadErrorMode != NULL)) + { + DWORD dwOldMode = (*pFnGetThreadErrorMode)(); + (*pFnSetThreadErrorMode)(dwOldMode | SEM_NOGPFAULTERRORBOX, &dwOldMode); + LOG((LF_EH, LL_INFO100, "DisableOSWatson: SetThreadErrorMode = 0x%x\n", dwOldMode | SEM_NOGPFAULTERRORBOX)); + } + } + } +#endif // FEATURE_CORECLR +} + + +//---------------------------------------------------------------------------- +// +// RaiseFailFastExceptionOnWin7 - invoke RaiseFailFastException on Win7 +// +// Arguments: +// pExceptionRecord - pointer to exception record +// pContext - pointer to exception context +// +// Return Value: +// None +// +// Note: +// RaiseFailFastException will not return unless a debugger is attached +// and the user chooses to keep going. +// +//---------------------------------------------------------------------------- +void RaiseFailFastExceptionOnWin7(PEXCEPTION_RECORD pExceptionRecord, PCONTEXT pContext) +{ + LIMITED_METHOD_CONTRACT; + _ASSERTE(RunningOnWin7()); + +#ifndef FEATURE_CORESYSTEM + typedef void (WINAPI * RaiseFailFastExceptionFnPtr)(PEXCEPTION_RECORD, PCONTEXT, DWORD); + RaiseFailFastExceptionFnPtr RaiseFailFastException; + + HINSTANCE hKernel32 = WszGetModuleHandle(WINDOWS_KERNEL32_DLLNAME_W); + if (hKernel32 == NULL) + return; + + RaiseFailFastException = (RaiseFailFastExceptionFnPtr)GetProcAddress(hKernel32, "RaiseFailFastException"); + if (RaiseFailFastException == NULL) + return; +#endif + + // enable preemptive mode before call into OS to allow runtime suspend to finish + GCX_PREEMP(); + + STRESS_LOG0(LF_CORDB,LL_INFO10, "D::RFFE: About to call RaiseFailFastException\n"); + RaiseFailFastException(pExceptionRecord, pContext, 0); + STRESS_LOG0(LF_CORDB,LL_INFO10, "D::RFFE: Return from RaiseFailFastException\n"); +} +#endif // !FEATURE_PAL + +//------------------------------------------------------------------------------ +// This function is called on an unhandled exception, via the runtime's +// Unhandled Exception Filter (Hence the name, "last chance", because this +// is the last chance to see the exception. When running under a native +// debugger, that won't generally happen, because the OS notifies the debugger +// instead of calling the application's registered UEF; the debugger will +// show the exception as second chance.) +// The function is also called sometimes for the side effects, which are +// to possibly invoke Watson and to possibly notify the managed debugger. +// If running in a debugger already, either native or managed, we shouldn't +// invoke Watson. +// If not running under a managed debugger, we shouldn't try to send a debugger +// notification. +//------------------------------------------------------------------------------ +LONG WatsonLastChance( // EXCEPTION_CONTINUE_SEARCH, _CONTINUE_EXECUTION + Thread *pThread, // Thread object. + EXCEPTION_POINTERS *pExceptionInfo,// Information about reported exception. + TypeOfReportedError tore) // Just what kind of error is reported? +{ + STATIC_CONTRACT_NOTHROW; + STATIC_CONTRACT_GC_TRIGGERS; + STATIC_CONTRACT_MODE_ANY; + + // If allocation fails, we may not produce watson dump. But this is not fatal. + CONTRACT_VIOLATION(AllViolation); + LOG((LF_EH, LL_INFO10, "D::WLC: Enter WatsonLastChance\n")); + +#ifndef FEATURE_PAL + static DWORD fDisableWatson = -1; + if (fDisableWatson == -1) + { + fDisableWatson = CLRConfig::GetConfigValue(CLRConfig::INTERNAL_DisableWatsonForManagedExceptions); + } + + if (fDisableWatson && (tore.GetType() == TypeOfReportedError::UnhandledException)) + { + DisableOSWatson(); + LOG((LF_EH, LL_INFO10, "D::WLC: OS Watson is disabled for an managed unhandled exception\n")); + return EXCEPTION_CONTINUE_SEARCH; + } +#endif // !FEATURE_PAL + + // We don't want to launch Watson if a debugger is already attached to + // the process. + BOOL shouldNotifyDebugger = FALSE; // Assume we won't debug. + + // VS debugger team requested the Whidbey experience, which is no Watson when the debugger thread detects + // that the debugger process is abruptly terminated, and triggers a failfast error. In this particular + // scenario CORDebuggerAttached() will be TRUE, but IsDebuggerPresent() will be FALSE because from OS + // perspective the native debugger has been detached from the debuggee, but CLR has not yet marked the + // managed debugger as detached. Therefore, CORDebuggerAttached() is checked, so Watson will not pop up + // when a debugger is abruptly terminated. It also prevents a debugger from being launched on a helper + // thread. + BOOL alreadyDebugging = CORDebuggerAttached() || IsDebuggerPresent(); + + BOOL jitAttachRequested = !alreadyDebugging; // Launch debugger if not already running. + +#ifdef _DEBUG + // If BreakOnUnCaughtException is set, we may be using a native debugger to debug this stuff + BOOL BreakOnUnCaughtException = CLRConfig::GetConfigValue(CLRConfig::INTERNAL_BreakOnUncaughtException); + if(!alreadyDebugging || (!CORDebuggerAttached() && BreakOnUnCaughtException) ) +#else + if (!alreadyDebugging) +#endif + { + LOG((LF_EH, LL_INFO10, "WatsonLastChance: Debugger not attached at sp %p ...\n", GetCurrentSP())); + +#ifndef FEATURE_PAL + BOOL bRunDoFaultReport = TRUE; + FaultReportResult result = FaultReportResultQuit; + + if (RunningOnWin7()) + { + BOOL fSOException = FALSE; + + if ((pExceptionInfo != NULL) && + (pExceptionInfo->ExceptionRecord != NULL) && + (pExceptionInfo->ExceptionRecord->ExceptionCode == STATUS_STACK_OVERFLOW)) + { + fSOException = TRUE; + } + + if (g_pDebugInterface) + { + // we are about to let the OS trigger jit attach, however we need to synchronize with our + // own jit attach that we might be doing on another thread + // PreJitAttach races this thread against any others which might be attaching and if some other + // thread is doing it then we wait for its attach to complete first + g_pDebugInterface->PreJitAttach(TRUE, FALSE, FALSE); + } + + // Let unhandled excpetions except stack overflow go to the OS + if (tore.IsUnhandledException() && !fSOException) + { + return EXCEPTION_CONTINUE_SEARCH; + } + else if (tore.IsUserBreakpoint()) + { + DoReportFault(pExceptionInfo); + } + else + { + BOOL fWatsonAlreadyLaunched = FALSE; + if (FastInterlockCompareExchange(&g_watsonAlreadyLaunched, 1, 0) != 0) + { + fWatsonAlreadyLaunched = TRUE; + } + + // Logic to avoid double prompt if more than one threads calling into WatsonLastChance + if (!fWatsonAlreadyLaunched) + { + // EEPolicy::HandleFatalStackOverflow pushes a FaultingExceptionFrame on the stack after SO + // exception. Our hijack code runs in the exception context, and overwrites the stack space + // after SO excpetion, so we need to pop up this frame before invoking RaiseFailFast. + // This cumbersome code should be removed once SO synchronization is moved to be completely + // out-of-process. + if (fSOException && pThread && pThread->GetFrame() != FRAME_TOP) + { + GCX_COOP(); // Must be cooperative to modify frame chain. + pThread->GetFrame()->Pop(pThread); + } + + LOG((LF_EH, LL_INFO10, "D::WLC: Call RaiseFailFastExceptionOnWin7\n")); + RaiseFailFastExceptionOnWin7(pExceptionInfo == NULL ? NULL : pExceptionInfo->ExceptionRecord, + pExceptionInfo == NULL ? NULL : pExceptionInfo->ContextRecord); + STRESS_LOG0(LF_CORDB,LL_INFO10, "D::WLC: Return from RaiseFailFastExceptionOnWin7\n"); + } + } + + if (g_pDebugInterface) + { + // if execution resumed here then we may or may not be attached + // either way we need to end the attach process and unblock any other + // threads which were waiting for the attach here to complete + g_pDebugInterface->PostJitAttach(); + } + + + if (IsDebuggerPresent()) + { + result = FaultReportResultDebug; + jitAttachRequested = FALSE; + } + } + else + { + // If we've got a fatal error but Watson isn't enabled, then fall back to old-style non-managed-aware + // error reporting using faultrep to try and ensure we get an error report about this fatal error. + if (!IsWatsonEnabled() && tore.IsFatalError() && (pExceptionInfo != NULL)) + { + EFaultRepRetVal r = DoReportFault(pExceptionInfo); + if (r != frrvErr && r != frrvErrNoDW && r != frrvErrTimeout) + { + // Once native Watson is sucessfully launched, we should not try to launch + // our fake Watson dailog box. + bRunDoFaultReport = FALSE; + } + } + + if (bRunDoFaultReport) + { + // http://devdiv/sites/docs/NetFX4/CLR/Specs/Developer%20Services/Error%20Reporting/WER%20SxS%20DCR.doc + // + // Watson SxS support for Desktop CLR + // + // For an unhandled exception thrown from native code, the first runtime that encounters the + // unhandled native exception will report Watson if it is allowed by Watson SxS manager to do + // Watson. If more than one runtimes attempt to report Watson concurrently, only one runtims + // will be bestowed to report Watson. The result is that at most one Watson report will be + // submitted for a process. + // + // To coordinate Watson reporting among runtimes in a process, Watson SxS manager, which is part + // of the shim, will provide a new set of APIs, and keeps a status of whether a Watson report + // has been submitted for a process. + // + // Each runtime registers an exception claiming callack with Watson SxS manager at startup. + // Watson SxS manager provide an exception claiming API, which iterators through registerd + // exception claiming callbacks to determine if an exception is thrown by one of registered + // runtimes. + // + // Before a runtime goes to process Watson for an unhandled exception, it first asks Waston SxS + // manager if a Watson report has already been submitted for the current process. If so, it + // will not try to do Watson. If not, it checks if the unhandled exception is thrown by itself. + // If true, it will report Watson only when Watson SxS manager allows it to do Watson. + // + // If the unhandled exception is not thrown by itself, it will invoke Watson SxS manager's exception + // claiming API to determine if the unhandled exception was thrown by another runtime which is + // responsible for reporting Watson. If true, it will not try to do Watson. If none of runtimes + // in the process claims the ownership of the unhandled exception, it will report Watson only when + // Watson SxS manager allows it to do Watson. + result = DoFaultReport(pExceptionInfo, tore); + + // Set the event to indicate that Watson processing is completed. Other threads can continue. +#if defined(FEATURE_UEF_CHAINMANAGER) + if (g_pUEFManager) + { + g_pUEFManager->GetWastonSxSManagerInstance()->SignalWatsonSxSCompletionEvent(); + } +#else + UnsafeSetEvent(g_hWatsonCompletionEvent); +#endif // FEATURE_UEF_CHAINMANAGER + } + } + + switch(result) + { + case FaultReportResultAbort: + { + // We couldn't launch watson properly. First fall-back to OS error-reporting + // so that we don't break native apps. + EFaultRepRetVal r = frrvErr; + + if (pExceptionInfo != NULL) + { + GCX_PREEMP(); + + if (pExceptionInfo->ExceptionRecord->ExceptionCode != STATUS_STACK_OVERFLOW) + { + r = DoReportFault(pExceptionInfo); + } + else + { + // Since the StackOverflow handler also calls us, we must keep our stack budget + // to a minimum. Thus, we will launch a thread to do the actual work. + FaultReportInfo fri; + fri.m_fDoReportFault = TRUE; + fri.m_pExceptionInfo = pExceptionInfo; + // DoFaultCreateThreadReportCallback will overwrite this - if it doesn't, we'll assume it failed. + fri.m_faultRepRetValResult = frrvErr; + + // Stack overflow case - we don't have enough stack on our own thread so let the debugger + // helper thread do the work. + if (!g_pDebugInterface || FAILED(g_pDebugInterface->RequestFavor(DoFaultReportDoFavorCallback, &fri))) + { + // If we can't initialize the debugger helper thread or we are running on the debugger helper + // thread, give it up. We don't have enough stack space. + } + + r = fri.m_faultRepRetValResult; + } + } + + if ((r == frrvErr) || (r == frrvErrNoDW) || (r == frrvErrTimeout)) + { + // If we don't have an exception record, or otherwise can't use OS error + // reporting then offer the old "press OK to terminate, cancel to debug" + // dialog as a futher fallback. + if (g_pDebugInterface && g_pDebugInterface->FallbackJITAttachPrompt()) + { + // User requested to launch the debugger + shouldNotifyDebugger = TRUE; + } + } + else if (r == frrvLaunchDebugger) + { + // User requested to launch the debugger + shouldNotifyDebugger = TRUE; + } + break; + } + case FaultReportResultQuit: + // No debugger, just exit normally + break; + case FaultReportResultDebug: + // JIT attach a debugger here. + shouldNotifyDebugger = TRUE; + break; + default: + UNREACHABLE_MSG("Unknown FaultReportResult"); + break; + } + } + // When the debugger thread detects that the debugger process is abruptly terminated, and triggers + // a failfast error, CORDebuggerAttached() will be TRUE, but IsDebuggerPresent() will be FALSE. + // If IsDebuggerPresent() is FALSE, do not try to notify the deubgger. + else if (CORDebuggerAttached() && IsDebuggerPresent()) +#else + } + else if (CORDebuggerAttached()) +#endif // !FEATURE_PAL + { + // Already debugging with a managed debugger. Should let that debugger know. + LOG((LF_EH, LL_INFO100, "WatsonLastChance: Managed debugger already attached at sp %p ...\n", GetCurrentSP())); + + // The managed EH subsystem ignores native breakpoints and single step exceptions. These exceptions are + // not considered managed, and the managed debugger should not be notified. Moreover, we won't have + // created a managed exception object at this point. + if (tore.GetType() != TypeOfReportedError::NativeBreakpoint) + { + shouldNotifyDebugger = TRUE; + } + } + +#ifndef FEATURE_PAL + DisableOSWatson(); +#endif // !FEATURE_PAL + + if (!shouldNotifyDebugger) + { + LOG((LF_EH, LL_INFO100, "WatsonLastChance: should not notify debugger. Returning EXCEPTION_CONTINUE_SEARCH\n")); + return EXCEPTION_CONTINUE_SEARCH; + } + + // If no debugger interface, we can't notify the debugger. + if (g_pDebugInterface == NULL) + { + LOG((LF_EH, LL_INFO100, "WatsonLastChance: No debugger interface. Returning EXCEPTION_CONTINUE_SEARCH\n")); + return EXCEPTION_CONTINUE_SEARCH; + } + + LOG((LF_EH, LL_INFO10, "WatsonLastChance: Notifying debugger\n")); + + switch (tore.GetType()) + { + case TypeOfReportedError::FatalError: + #ifdef MDA_SUPPORTED + { + MdaFatalExecutionEngineError * pMDA = MDA_GET_ASSISTANT_EX(FatalExecutionEngineError); + + if ((pMDA != NULL) && (pExceptionInfo != NULL) && (pExceptionInfo->ExceptionRecord != NULL)) + { + TADDR addr = (TADDR) pExceptionInfo->ExceptionRecord->ExceptionAddress; + HRESULT hrError = pExceptionInfo->ExceptionRecord->ExceptionCode; + pMDA->ReportFEEE(addr, hrError); + } + } + #endif // MDA_SUPPORTED + + if (pThread != NULL) + { + NotifyDebuggerLastChance(pThread, pExceptionInfo, jitAttachRequested); + + // If the registed debugger is not a managed debugger, we need to stop the debugger here. + if (!CORDebuggerAttached() && IsDebuggerPresent()) + { + DebugBreak(); + } + } + else + { + g_pDebugInterface->LaunchDebuggerForUser(GetThread(), pExceptionInfo, FALSE, FALSE); + } + + return EXCEPTION_CONTINUE_SEARCH; + + case TypeOfReportedError::UnhandledException: + case TypeOfReportedError::NativeBreakpoint: + // Notify the debugger only if this is a managed thread. + if (pThread != NULL) + { + return NotifyDebuggerLastChance(pThread, pExceptionInfo, jitAttachRequested); + } + else + { + g_pDebugInterface->JitAttach(pThread, pExceptionInfo, FALSE, FALSE); + + // return EXCEPTION_CONTINUE_SEARCH, so OS's UEF will reraise the unhandled exception for debuggers + return EXCEPTION_CONTINUE_SEARCH; + } + + case TypeOfReportedError::UserBreakpoint: + g_pDebugInterface->LaunchDebuggerForUser(pThread, pExceptionInfo, TRUE, FALSE); + + return EXCEPTION_CONTINUE_EXECUTION; + + case TypeOfReportedError::NativeThreadUnhandledException: + g_pDebugInterface->JitAttach(pThread, pExceptionInfo, FALSE, FALSE); + + // return EXCEPTION_CONTINUE_SEARCH, so OS's UEF will reraise the unhandled exception for debuggers + return EXCEPTION_CONTINUE_SEARCH; + + default: + _ASSERTE(!"Unknown case in WatsonLastChance"); + return EXCEPTION_CONTINUE_SEARCH; + } + + UNREACHABLE(); +} // LONG WatsonLastChance() + +//--------------------------------------------------------------------------------------- +// +// This is just a simple helper to do some basic checking to see if an exception is intercepted. +// It checks that we are on a managed thread and that an exception is indeed in progress. +// +// Return Value: +// true iff we are on a managed thread and an exception is in flight +// + +bool CheckThreadExceptionStateForInterception() +{ + LIMITED_METHOD_CONTRACT; + + Thread* pThread = GetThread(); + + if (pThread == NULL) + { + return false; + } + + if (!pThread->IsExceptionInProgress()) + { + return false; + } + + return true; +} +#endif + +//=========================================================================================== +// +// UNHANDLED EXCEPTION HANDLING +// + +static Volatile<BOOL> fReady = 0; +static SpinLock initLock; + +void DECLSPEC_NORETURN RaiseDeadLockException() +{ + STATIC_CONTRACT_THROWS; + STATIC_CONTRACT_SO_TOLERANT; + +// Disable the "initialization of static local vars is no thread safe" error +#ifdef _MSC_VER +#pragma warning(disable: 4640) +#endif + CHECK_LOCAL_STATIC_VAR(static SString s); +#ifdef _MSC_VER +#pragma warning(default : 4640) +#endif + if (!fReady) + { + WCHAR name[256]; + HRESULT hr = S_OK; + { + FAULT_NOT_FATAL(); + GCX_COOP(); + hr = UtilLoadStringRC(IDS_EE_THREAD_DEADLOCK_VICTIM, name, sizeof(name)/sizeof(WCHAR), 1); + } + initLock.Init(LOCK_TYPE_DEFAULT); + SpinLockHolder __spinLockHolder(&initLock); + if (!fReady) + { + if (SUCCEEDED(hr)) + { + s.Set(name); + fReady = 1; + } + else + { + ThrowHR(hr); + } + } + } + + ThrowHR(HOST_E_DEADLOCK, s); +} + +//****************************************************************************** +// +// ExceptionIsAlwaysSwallowed +// +// Determine whether an exception is of a type that it should always +// be swallowed, even when exceptions otherwise are left to go unhandled. +// (For Whidbey, ThreadAbort, RudeThreadAbort, or AppDomainUnload exception) +// +// Parameters: +// pExceptionInfo EXCEPTION_POINTERS for current exception +// +// Returns: +// true If the exception is of a type that is always swallowed. +// +bool ExceptionIsAlwaysSwallowed(EXCEPTION_POINTERS *pExceptionInfo) +{ + bool isSwallowed = false; + + // The exception code must be ours, if it is one of our Exceptions. + if (IsComPlusException(pExceptionInfo->ExceptionRecord)) + { + // Our exception code. Get the current exception from the thread. + Thread *pThread = GetThread(); + if (pThread) + { + OBJECTREF throwable; + + GCX_COOP(); + if ((throwable = pThread->GetThrowable()) == NULL) + { + throwable = pThread->LastThrownObject(); + } + //@todo: could throwable be NULL here? + isSwallowed = IsExceptionOfType(kThreadAbortException, &throwable) || + IsExceptionOfType(kAppDomainUnloadedException, &throwable); + } + } + + return isSwallowed; +} // BOOL ExceptionIsAlwaysSwallowed() + +// +// UserBreakpointFilter is used to ensure that we get a popup on user breakpoints (DebugBreak(), hard-coded int 3, +// etc.) as soon as possible. +// +LONG UserBreakpointFilter(EXCEPTION_POINTERS* pEP) +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + MODE_ANY; + FORBID_FAULT; + SO_TOLERANT; + } + CONTRACTL_END; + +#ifdef DEBUGGING_SUPPORTED + // Invoke the unhandled exception filter, bypassing any further first pass exception processing and treating + // user breakpoints as if they're unhandled exceptions right away. + // + // @todo: The InternalUnhandledExceptionFilter can trigger. + CONTRACT_VIOLATION(GCViolation | ThrowsViolation | ModeViolation | FaultViolation | FaultNotFatal); + +#ifdef FEATURE_PAL + int result = COMUnhandledExceptionFilter(pEP); +#else + int result = UnhandledExceptionFilter(pEP); +#endif + + if (result == EXCEPTION_CONTINUE_SEARCH) + { + // A debugger got attached. Instead of allowing the exception to continue up, and hope for the + // second-chance, we cause it to happen again. The debugger snags all int3's on first-chance. NOTE: the + // InternalUnhandledExceptionFilter allowed GC's to occur, but it may be the case that some managed frames + // may have been unprotected. Therefore, you may have GC holes if you attempt to continue execution from + // here. + return EXCEPTION_CONTINUE_EXECUTION; + } +#endif // DEBUGGING_SUPPORTED + + if(ETW_EVENT_ENABLED(MICROSOFT_WINDOWS_DOTNETRUNTIME_PRIVATE_PROVIDER_Context, FailFast)) + { + // Fire an ETW FailFast event + FireEtwFailFast(W("StatusBreakpoint"), + (const PVOID)((pEP && pEP->ContextRecord) ? GetIP(pEP->ContextRecord) : 0), + ((pEP && pEP->ExceptionRecord) ? pEP->ExceptionRecord->ExceptionCode : 0), + STATUS_BREAKPOINT, + GetClrInstanceId()); + } + + // Otherwise, we termintate the process. + TerminateProcess(GetCurrentProcess(), STATUS_BREAKPOINT); + + // Shouldn't get here ... + return EXCEPTION_CONTINUE_EXECUTION; +} // LONG UserBreakpointFilter() + +//****************************************************************************** +// +// DefaultCatchFilter +// +// The old default except filter (v1.0/v1.1) . For user breakpoints, call out to UserBreakpointFilter() +// but otherwise return EXCEPTION_EXECUTE_HANDLER, to swallow the exception. +// +// Parameters: +// pExceptionInfo EXCEPTION_POINTERS for current exception +// pv A constant as an INT_PTR. Must be COMPLUS_EXCEPTION_EXECUTE_HANDLER. +// +// Returns: +// EXCEPTION_EXECUTE_HANDLER Generally returns this to swallow the exception. +// +// IMPORTANT!! READ ME!! +// +// This filter is very similar to DefaultCatchNoSwallowFilter, except when unhandled +// exception policy/config dictate swallowing the exception. +// If you make any changes to this function, look to see if the other one also needs +// the same change. +// +LONG DefaultCatchFilter(EXCEPTION_POINTERS *ep, PVOID pv) +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + MODE_ANY; + FORBID_FAULT; + SO_TOLERANT; + } + CONTRACTL_END; + + // + // @TODO: this seems like a strong candidate for elimination due to duplication with + // our vectored exception handler. + // + + DefaultCatchFilterParam *pParam; + pParam = (DefaultCatchFilterParam *) pv; + + // the only valid parameter for DefaultCatchFilter so far + _ASSERTE(pParam->pv == COMPLUS_EXCEPTION_EXECUTE_HANDLER); + + PEXCEPTION_RECORD er = ep->ExceptionRecord; + DWORD code = er->ExceptionCode; + + if (code == STATUS_SINGLE_STEP || code == STATUS_BREAKPOINT) + { + return UserBreakpointFilter(ep); + } + + // return EXCEPTION_EXECUTE_HANDLER to swallow the exception. + return EXCEPTION_EXECUTE_HANDLER; +} // LONG DefaultCatchFilter() + + +//****************************************************************************** +// +// DefaultCatchNoSwallowFilter +// +// The new default except filter (v2.0). For user breakpoints, call out to UserBreakpointFilter(). +// Otherwise consults host policy and config file to return EXECUTE_HANDLER / CONTINUE_SEARCH. +// +// Parameters: +// pExceptionInfo EXCEPTION_POINTERS for current exception +// pv A constant as an INT_PTR. Must be COMPLUS_EXCEPTION_EXECUTE_HANDLER. +// +// Returns: +// EXCEPTION_CONTINUE_SEARCH Generally returns this to let the exception go unhandled. +// EXCEPTION_EXECUTE_HANDLER May return this to swallow the exception. +// +// IMPORTANT!! READ ME!! +// +// This filter is very similar to DefaultCatchFilter, except when unhandled +// exception policy/config dictate swallowing the exception. +// If you make any changes to this function, look to see if the other one also needs +// the same change. +// +LONG DefaultCatchNoSwallowFilter(EXCEPTION_POINTERS *ep, PVOID pv) +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_ANY; + } + CONTRACTL_END; + + DefaultCatchFilterParam *pParam; pParam = (DefaultCatchFilterParam *) pv; + + // the only valid parameter for DefaultCatchFilter so far + _ASSERTE(pParam->pv == COMPLUS_EXCEPTION_EXECUTE_HANDLER); + + PEXCEPTION_RECORD er = ep->ExceptionRecord; + DWORD code = er->ExceptionCode; + + if (code == STATUS_SINGLE_STEP || code == STATUS_BREAKPOINT) + { + return UserBreakpointFilter(ep); + } + + // If host policy or config file says "swallow"... + if (SwallowUnhandledExceptions()) + { // ...return EXCEPTION_EXECUTE_HANDLER to swallow the exception. + return EXCEPTION_EXECUTE_HANDLER; + } + + // If the exception is of a type that is always swallowed (ThreadAbort, AppDomainUnload)... + if (ExceptionIsAlwaysSwallowed(ep)) + { // ...return EXCEPTION_EXECUTE_HANDLER to swallow the exception. + return EXCEPTION_EXECUTE_HANDLER; + } + + // Otherwise, continue search. i.e. let the exception go unhandled (at least for now). + return EXCEPTION_CONTINUE_SEARCH; +} // LONG DefaultCatchNoSwallowFilter() + +#if defined(FEATURE_CORECLR) +// Note: This is used only for CoreCLR on WLC. +// +// We keep a pointer to the previous unhandled exception filter. After we install, we use +// this to call the previous guy. When we un-install, we put them back. Putting them back +// is a bug -- we have no guarantee that the DLL unload order matches the DLL load order -- we +// may in fact be putting back a pointer to a DLL that has been unloaded. +// + +// initialize to -1 because NULL won't detect difference between us not having installed our handler +// yet and having installed it but the original handler was NULL. +static LPTOP_LEVEL_EXCEPTION_FILTER g_pOriginalUnhandledExceptionFilter = (LPTOP_LEVEL_EXCEPTION_FILTER)-1; +#define FILTER_NOT_INSTALLED (LPTOP_LEVEL_EXCEPTION_FILTER) -1 +#endif // defined(FEATURE_CORECLR) + + +BOOL InstallUnhandledExceptionFilter() { + STATIC_CONTRACT_NOTHROW; + STATIC_CONTRACT_GC_NOTRIGGER; + STATIC_CONTRACT_MODE_ANY; + STATIC_CONTRACT_FORBID_FAULT; + +#ifndef FEATURE_PAL +#ifdef FEATURE_UEF_CHAINMANAGER + if (g_pUEFManager == NULL) { + + /*CONTRACTL { + NOTHROW; + GC_NOTRIGGER; + MODE_PREEMPTIVE; + } CONTRACTL_END;*/ + + static HMODULE hMSCorEE; + + if (!hMSCorEE) { + hMSCorEE = WszGetModuleHandle(MSCOREE_SHIM_W); + if (!hMSCorEE) { + + _ASSERTE(!"InstallUnhandledExceptionFilter failed to get MSCorEE instance!"); + STRESS_LOG0(LF_EH, LL_INFO10, "InstallUnhandledExceptionFilter failed to get MSCorEE instance!\n"); + + // Failure to get instance of mscoree.dll is fatal since that would imply + // that we cannot setup our UEF + return FALSE; + } + } + + // Signature of GetCLRUEFManager exported by MSCorEE.dll + typedef HRESULT (*pGetCLRUEFManager)(REFIID riid, + IUnknown **ppUnk); + + static pGetCLRUEFManager pFuncGetCLRUEFManager; + + if (!pFuncGetCLRUEFManager) { + + // Try to get function address via ordinal + pFuncGetCLRUEFManager = (pGetCLRUEFManager)GetProcAddress(hMSCorEE, MAKEINTRESOURCEA(24)); + if (!pFuncGetCLRUEFManager) { + _ASSERTE(!"InstallUnhandledExceptionFilter failed to get UEFManager!"); + STRESS_LOG0(LF_EH, LL_INFO10, "InstallUnhandledExceptionFilter failed to find UEFManager!\n"); + return FALSE; + } + } + + HRESULT hr = (*pFuncGetCLRUEFManager)((REFIID)IID_IUEFManager, (IUnknown **)&g_pUEFManager); + if (FAILED(hr)) + { + _ASSERTE(!"InstallUnhandledExceptionFilter failed to get IUEFManager*!"); + + STRESS_LOG0(LF_EH, LL_INFO10, "InstallUnhandledExceptionFilter failed to get IUEFManager*\n"); + + // Ensure the reference to chain manager is NULL + g_pUEFManager= NULL; + + return FALSE; + } + + // Register our UEF callback with the UEF chain manager + if (!g_pUEFManager->AddUnhandledExceptionFilter(COMUnhandledExceptionFilter, TRUE)) + { + _ASSERTE(!"InstallUnhandledExceptionFilter failed to register the UEF callback!"); + + // Failed to register the UEF callback + STRESS_LOG0(LF_EH, LL_INFO10, "InstallUnhandledExceptionFilter failed to register the UEF callback!\n"); + + g_pUEFManager->Release(); + + // Ensure the reference to chain manager is NULL + g_pUEFManager= NULL; + + return FALSE; + } + + // Register our exception claiming callback with the UEF chain manager on preWin7 + if (!RunningOnWin7() && !g_pUEFManager->GetWastonSxSManagerInstance()->RegisterExceptionClaimingCallback(IsExceptionFromManagedCodeCallback)) + { + _ASSERTE(!"RegisterExceptionClaimingCallback failed to register the exception claiming callback!"); + + // Failed to register the exception claiming callback + STRESS_LOG0(LF_EH, LL_INFO10, "RegisterExceptionClaimingCallback failed to register the exception claiming callback!"); + + return FALSE; + } + } +#else // !FEATURE_UEF_CHAINMANAGER + // We will be here only for CoreCLR on WLC since we dont + // register UEF for SL. + if (g_pOriginalUnhandledExceptionFilter == FILTER_NOT_INSTALLED) { + + #pragma prefast(push) + #pragma prefast(suppress:28725, "Calling to SetUnhandledExceptionFilter is intentional in this case.") + g_pOriginalUnhandledExceptionFilter = SetUnhandledExceptionFilter(COMUnhandledExceptionFilter); + #pragma prefast(pop) + + // make sure is set (ie. is not our special value to indicate unset) + LOG((LF_EH, LL_INFO10, "InstallUnhandledExceptionFilter registered UEF with OS for CoreCLR!\n")); + } + _ASSERTE(g_pOriginalUnhandledExceptionFilter != FILTER_NOT_INSTALLED); +#endif // FEATURE_UEF_CHAINMANAGER +#endif // !FEATURE_PAL + + // All done - successfully! + return TRUE; +} + +void UninstallUnhandledExceptionFilter() { + STATIC_CONTRACT_NOTHROW; + STATIC_CONTRACT_GC_NOTRIGGER; + STATIC_CONTRACT_MODE_ANY; + STATIC_CONTRACT_FORBID_FAULT; + +#ifndef FEATURE_PAL +#ifdef FEATURE_UEF_CHAINMANAGER + if (g_pUEFManager) + { + if (!RunningOnWin7()) + { + g_pUEFManager->GetWastonSxSManagerInstance()->UnregisterExceptionClaimingCallback(IsExceptionFromManagedCodeCallback); + } + + g_pUEFManager->RemoveUnhandledExceptionFilter(COMUnhandledExceptionFilter); + g_pUEFManager->Release(); + g_pUEFManager = NULL; + } +#else // !FEATURE_UEF_CHAINMANAGER + // We will be here only for CoreCLR on WLC or on Mac SL. + if (g_pOriginalUnhandledExceptionFilter != FILTER_NOT_INSTALLED) { + + #pragma prefast(push) + #pragma prefast(suppress:28725, "Calling to SetUnhandledExceptionFilter is intentional in this case.") + SetUnhandledExceptionFilter(g_pOriginalUnhandledExceptionFilter); + #pragma prefast(pop) + + g_pOriginalUnhandledExceptionFilter = FILTER_NOT_INSTALLED; + LOG((LF_EH, LL_INFO10, "UninstallUnhandledExceptionFilter unregistered UEF from OS for CoreCLR!\n")); + } +#endif // FEATURE_UEF_CHAINMANAGER +#endif // !FEATURE_PAL +} + +// +// Update the current throwable on the thread if necessary. If we're looking at one of our exceptions, and if the +// current throwable on the thread is NULL, then we'll set it to something more useful based on the +// LastThrownObject. +// +BOOL UpdateCurrentThrowable(PEXCEPTION_RECORD pExceptionRecord) +{ + STATIC_CONTRACT_THROWS; + STATIC_CONTRACT_MODE_ANY; + STATIC_CONTRACT_GC_TRIGGERS; + + BOOL useLastThrownObject = FALSE; + + Thread* pThread = GetThread(); + + // GetThrowable needs cooperative. + GCX_COOP(); + + if ((pThread->GetThrowable() == NULL) && (pThread->LastThrownObject() != NULL)) + { + // If GetThrowable is NULL and LastThrownObject is not, use lastThrownObject. + // In current (June 05) implementation, this is only used to pass to + // NotifyAppDomainsOfUnhandledException, which needs to get a throwable + // from somewhere, with which to notify the AppDomains. + useLastThrownObject = TRUE; + + if (IsComPlusException(pExceptionRecord)) + { +#ifndef WIN64EXCEPTIONS + OBJECTREF oThrowable = pThread->LastThrownObject(); + + // @TODO: we have a problem on Win64 where we won't have any place to + // store the throwable on an unhandled exception. Currently this + // only effects the managed debugging services as they will try + // to inspect the thread to see what the throwable is on an unhandled + // exception.. (but clearly it needs to be fixed asap) + // We have the same problem in EEPolicy::LogFatalError(). + LOG((LF_EH, LL_INFO100, "UpdateCurrentThrowable: setting throwable to %s\n", (oThrowable == NULL) ? "NULL" : oThrowable->GetTrueMethodTable()->GetDebugClassName())); + pThread->SafeSetThrowables(oThrowable); +#endif // WIN64EXCEPTIONS + } + } + + return useLastThrownObject; +} + +// +// COMUnhandledExceptionFilter is used to catch all unhandled exceptions. +// The debugger will either handle the exception, attach a debugger, or +// notify an existing attached debugger. +// + +struct SaveIPFilterParam +{ + SLOT ExceptionEIP; +}; + +LONG SaveIPFilter(EXCEPTION_POINTERS* ep, LPVOID pv) +{ + WRAPPER_NO_CONTRACT; + + SaveIPFilterParam *pParam = (SaveIPFilterParam *) pv; + pParam->ExceptionEIP = (SLOT)GetIP(ep->ContextRecord); + DefaultCatchFilterParam param(COMPLUS_EXCEPTION_EXECUTE_HANDLER); + return DefaultCatchFilter(ep, ¶m); +} + +//------------------------------------------------------------------------------ +// Description +// Does not call any previous UnhandledExceptionFilter. The assumption is that +// either it is inappropriate to call it (because we have elected to rip the +// process without transitioning completely to the base of the thread), or +// the caller has already consulted the previously installed UnhandledExceptionFilter. +// +// So we know we are ripping and Watson is appropriate. +// +// **** Note***** +// This is a stack-sensitive function if we have an unhandled SO. +// Do not allocate more than a few bytes on the stack or we risk taking an +// AV while trying to throw up Watson. + +// Parameters +// pExceptionInfo -- information about the exception that caused the error. +// If the error is not the result of an exception, pass NULL for this +// parameter +// +// Returns +// EXCEPTION_CONTINUE_SEARCH -- we've done anything we will with the exception. +// As far as the runtime is concerned, the process is doomed. +// EXCEPTION_CONTINUE_EXECUTION -- means a debugger "caught" the exception and +// wants to continue running. +// EXCEPTION_EXECUTE_HANDLER -- CoreCLR only, and only when not running as a UEF. +// Returned only if the host has asked us to swallow unhandled exceptions on +// managed threads in an AD they (the host) creates. +//------------------------------------------------------------------------------ +LONG InternalUnhandledExceptionFilter_Worker( + EXCEPTION_POINTERS *pExceptionInfo) // Information about the exception +{ + STATIC_CONTRACT_THROWS; + STATIC_CONTRACT_GC_TRIGGERS; + STATIC_CONTRACT_MODE_ANY; + +#ifdef _DEBUG + static int fBreakOnUEF = -1; + if (fBreakOnUEF==-1) fBreakOnUEF = CLRConfig::GetConfigValue(CLRConfig::INTERNAL_BreakOnUEF); + _ASSERTE(!fBreakOnUEF); +#endif + + STRESS_LOG2(LF_EH, LL_INFO10, "In InternalUnhandledExceptionFilter_Worker, Exception = %x, sp = %p\n", + pExceptionInfo->ExceptionRecord->ExceptionCode, GetCurrentSP()); + + // If we can't enter the EE, done. + if (g_fForbidEnterEE) + { + LOG((LF_EH, LL_INFO100, "InternalUnhandledExceptionFilter_Worker: g_fForbidEnterEE is TRUE\n")); + return EXCEPTION_CONTINUE_SEARCH; + } + + + if (GetEEPolicy()->GetActionOnFailure(FAIL_FatalRuntime) == eDisableRuntime) + { + ETaskType type = ::GetCurrentTaskType(); + if (type != TT_UNKNOWN && type != TT_USER) + { + LOG((LF_EH, LL_INFO100, "InternalUnhandledExceptionFilter_Worker: calling EEPolicy::HandleFatalError\n")); + EEPolicy::HandleFatalError(COR_E_EXECUTIONENGINE, (UINT_PTR)GetIP(pExceptionInfo->ContextRecord), NULL, pExceptionInfo); + } + } + + // We don't do anything when this is called from an unmanaged thread. + Thread *pThread = GetThread(); + +#ifdef _DEBUG + static bool bBreakOnUncaught = false; + static int fBreakOnUncaught = 0; + + if (!bBreakOnUncaught) + { + fBreakOnUncaught = CLRConfig::GetConfigValue(CLRConfig::INTERNAL_BreakOnUncaughtException); + bBreakOnUncaught = true; + } + if (fBreakOnUncaught != 0) + { + if (pExceptionInfo->ExceptionRecord->ExceptionCode == STATUS_STACK_OVERFLOW) + { + // if we've got an uncaught SO, we don't have enough stack to pop a debug break. So instead, + // loop infinitely and we can attach a debugger at that point and break in. + LOG((LF_EH, LL_INFO100, "InternalUnhandledExceptionFilter_Worker: Infinite loop on uncaught SO\n")); + for ( ;; ) + { + } + } + else + { + LOG((LF_EH, LL_INFO100, "InternalUnhandledExceptionFilter_Worker: ASSERTING on uncaught\n")); + _ASSERTE(!"BreakOnUnCaughtException"); + } + } +#endif + +#ifdef _DEBUG_ADUNLOAD + printf("%x InternalUnhandledExceptionFilter_Worker: Called for %x\n", + ((pThread == NULL) ? NULL : pThread->GetThreadId()), pExceptionInfo->ExceptionRecord->ExceptionCode); + fflush(stdout); +#endif + + // This shouldn't be possible, but MSVC re-installs us... for now, just bail if this happens. + if (g_fNoExceptions) + { + return EXCEPTION_CONTINUE_SEARCH; + } + + // Are we looking at a stack overflow here? + if ((pThread != NULL) && !pThread->DetermineIfGuardPagePresent()) + { + g_fForbidEnterEE = true; + } + +#ifdef DEBUGGING_SUPPORTED + + // Mark that this exception has gone unhandled. At the moment only the debugger will + // ever look at this flag. This should come before any user-visible side effect of an exception + // being unhandled as seen from managed code or from a debugger. These include the + // managed unhandled notification callback, execution of catch/finally clauses, + // receiving the managed debugger unhandled exception event, + // the OS sending the debugger 2nd pass native exception notification, etc. + // + // This needs to be done before the check for TSNC_ProcessedUnhandledException because it is perfectly + // legitimate (though rare) for the debugger to be inspecting exceptions which are nested in finally + // clauses that run after an unhandled exception has already occurred on the thread + if ((pThread != NULL) && pThread->IsExceptionInProgress()) + { + LOG((LF_EH, LL_INFO1000, "InternalUnhandledExceptionFilter_Worker: Set unhandled exception flag at %p\n", + pThread->GetExceptionState()->GetFlags() )); + pThread->GetExceptionState()->GetFlags()->SetUnhandled(); + } +#endif + + // If we have already done unhandled exception processing for this thread, then + // simply return back. See comment in threads.h for details for the flag + // below. + // + if (pThread && (pThread->HasThreadStateNC(Thread::TSNC_ProcessedUnhandledException) || pThread->HasThreadStateNC(Thread::TSNC_AppDomainContainUnhandled))) + { +#ifdef FEATURE_CORECLR + // This assert shouldnt be hit in CoreCLR since: + // + // 1) It has no concept of managed entry point that is invoked by the shim. You can + // only run managed code via hosting APIs that will run code in non-default domains. + // + // 2) Managed threads cannot be created in DefaultDomain since no user code executes + // in default domain. + // + // So, if this is hit, something is not right! + if (pThread->HasThreadStateNC(Thread::TSNC_ProcessedUnhandledException)) + { + _ASSERTE(!"How come a thread with TSNC_ProcessedUnhandledException state entered the UEF on CoreCLR?"); + } +#endif // FEATURE_CORECLR + + LOG((LF_EH, LL_INFO100, "InternalUnhandledExceptionFilter_Worker: have already processed unhandled exception for this thread.\n")); + return EXCEPTION_CONTINUE_SEARCH; + } + + LOG((LF_EH, LL_INFO100, "InternalUnhandledExceptionFilter_Worker: Handling\n")); + + struct Param : SaveIPFilterParam + { + EXCEPTION_POINTERS *pExceptionInfo; + Thread *pThread; + LONG retval; + BOOL fIgnore; + }; Param param; + + param.ExceptionEIP = 0; + param.pExceptionInfo = pExceptionInfo; + param.pThread = pThread; + param.retval = EXCEPTION_CONTINUE_SEARCH; // Result of UEF filter. + + // Is this a particular kind of exception that we'd like to ignore? + param.fIgnore = ((param.pExceptionInfo->ExceptionRecord->ExceptionCode == STATUS_BREAKPOINT) || + (param.pExceptionInfo->ExceptionRecord->ExceptionCode == STATUS_SINGLE_STEP)); + + PAL_TRY(Param *, pParam, ¶m) + { + // If fIgnore, then this is some sort of breakpoint, not a "normal" unhandled exception. But, the + // breakpoint is due to an int3 or debugger step instruction, not due to calling Debugger.Break() + TypeOfReportedError tore = pParam->fIgnore ? TypeOfReportedError::NativeBreakpoint : TypeOfReportedError::UnhandledException; + + // + // If this exception is on a thread without managed code, then report this as a NativeThreadUnhandledException + // + // The thread object may exist if there was once managed code on the stack, but if the exception never + // bubbled thru managed code, ie no managed code is on its stack, then this is a native unhandled exception + // + // Ignore breakpoints and single-step. + if (!pParam->fIgnore) + { // Possibly interesting exception. Is there no Thread at all? Or, is there a Thread, + // but with no exception at all on it? + if ((pParam->pThread == NULL) || + (pParam->pThread->IsThrowableNull() && pParam->pThread->IsLastThrownObjectNull()) ) + { // Whatever this exception is, we don't know about it. Treat as Native. + tore = TypeOfReportedError::NativeThreadUnhandledException; + } + } + + // If there is no throwable on the thread, go ahead and update from the last thrown exception if possible. + // Note: don't do this for exceptions that we're going to ignore below anyway... + BOOL useLastThrownObject = FALSE; + if (!pParam->fIgnore && (pParam->pThread != NULL)) + { + useLastThrownObject = UpdateCurrentThrowable(pParam->pExceptionInfo->ExceptionRecord); + } + +#ifdef DEBUGGING_SUPPORTED + + LOG((LF_EH, LL_INFO100, "InternalUnhandledExceptionFilter_Worker: Notifying Debugger...\n")); + + // If we are using the throwable in LastThrownObject, mark that it is now unhandled + if ((pParam->pThread != NULL) && useLastThrownObject) + { + LOG((LF_EH, LL_INFO1000, "InternalUnhandledExceptionFilter_Worker: Set lto is unhandled\n")); + pParam->pThread->MarkLastThrownObjectUnhandled(); + } + + // + // We don't want the managed debugger to try to "intercept" breakpoints + // or singlestep exceptions. + // TODO: why does the exception handling code need to set this? Shouldn't the debugger code + // be able to determine what it can/should intercept? + if ((pParam->pThread != NULL) && pParam->pThread->IsExceptionInProgress() && pParam->fIgnore) + { + pParam->pThread->GetExceptionState()->GetFlags()->SetDebuggerInterceptNotPossible(); + } + + + if (pParam->pThread != NULL) + { + BOOL fIsProcessTerminating = TRUE; + +#ifdef FEATURE_CORECLR + // In CoreCLR, we can be asked to not let an exception go unhandled on managed threads in a given AppDomain. + // If the exception reaches the top of the thread's stack, we simply deliver AppDomain's UnhandledException event and + // return back to the filter, instead of letting the process terminate because of unhandled exception. + + // Below is how we perform the check: + // + // 1) The flag is specified on the AD when it is created by the host and all managed threads created + // in such an AD will inherit the flag. For non-finalizer and non-threadpool threads, we check the flag against the thread. + // 2) The finalizer thread always switches to the AD of the object that is going to be finalized. Thus, + // while it wont have the flag specified, the AD it switches to will. + // 3) The threadpool thread also switches to the correct AD before executing the request. The thread wont have the + // flag specified, but the AD it switches to will. + + // This code must only be exercised when running as a normal filter; returning + // EXCEPTION_EXECUTE_HANDLER is not valid if this code is being invoked from + // the UEF. + // Fortunately, we should never get into this case, since the thread flag about + // ignoring unhandled exceptions cannot be set on the default domain. + + if (IsFinalizerThread() || (pParam->pThread->IsThreadPoolThread())) + fIsProcessTerminating = !(pParam->pThread->GetDomain()->IgnoreUnhandledExceptions()); + else + fIsProcessTerminating = !(pParam->pThread->HasThreadStateNC(Thread::TSNC_IgnoreUnhandledExceptions)); +#endif // FEATURE_CORECLR + +#ifndef FEATURE_PAL + // Setup the watson bucketing details for UE processing. + // do this before notifying appdomains of the UE so if an AD attempts to + // retrieve the bucket params in the UE event handler it gets the correct data. + SetupWatsonBucketsForUEF(useLastThrownObject); +#endif // !FEATURE_PAL + + // Send notifications to the AppDomains. + NotifyAppDomainsOfUnhandledException(pParam->pExceptionInfo, NULL, useLastThrownObject, fIsProcessTerminating /*isTerminating*/); + +#ifdef FEATURE_CORECLR + // If the process is not terminating, then return back to the filter and ask it to execute + if (!fIsProcessTerminating) + { + pParam->retval = EXCEPTION_EXECUTE_HANDLER; + goto lDone; + } +#endif // FEATURE_CORECLR + } + else + { + LOG((LF_EH, LL_INFO100, "InternalUnhandledExceptionFilter_Worker: Not collecting bucket information as thread object does not exist\n")); + } + + // AppDomain.UnhandledException event could have thrown an exception that would have gone unhandled in managed code. + // The runtime swallows all such exceptions. Hence, if we are not using LastThrownObject and the current LastThrownObject + // is not the same as the one in active exception tracker (if available), then update the last thrown object. + if ((pParam->pThread != NULL) && (!useLastThrownObject)) + { + GCX_COOP_NO_DTOR(); + + OBJECTREF oThrowable = pParam->pThread->GetThrowable(); + if ((oThrowable != NULL) && (pParam->pThread->LastThrownObject() != oThrowable)) + { + pParam->pThread->SafeSetLastThrownObject(oThrowable); + LOG((LF_EH, LL_INFO100, "InternalUnhandledExceptionFilter_Worker: Resetting the LastThrownObject as it appears to have changed.\n")); + } + + GCX_COOP_NO_DTOR_END(); + } + + // Launch Watson and see if we want to debug the process + // + // Note that we need to do this before "ignoring" exceptions like + // breakpoints and single step exceptions + // + + LOG((LF_EH, LL_INFO100, "InternalUnhandledExceptionFilter_Worker: Launching Watson at sp %p ...\n", GetCurrentSP())); + + if (WatsonLastChance(pParam->pThread, pParam->pExceptionInfo, tore) == EXCEPTION_CONTINUE_EXECUTION) + { + LOG((LF_EH, LL_INFO100, "InternalUnhandledExceptionFilter_Worker: debugger ==> EXCEPTION_CONTINUE_EXECUTION\n")); + pParam->retval = EXCEPTION_CONTINUE_EXECUTION; + goto lDone; + } + + LOG((LF_EH, LL_INFO100, "InternalUnhandledExceptionFilter_Worker: ... returned.\n")); +#endif // DEBUGGING_SUPPORTED + + +#if defined(FEATURE_EVENT_TRACE) && !defined(FEATURE_PAL) + DoReportForUnhandledException(pParam->pExceptionInfo); +#endif // FEATURE_EVENT_TRACE + + // + // Except for notifying debugger, ignore exception if unmanaged, or + // if it's a debugger-generated exception or user breakpoint exception. + // + if (tore.GetType() == TypeOfReportedError::NativeThreadUnhandledException) + { + pParam->retval = EXCEPTION_CONTINUE_SEARCH; + goto lDone; + } + + if (pParam->fIgnore) + { + LOG((LF_EH, LL_INFO100, "InternalUnhandledExceptionFilter_Worker, ignoring the exception\n")); + pParam->retval = EXCEPTION_CONTINUE_SEARCH; + goto lDone; + } + + LOG((LF_EH, LL_INFO100, "InternalUnhandledExceptionFilter_Worker: Calling DefaultCatchHandler\n")); + + + // Call our default catch handler to do the managed unhandled exception work. + DefaultCatchHandler(pParam->pExceptionInfo, NULL, useLastThrownObject, + TRUE /*isTerminating*/, FALSE /*isThreadBaseFIlter*/, FALSE /*sendAppDomainEvents*/); + +lDone: ; + } + PAL_EXCEPT_FILTER (SaveIPFilter) + { + // Should never get here. +#ifdef _DEBUG + char buffer[200]; + sprintf_s(buffer, 200, "\nInternal error: Uncaught exception was thrown from IP = %p in UnhandledExceptionFilter_Worker on thread 0x%08x\n", + param.ExceptionEIP, ((GetThread() == NULL) ? NULL : GetThread()->GetThreadId())); + PrintToStdErrA(buffer); + _ASSERTE(!"Unexpected exception in UnhandledExceptionFilter_Worker"); +#endif + EEPOLICY_HANDLE_FATAL_ERROR(COR_E_EXECUTIONENGINE) + } + PAL_ENDTRY; + + //if (param.fIgnore) + //{ + // VC's try/catch ignores breakpoint or single step exceptions. We can not continue running. + // TerminateProcess(GetCurrentProcess(), pExceptionInfo->ExceptionRecord->ExceptionCode); + //} + + return param.retval; +} // LONG InternalUnhandledExceptionFilter_Worker() + +//------------------------------------------------------------------------------ +// Description +// Calls our InternalUnhandledExceptionFilter for Watson at the appropriate +// place in the chain. +// +// For non-side-by-side CLR's, we call everyone else's UEF first. +// +// For side-by-side CLR's, we call our own filter first. This is primary +// so Whidbey's UEF won't put up a second dialog box. In exchange, +// side-by-side CLR's won't put up UI's unless the EH really came +// from that instance's managed code. +// +// Parameters +// pExceptionInfo -- information about the exception that caused the error. +// If the error is not the result of an exception, pass NULL for this +// parameter +// +// Returns +// EXCEPTION_CONTINUE_SEARCH -- we've done anything we will with the exception. +// As far as the runtime is concerned, the process is doomed. +// EXCEPTION_CONTINUE_EXECUTION -- means a debugger "caught" the exception and +// wants to continue running. +//------------------------------------------------------------------------------ +LONG InternalUnhandledExceptionFilter( + EXCEPTION_POINTERS *pExceptionInfo) // Information about the exception +{ + STATIC_CONTRACT_THROWS; + STATIC_CONTRACT_GC_TRIGGERS; + STATIC_CONTRACT_MODE_ANY; + // We don't need to be SO-robust for an unhandled exception + SO_NOT_MAINLINE_FUNCTION; + + LOG((LF_EH, LL_INFO100, "InternalUnhandledExceptionFilter: at sp %p.\n", GetCurrentSP())); + + // Side-by-side UEF: Calls ours first, then the rest (unless we put up a UI for + // the exception.) + + LONG retval = InternalUnhandledExceptionFilter_Worker(pExceptionInfo); // Result of UEF filter. + + // Keep looking, or done? + if (retval != EXCEPTION_CONTINUE_SEARCH) + { // done. + return retval; + } + + BOOL fShouldOurUEFDisplayUI = ShouldOurUEFDisplayUI(pExceptionInfo); + + // If this is a managed exception thrown by this instance of the CLR, the exception is no one's + // business but ours (nudge, nudge: Whidbey). Break the UEF chain at this point. + if (fShouldOurUEFDisplayUI) + { + return retval; + } + + // Chaining back to previous UEF handler could be a potential security risk. See + // http://uninformed.org/index.cgi?v=4&a=5&p=1 for details. We are not alone in + // stopping the chain - CRT (as of Orcas) is also doing that. + // + // The change below applies to a thread that starts in native mode and transitions to managed. + + // Let us assume the process loaded two CoreCLRs, C1 and C2, in that order. Thus, in the UEF chain + // (assuming no other entity setup their UEF), C2?s UEF will be the topmost. + // + // Now, assume the stack looks like the following (stack grows down): + // + // Native frame + // Managed Frame (C1) + // Managed Frame (C2) + // Managed Frame (C1) + // Managed Frame (C2) + // Managed Frame (C1) + // + // Suppose an exception is thrown in C1 instance in the last managed frame and it goes unhandled. Eventually + // it will reach the OS which will invoke the UEF. Note that the topmost UEF belongs to C2 instance and it + // will start processing the exception. C2?s UEF could return EXCEPTION_CONTINUE_SEARCH to indicate + // that we should handoff the processing to the last installed UEF. In the example above, we would handoff + // the control to the UEF of the CoreCLR instance that actually threw the exception today. In reality, it + // could be some unknown code too. + // + // Not chaining back to the last UEF, in the case of this example, would imply that certain notifications + // (e.g. Unhandled Exception Notification to the AppDomain) specific to the instance that raised the exception + // will not get fired. However, similar behavior can happen today if another UEF sits between + // C1 and C2 and that may not callback to C1 or perhaps just terminate process. + // + // For CoreCLR, this will not be an issue. See + // http://sharepoint/sites/clros/Shared%20Documents/Design%20Documents/EH/Chaining%20in%20%20UEF%20-%20One%20Pager.docx + // for details. + // + // Note: Also see the conditional UEF registration with the OS in EEStartupHelper. + +#ifdef FEATURE_CORECLR + // We would be here only on CoreCLR for WLC since we dont register + // the UEF with the OS for SL. + if (g_pOriginalUnhandledExceptionFilter != FILTER_NOT_INSTALLED + && g_pOriginalUnhandledExceptionFilter != NULL) + { + STRESS_LOG1(LF_EH, LL_INFO100, "InternalUnhandledExceptionFilter: Not chaining back to previous UEF at address %p on CoreCLR!\n", g_pOriginalUnhandledExceptionFilter); + } +#endif // FEATURE_CORECLR + + return retval; + +} // LONG InternalUnhandledExceptionFilter() + +// This filter is used to trigger unhandled exception processing for the entrypoint thread +// incase an exception goes unhandled from it. This makes us independent of the OS +// UEF mechanism to invoke our registered UEF to trigger CLR specific unhandled exception +// processing since that can be skipped if another UEF registered over ours and not chain back. +LONG EntryPointFilter(PEXCEPTION_POINTERS pExceptionInfo, PVOID _pData) +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_ANY; + SO_TOLERANT; + } + CONTRACTL_END; + + LONG ret = -1; + + BEGIN_SO_INTOLERANT_CODE_NO_THROW_CHECK_THREAD(return EXCEPTION_CONTINUE_SEARCH;); + + // Invoke the UEF worker to perform unhandled exception processing + ret = InternalUnhandledExceptionFilter_Worker (pExceptionInfo); + + Thread* pThread = GetThread(); + if (pThread) + { + // Set the flag that we have done unhandled exception processing for this thread + // so that we dont duplicate the effort in the UEF. + // + // For details on this flag, refer to threads.h. + LOG((LF_EH, LL_INFO100, "EntryPointFilter: setting TSNC_ProcessedUnhandledException\n")); + pThread->SetThreadStateNC(Thread::TSNC_ProcessedUnhandledException); + } + +#ifdef FEATURE_UEF_CHAINMANAGER + if (g_pUEFManager && (ret == EXCEPTION_CONTINUE_SEARCH)) + { + // Since the "UEF" of this runtime instance didnt handle the exception, + // invoke the other registered UEF callbacks as well + ret = g_pUEFManager->InvokeUEFCallbacks(pExceptionInfo); + } +#endif // FEATURE_UEF_CHAINMANAGER + + END_SO_INTOLERANT_CODE; + + return ret; +} + +//------------------------------------------------------------------------------ +// Description +// The actual UEF. Defers to InternalUnhandledExceptionFilter. +// +// Updated to be in its own code segment named CLR_UEF_SECTION_NAME to prevent +// "VirtualProtect" calls from affecting its pages and thus, its +// invocation. For details, see the comment within the implementation of +// CExecutionEngine::ClrVirtualProtect. +// +// Parameters +// pExceptionInfo -- information about the exception +// +// Returns +// the result of calling InternalUnhandledExceptionFilter +//------------------------------------------------------------------------------ +#if defined(FEATURE_CORECLR) && !defined(FEATURE_PAL) +#pragma code_seg(push, uef, CLR_UEF_SECTION_NAME) +#endif // FEATURE_CORECLR && !FEATURE_PAL +LONG __stdcall COMUnhandledExceptionFilter( // EXCEPTION_CONTINUE_SEARCH or EXCEPTION_CONTINUE_EXECUTION + EXCEPTION_POINTERS *pExceptionInfo) // Information about the exception. +{ + STATIC_CONTRACT_THROWS; + STATIC_CONTRACT_GC_TRIGGERS; + STATIC_CONTRACT_MODE_ANY; + // We don't need to be SO-robust for an unhandled exception + SO_NOT_MAINLINE_FUNCTION; + + LONG retVal = EXCEPTION_CONTINUE_SEARCH; + + // Incase of unhandled exceptions on managed threads, we kick in our UE processing at the thread base and also invoke + // UEF callbacks that various runtimes have registered with us. Once the callbacks return, we return back to the OS + // to give other registered UEFs a chance to do their custom processing. + // + // If the topmost UEF registered with the OS belongs to mscoruef.dll (or someone chained back to its UEF callback), + // it will start invoking the UEF callbacks (which is this function, COMUnhandledExceptionFiler) registered by + // various runtimes again. + // + // Thus, check if this UEF has already been invoked in context of this thread and runtime and if so, dont invoke it again. + if (GetThread() && (GetThread()->HasThreadStateNC(Thread::TSNC_ProcessedUnhandledException) || + GetThread()->HasThreadStateNC(Thread::TSNC_AppDomainContainUnhandled))) + { + LOG((LF_EH, LL_INFO10, "Exiting COMUnhandledExceptionFilter since we have already done UE processing for this thread!\n")); + return retVal; + } + +#ifndef FEATURE_CORECLR +#ifdef _DEBUG + // V4 onwards, we will reach here in the UEF only on the following conditions: + // + // 1) Faulting address is in native code on a reverse pinvoke thread. An example is an exception that escaped + // out of the reverse pinvoke thread but was caught in the native part of the thread. The native part then + // had another exception that went unhandled. The difference between this and (3) below is that + // we have a thread object here but not in (3). + // + // An exception from PInvoke, that is never caught/rethrown in managed code and goes unhandled, also falls + // in this category. + // + // 2) The exception escaped out of a reverse pinvoke thread and went unhandled. + // + // 3) Faulting thread was never seen by the runtime. An example is a another native thread + // which the user code created that had unhandled exception. + // + // 4) A corrupting exception may become unhandled. + // + // This is not applicable to CoreCLR, as this unhandled exception filter is always set up, and all hardware exceptions in + // managed code, including those that are not process-corrupting, such as integer division by zero, will end up here. + + // Assert these conditions here - we shouldnt be here for any other unhandled exception processing. + Thread *pThread = GetThread(); + if ((pThread != NULL) && // condition 3 + !(pThread->GetExceptionState()->IsExceptionInProgress() && + pThread->GetExceptionState()->GetFlags()->ReversePInvokeEscapingException()) && // condition 2 + (ExecutionManager::IsManagedCode((PCODE)pExceptionInfo->ExceptionRecord->ExceptionAddress))) // condition 1 + { +#ifdef FEATURE_CORRUPTING_EXCEPTIONS + if (!CEHelper::IsProcessCorruptedStateException(pExceptionInfo->ExceptionRecord->ExceptionCode)) // condition 4 + { + GCX_COOP(); + _ASSERTE(CEHelper::IsProcessCorruptedStateException(pThread->GetThrowable())); // condition 4 + } +#else // !FEATURE_CORRUPTING_EXCEPTIONS + _ASSERTE(false); +#endif // FEATURE_CORRUPTING_EXCEPTIONS + } +#endif // _DEBUG +#endif // !FEATURE_CORECLR + + retVal = InternalUnhandledExceptionFilter(pExceptionInfo); + + // If thread object exists, mark that this thread has done unhandled exception processing + if (GetThread()) + { + LOG((LF_EH, LL_INFO100, "COMUnhandledExceptionFilter: setting TSNC_ProcessedUnhandledException\n")); + GetThread()->SetThreadStateNC(Thread::TSNC_ProcessedUnhandledException); + } + + return retVal; +} // LONG __stdcall COMUnhandledExceptionFilter() +#if defined(FEATURE_CORECLR) && !defined(FEATURE_PAL) +#pragma code_seg(pop, uef) +#endif // FEATURE_CORECLR && !FEATURE_PAL + +void PrintStackTraceToStdout(); + +static SString GetExceptionMessageWrapper(Thread* pThread, OBJECTREF throwable) +{ + STATIC_CONTRACT_THROWS; + STATIC_CONTRACT_MODE_COOPERATIVE; + STATIC_CONTRACT_GC_TRIGGERS; + + StackSString result; + + INSTALL_NESTED_EXCEPTION_HANDLER(pThread->GetFrame()); + GetExceptionMessage(throwable, result); + UNINSTALL_NESTED_EXCEPTION_HANDLER(); + + return result; +} + +void STDMETHODCALLTYPE +DefaultCatchHandlerExceptionMessageWorker(Thread* pThread, + OBJECTREF throwable, + __inout_ecount(buf_size) WCHAR *buf, + const int buf_size) +{ + if (throwable != NULL) + { + PrintToStdErrA("\n"); + + if (FAILED(UtilLoadResourceString(CCompRC::Error, IDS_EE_UNHANDLED_EXCEPTION, buf, buf_size))) + { + wcsncpy_s(buf, buf_size, SZ_UNHANDLED_EXCEPTION, SZ_UNHANDLED_EXCEPTION_CHARLEN); + } + + PrintToStdErrW(buf); + PrintToStdErrA(" "); + + SString message = GetExceptionMessageWrapper(pThread, throwable); + + if (!message.IsEmpty()) + { + NPrintToStdErrW(message, message.GetCount()); + } + + PrintToStdErrA("\n"); + } +} + +//****************************************************************************** +// DefaultCatchHandler -- common processing for otherwise uncaught exceptions. +//****************************************************************************** +void STDMETHODCALLTYPE +DefaultCatchHandler(PEXCEPTION_POINTERS pExceptionPointers, + OBJECTREF *pThrowableIn, + BOOL useLastThrownObject, + BOOL isTerminating, + BOOL isThreadBaseFilter, + BOOL sendAppDomainEvents) +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_ANY; + } + CONTRACTL_END; + + // <TODO> The strings in here should be translatable.</TODO> + LOG((LF_EH, LL_INFO10, "In DefaultCatchHandler\n")); + +#if defined(_DEBUG) + static bool bHaveInitialized_BreakOnUncaught = false; + enum BreakOnUncaughtAction { + breakOnNone = 0, // Default. + breakOnAll = 1, // Always break. + breakSelective = 2, // Break on exceptions application can catch, + // but not ThreadAbort, AppdomainUnload + breakOnMax = 2 + }; + static DWORD breakOnUncaught = breakOnNone; + + if (!bHaveInitialized_BreakOnUncaught) + { + breakOnUncaught = CLRConfig::GetConfigValue(CLRConfig::INTERNAL_BreakOnUncaughtException); + if (breakOnUncaught > breakOnMax) + { // Could turn it off completely, or turn into legal value. Since it is debug code, be accommodating. + breakOnUncaught = breakOnAll; + } + bHaveInitialized_BreakOnUncaught = true; + } + + if (breakOnUncaught == breakOnAll) + { + _ASSERTE(!"BreakOnUnCaughtException"); + } + + int suppressSelectiveBreak = false; // to filter for the case where breakOnUncaught == "2" +#endif + + Thread *pThread = GetThread(); + + // The following reduces a window for a race during shutdown. + if (!pThread) + { + _ASSERTE(g_fEEShutDown); + return; + } + + _ASSERTE(pThread); + + ThreadPreventAsyncHolder prevAsync; + + GCX_COOP(); + + OBJECTREF throwable; + + if (pThrowableIn != NULL) + { + throwable = *pThrowableIn; + } + else if (useLastThrownObject) + { + throwable = pThread->LastThrownObject(); + } + else + { + throwable = pThread->GetThrowable(); + } + + // If we've got no managed object, then we can't send an event or print a message, so we just return. + if (throwable == NULL) + { +#ifdef LOGGING + if (!pThread->IsRudeAbortInitiated()) + { + LOG((LF_EH, LL_INFO10, "Unhandled exception, throwable == NULL\n")); + } +#endif + + return; + } + +#ifdef _DEBUG + DWORD unbreakableLockCount = 0; + // Do not care about lock check for unhandled exception. + while (pThread->HasUnbreakableLock()) + { + pThread->DecUnbreakableLockCount(); + unbreakableLockCount ++; + } + BOOL fOwnsSpinLock = pThread->HasThreadStateNC(Thread::TSNC_OwnsSpinLock); + if (fOwnsSpinLock) + { + pThread->ResetThreadStateNC(Thread::TSNC_OwnsSpinLock); + } +#endif + + GCPROTECT_BEGIN(throwable); + //BOOL IsStackOverflow = (throwable->GetTrueMethodTable() == g_pStackOverflowExceptionClass); + BOOL IsOutOfMemory = (throwable->GetTrueMethodTable() == g_pOutOfMemoryExceptionClass); + + // Notify the AppDomain that we have taken an unhandled exception. Can't notify of stack overflow -- guard + // page is not yet reset. + BOOL SentEvent = FALSE; + + // Send up the unhandled exception appdomain event. + if (sendAppDomainEvents) + { + SentEvent = NotifyAppDomainsOfUnhandledException(pExceptionPointers, &throwable, useLastThrownObject, isTerminating); + } + + const int buf_size = 128; + WCHAR buf[buf_size] = {0}; + + // See detailed explanation of this flag in threads.cpp. But the basic idea is that we already + // reported the exception in the AppDomain where it went unhandled, so we don't need to report + // it at the process level. + // Print the unhandled exception message. + if (!pThread->HasThreadStateNC(Thread::TSNC_AppDomainContainUnhandled)) + { + EX_TRY + { + EX_TRY + { + // If this isn't ThreadAbortException, we want to print a stack trace to indicate why this thread abruptly + // terminated. Exceptions kill threads rarely enough that an uncached name check is reasonable. + BOOL dump = TRUE; + + if (/*IsStackOverflow ||*/ + !pThread->DetermineIfGuardPagePresent() || + IsOutOfMemory) + { + // We have to be very careful. If we walk off the end of the stack, the process will just + // die. e.g. IsAsyncThreadException() and Exception.ToString both consume too much stack -- and can't + // be called here. + dump = FALSE; + PrintToStdErrA("\n"); + + if (FAILED(UtilLoadStringRC(IDS_EE_UNHANDLED_EXCEPTION, buf, buf_size))) + { + wcsncpy_s(buf, COUNTOF(buf), SZ_UNHANDLED_EXCEPTION, SZ_UNHANDLED_EXCEPTION_CHARLEN); + } + + PrintToStdErrW(buf); + + if (IsOutOfMemory) + { + PrintToStdErrA(" OutOfMemoryException.\n"); + } + else + { + PrintToStdErrA(" StackOverflowException.\n"); + } + } + else if (!CanRunManagedCode(LoaderLockCheck::None)) + { + // Well, if we can't enter the runtime, we very well can't get the exception message. + dump = FALSE; + } + else if (SentEvent || IsAsyncThreadException(&throwable)) + { + // We don't print anything on async exceptions, like ThreadAbort. + dump = FALSE; + INDEBUG(suppressSelectiveBreak=TRUE); + } + else if (isThreadBaseFilter && IsExceptionOfType(kAppDomainUnloadedException, &throwable)) + { + // AppdomainUnloadedException is also a special case. + dump = FALSE; + INDEBUG(suppressSelectiveBreak=TRUE); + } + + // Finally, should we print the message? + if (dump) + { + // this is stack heavy because of the CQuickWSTRBase, so we break it out + // and don't have to carry the weight through our other code paths. + DefaultCatchHandlerExceptionMessageWorker(pThread, throwable, buf, buf_size); + } + } + EX_CATCH + { + LOG((LF_EH, LL_INFO10, "Exception occurred while processing uncaught exception\n")); + UtilLoadStringRC(IDS_EE_EXCEPTION_TOSTRING_FAILED, buf, buf_size); + PrintToStdErrA("\n "); + PrintToStdErrW(buf); + PrintToStdErrA("\n"); + } + EX_END_CATCH(SwallowAllExceptions); + } + EX_CATCH + { // If we got here, we can't even print the localized error message. Print non-localized. + LOG((LF_EH, LL_INFO10, "Exception occurred while logging processing uncaught exception\n")); + PrintToStdErrA("\n Error: Can't print exception string because Exception.ToString() failed.\n"); + } + EX_END_CATCH(SwallowAllExceptions); + } + +#if defined(_DEBUG) + if ((breakOnUncaught == breakSelective) && !suppressSelectiveBreak) + { + _ASSERTE(!"BreakOnUnCaughtException"); + } +#endif // defined(_DEBUG) + + FlushLogging(); // Flush any logging output + GCPROTECT_END(); + +#ifdef _DEBUG + // Do not care about lock check for unhandled exception. + while (unbreakableLockCount) + { + pThread->IncUnbreakableLockCount(); + unbreakableLockCount --; + } + if (fOwnsSpinLock) + { + pThread->SetThreadStateNC(Thread::TSNC_OwnsSpinLock); + } +#endif +} // DefaultCatchHandler() + + +//****************************************************************************** +// NotifyAppDomainsOfUnhandledException -- common processing for otherwise uncaught exceptions. +//****************************************************************************** +BOOL NotifyAppDomainsOfUnhandledException( + PEXCEPTION_POINTERS pExceptionPointers, + OBJECTREF *pThrowableIn, + BOOL useLastThrownObject, + BOOL isTerminating) +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_ANY; + } + CONTRACTL_END; + +#ifdef _DEBUG + static int fBreakOnNotify = -1; + if (fBreakOnNotify==-1) fBreakOnNotify = CLRConfig::GetConfigValue(CLRConfig::INTERNAL_BreakOnNotify); + _ASSERTE(!fBreakOnNotify); +#endif + + BOOL SentEvent = FALSE; + + LOG((LF_EH, LL_INFO10, "In NotifyAppDomainsOfUnhandledException\n")); + + Thread *pThread = GetThread(); + + // The following reduces a window for a race during shutdown. + if (!pThread) + { + _ASSERTE(g_fEEShutDown); + return FALSE; + } + + // See detailed explanation of this flag in threads.cpp. But the basic idea is that we already + // reported the exception in the AppDomain where it went unhandled, so we don't need to report + // it at the process level. + if (pThread->HasThreadStateNC(Thread::TSNC_AppDomainContainUnhandled)) + return FALSE; + + ThreadPreventAsyncHolder prevAsync; + + GCX_COOP(); + + OBJECTREF throwable; + + if (pThrowableIn != NULL) + { + throwable = *pThrowableIn; + } + else if (useLastThrownObject) + { + throwable = pThread->LastThrownObject(); + } + else + { + throwable = pThread->GetThrowable(); + } + + // If we've got no managed object, then we can't send an event, so we just return. + if (throwable == NULL) + { + return FALSE; + } + +#ifdef _DEBUG + DWORD unbreakableLockCount = 0; + // Do not care about lock check for unhandled exception. + while (pThread->HasUnbreakableLock()) + { + pThread->DecUnbreakableLockCount(); + unbreakableLockCount ++; + } + BOOL fOwnsSpinLock = pThread->HasThreadStateNC(Thread::TSNC_OwnsSpinLock); + if (fOwnsSpinLock) + { + pThread->ResetThreadStateNC(Thread::TSNC_OwnsSpinLock); + } +#endif + + GCPROTECT_BEGIN(throwable); + //BOOL IsStackOverflow = (throwable->GetTrueMethodTable() == g_pStackOverflowExceptionClass); + + // Notify the AppDomain that we have taken an unhandled exception. Can't notify of stack overflow -- guard + // page is not yet reset. + + // Send up the unhandled exception appdomain event. + // + // If we can't run managed code, we can't deliver the event. Nor do we attempt to delieve the event in stack + // overflow or OOM conditions. + if (/*!IsStackOverflow &&*/ + pThread->DetermineIfGuardPagePresent() && + CanRunManagedCode(LoaderLockCheck::None)) + { + + // x86 only +#if !defined(WIN64EXCEPTIONS) + // If the Thread object's exception state's exception pointers + // is null, use the passed-in pointer. + BOOL bSetPointers = FALSE; + + ThreadExceptionState* pExceptionState = pThread->GetExceptionState(); + + if (pExceptionState->GetExceptionPointers() == NULL) + { + bSetPointers = TRUE; + pExceptionState->SetExceptionPointers(pExceptionPointers); + } + +#endif // !defined(WIN64EXCEPTIONS) + + INSTALL_NESTED_EXCEPTION_HANDLER(pThread->GetFrame()); + + // This guy will never throw, but it will need a spot to store + // any nested exceptions it might find. + SentEvent = AppDomain::OnUnhandledException(&throwable, isTerminating); + + UNINSTALL_NESTED_EXCEPTION_HANDLER(); + +#if !defined(WIN64EXCEPTIONS) + + if (bSetPointers) + { + pExceptionState->SetExceptionPointers(NULL); + } + +#endif // !defined(WIN64EXCEPTIONS) + + } + + GCPROTECT_END(); + +#ifdef _DEBUG + // Do not care about lock check for unhandled exception. + while (unbreakableLockCount) + { + pThread->IncUnbreakableLockCount(); + unbreakableLockCount --; + } + if (fOwnsSpinLock) + { + pThread->SetThreadStateNC(Thread::TSNC_OwnsSpinLock); + } +#endif + + return SentEvent; + +} // NotifyAppDomainsOfUnhandledException() + + +//****************************************************************************** +// +// ThreadBaseExceptionFilter_Worker +// +// The return from the function can be EXCEPTION_CONTINUE_SEARCH to let an +// exception go unhandled. This is the default behaviour (starting in v2.0), +// but can be overridden by hosts or by config file. +// When the behaviour is overridden, the return will be EXCEPTION_EXECUTE_HANDLER +// to swallow the exception. +// Note that some exceptions are always swallowed: ThreadAbort, and AppDomainUnload. +// +// Parameters: +// pExceptionInfo EXCEPTION_POINTERS for current exception +// _location A constant as an INT_PTR. Tells the context from whence called. +// swallowing Are we swallowing unhandled exceptions based on policy? +// +// Returns: +// EXCEPTION_CONTINUE_SEARCH Generally returns this to let the exception go unhandled. +// EXCEPTION_EXECUTE_HANDLER May return this to swallow the exception. +// +static LONG ThreadBaseExceptionFilter_Worker(PEXCEPTION_POINTERS pExceptionInfo, + PVOID pvParam, + BOOL swallowing) +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_ANY; + } + CONTRACTL_END; + + LOG((LF_EH, LL_INFO100, "ThreadBaseExceptionFilter_Worker: Enter\n")); + + ThreadBaseExceptionFilterParam *pParam = (ThreadBaseExceptionFilterParam *) pvParam; + UnhandledExceptionLocation location = pParam->location; + + _ASSERTE(!g_fNoExceptions); + + Thread* pThread = GetThread(); + _ASSERTE(pThread); + +#ifdef _DEBUG + if (CLRConfig::GetConfigValue(CLRConfig::INTERNAL_BreakOnUncaughtException) && + !(swallowing && (SwallowUnhandledExceptions() || ExceptionIsAlwaysSwallowed(pExceptionInfo))) && + !(location == ClassInitUnhandledException && pThread->IsRudeAbortInitiated())) + _ASSERTE(!"BreakOnUnCaughtException"); +#endif + + BOOL doDefault = ((location != ClassInitUnhandledException) && + (pExceptionInfo->ExceptionRecord->ExceptionCode != STATUS_BREAKPOINT) && + (pExceptionInfo->ExceptionRecord->ExceptionCode != STATUS_SINGLE_STEP)); + + if (swallowing) + { + // The default handling for versions v1.0 and v1.1 was to swallow unhandled exceptions. + // With v2.0, the default is to let them go unhandled. Hosts & config files can modify the default + // to retain the v1.1 behaviour. + // Should we swallow this exception, or let it continue up and be unhandled? + if (!SwallowUnhandledExceptions()) + { + // No, don't swallow unhandled exceptions... + + // ...except if the exception is of a type that is always swallowed (ThreadAbort, AppDomainUnload)... + if (ExceptionIsAlwaysSwallowed(pExceptionInfo)) + { // ...return EXCEPTION_EXECUTE_HANDLER to swallow the exception anyway. + return EXCEPTION_EXECUTE_HANDLER; + } + + #ifdef _DEBUG + if (CLRConfig::GetConfigValue(CLRConfig::INTERNAL_BreakOnUncaughtException)) + _ASSERTE(!"BreakOnUnCaughtException"); + #endif + + // ...so, continue search. i.e. let the exception go unhandled. + return EXCEPTION_CONTINUE_SEARCH; + } + } + +#ifdef DEBUGGING_SUPPORTED + // If there's a debugger (and not doing a thread abort), give the debugger a shot at the exception. + // If the debugger is going to try to continue the exception, it will return ContinueException (which + // we see here as EXCEPTION_CONTINUE_EXECUTION). + if (!pThread->IsAbortRequested()) + { + // TODO: do we really need this check? I don't think we do + if(CORDebuggerAttached()) + { + if (NotifyDebuggerLastChance(pThread, pExceptionInfo, FALSE) == EXCEPTION_CONTINUE_EXECUTION) + { + LOG((LF_EH, LL_INFO100, "ThreadBaseExceptionFilter_Worker: EXCEPTION_CONTINUE_EXECUTION\n")); + return EXCEPTION_CONTINUE_EXECUTION; + } + } + } +#endif // DEBUGGING_SUPPORTED + + // Do default handling, but ignore breakpoint exceptions and class init exceptions + if (doDefault) + { + LOG((LF_EH, LL_INFO100, "ThreadBaseExceptionFilter_Worker: Calling DefaultCatchHandler\n")); + + BOOL useLastThrownObject = UpdateCurrentThrowable(pExceptionInfo->ExceptionRecord); + + DefaultCatchHandler(pExceptionInfo, + NULL, + useLastThrownObject, + FALSE, + location == ManagedThread || location == ThreadPoolThread || location == FinalizerThread); + } + + // Return EXCEPTION_EXECUTE_HANDLER to swallow the exception. + return (swallowing + ? EXCEPTION_EXECUTE_HANDLER + : EXCEPTION_CONTINUE_SEARCH); +} // LONG ThreadBaseExceptionFilter_Worker() + + +// This is the filter for new managed threads, for threadpool threads, and for +// running finalizer methods. +LONG ThreadBaseExceptionSwallowingFilter(PEXCEPTION_POINTERS pExceptionInfo, PVOID pvParam) +{ + return ThreadBaseExceptionFilter_Worker(pExceptionInfo, pvParam, /*swallowing=*/true); +} + +// This was the filter for new managed threads in v1.0 and v1.1. Now used +// for delegate invoke, various things in the thread pool, and the +// class init handler. +LONG ThreadBaseExceptionFilter(PEXCEPTION_POINTERS pExceptionInfo, PVOID pvParam) +{ + return ThreadBaseExceptionFilter_Worker(pExceptionInfo, pvParam, /*swallowing=*/false); +} + + +// This is the filter that we install when transitioning an AppDomain at the base of a managed +// thread. Nothing interesting will get swallowed after us. So we never decide to continue +// the search. Instead, we let it go unhandled and get the Watson report and debugging +// experience before the AD transition has an opportunity to catch/rethrow and lose all the +// relevant information. +LONG ThreadBaseExceptionAppDomainFilter(EXCEPTION_POINTERS *pExceptionInfo, PVOID pvParam) +{ + LONG ret = ThreadBaseExceptionSwallowingFilter(pExceptionInfo, pvParam); + + if (ret != EXCEPTION_CONTINUE_SEARCH) + return ret; + + // Consider the exception to be unhandled + return InternalUnhandledExceptionFilter_Worker(pExceptionInfo); +} + +// Filter for calls out from the 'vm' to native code, if there's a possibility of SEH exceptions +// in the native code. +LONG CallOutFilter(PEXCEPTION_POINTERS pExceptionInfo, PVOID pv) +{ + CallOutFilterParam *pParam = static_cast<CallOutFilterParam *>(pv); + + _ASSERTE(pParam->OneShot && (pParam->OneShot == TRUE || pParam->OneShot == FALSE)); + + if (pParam->OneShot == TRUE) + { + pParam->OneShot = FALSE; + + // Replace whatever SEH exception is in flight, with an SEHException derived from + // CLRException. But if the exception already looks like one of ours, let it + // go past since LastThrownObject should already represent it. + if ((!IsComPlusException(pExceptionInfo->ExceptionRecord)) && + (pExceptionInfo->ExceptionRecord->ExceptionCode != EXCEPTION_MSVC)) + PAL_CPP_THROW(SEHException *, new SEHException(pExceptionInfo->ExceptionRecord, + pExceptionInfo->ContextRecord)); + } + return EXCEPTION_CONTINUE_SEARCH; +} + + +//========================================================================== +// Convert the format string used by sprintf to the format used by String.Format. +// Using the managed formatting routine avoids bogus access violations +// that happen for long strings in Win32's FormatMessage. +// +// Note: This is not general purpose routine. It handles only cases found +// in TypeLoadException and FileLoadException. +//========================================================================== +static BOOL GetManagedFormatStringForResourceID(CCompRC::ResourceCategory eCategory, UINT32 resId, SString & converted) +{ + STANDARD_VM_CONTRACT; + + StackSString temp; + if (!temp.LoadResource(eCategory, resId)) + return FALSE; + + SString::Iterator itr = temp.Begin(); + while (*itr) + { + WCHAR c = *itr++; + switch (c) { + case '%': + { + WCHAR fmt = *itr++; + if (fmt >= '1' && fmt <= '9') { + converted.Append(W("{")); + converted.Append(fmt - 1); // the managed args start at 0 + converted.Append(W("}")); + } + else + if (fmt == '%') { + converted.Append(W("%")); + } + else { + _ASSERTE(!"Unexpected formating string: %s"); + } + } + break; + case '{': + converted.Append(W("{{")); + break; + case '}': + converted.Append(W("}}")); + break; + default: + converted.Append(c); + break; + } + } + return TRUE; +} + +//========================================================================== +// Private helper for TypeLoadException. +//========================================================================== +void QCALLTYPE GetTypeLoadExceptionMessage(UINT32 resId, QCall::StringHandleOnStack retString) +{ + QCALL_CONTRACT; + + BEGIN_QCALL; + + StackSString format; + GetManagedFormatStringForResourceID(CCompRC::Error, resId ? resId : IDS_CLASSLOAD_GENERAL, format); + retString.Set(format); + + END_QCALL; +} + + + +//========================================================================== +// Private helper for FileLoadException and FileNotFoundException. +//========================================================================== + +void QCALLTYPE GetFileLoadExceptionMessage(UINT32 hr, QCall::StringHandleOnStack retString) +{ + QCALL_CONTRACT; + + BEGIN_QCALL; + + StackSString format; + GetManagedFormatStringForResourceID(CCompRC::Error, GetResourceIDForFileLoadExceptionHR(hr), format); + retString.Set(format); + + END_QCALL; +} + +//========================================================================== +// Private helper for FileLoadException and FileNotFoundException. +//========================================================================== +void QCALLTYPE FileLoadException_GetMessageForHR(UINT32 hresult, QCall::StringHandleOnStack retString) +{ + QCALL_CONTRACT; + + BEGIN_QCALL; + + BOOL bNoGeekStuff = FALSE; + switch ((HRESULT)hresult) + { + // These are not usually app errors - as long + // as the message is reasonably clear, we can live without the hex code stuff. + case COR_E_FILENOTFOUND: + case __HRESULT_FROM_WIN32(ERROR_MOD_NOT_FOUND): + case __HRESULT_FROM_WIN32(ERROR_PATH_NOT_FOUND): + case __HRESULT_FROM_WIN32(ERROR_INVALID_NAME): + case __HRESULT_FROM_WIN32(ERROR_BAD_NET_NAME): + case __HRESULT_FROM_WIN32(ERROR_BAD_NETPATH): + case __HRESULT_FROM_WIN32(ERROR_DLL_NOT_FOUND): + case CTL_E_FILENOTFOUND: + case COR_E_DLLNOTFOUND: + case COR_E_PATHTOOLONG: + case E_ACCESSDENIED: + case COR_E_BADIMAGEFORMAT: + case COR_E_NEWER_RUNTIME: + case COR_E_ASSEMBLYEXPECTED: + bNoGeekStuff = TRUE; + break; + } + + SString s; + GetHRMsg((HRESULT)hresult, s, bNoGeekStuff); + retString.Set(s); + + END_QCALL; +} + + +#define ValidateSigBytes(_size) do { if ((_size) > csig) COMPlusThrow(kArgumentException, W("Argument_BadSigFormat")); csig -= (_size); } while (false) + +//========================================================================== +// Unparses an individual type. +//========================================================================== +const BYTE *UnparseType(const BYTE *pType, DWORD& csig, StubLinker *psl) +{ + CONTRACTL + { + THROWS; + GC_NOTRIGGER; + MODE_ANY; + INJECT_FAULT(ThrowOutOfMemory();); // Emitting data to the StubLinker can throw OOM. + } + CONTRACTL_END; + + LPCUTF8 pName = NULL; + + ValidateSigBytes(sizeof(BYTE)); + switch ( (CorElementType) *(pType++) ) { + case ELEMENT_TYPE_VOID: + psl->EmitUtf8("void"); + break; + + case ELEMENT_TYPE_BOOLEAN: + psl->EmitUtf8("boolean"); + break; + + case ELEMENT_TYPE_CHAR: + psl->EmitUtf8("char"); + break; + + case ELEMENT_TYPE_U1: + psl->EmitUtf8("unsigned "); + //fallthru + case ELEMENT_TYPE_I1: + psl->EmitUtf8("byte"); + break; + + case ELEMENT_TYPE_U2: + psl->EmitUtf8("unsigned "); + //fallthru + case ELEMENT_TYPE_I2: + psl->EmitUtf8("short"); + break; + + case ELEMENT_TYPE_U4: + psl->EmitUtf8("unsigned "); + //fallthru + case ELEMENT_TYPE_I4: + psl->EmitUtf8("int"); + break; + + case ELEMENT_TYPE_I: + psl->EmitUtf8("native int"); + break; + case ELEMENT_TYPE_U: + psl->EmitUtf8("native unsigned"); + break; + + case ELEMENT_TYPE_U8: + psl->EmitUtf8("unsigned "); + //fallthru + case ELEMENT_TYPE_I8: + psl->EmitUtf8("long"); + break; + + + case ELEMENT_TYPE_R4: + psl->EmitUtf8("float"); + break; + + case ELEMENT_TYPE_R8: + psl->EmitUtf8("double"); + break; + + case ELEMENT_TYPE_STRING: + psl->EmitUtf8(g_StringName); + break; + + case ELEMENT_TYPE_VAR: + case ELEMENT_TYPE_OBJECT: + psl->EmitUtf8(g_ObjectName); + break; + + case ELEMENT_TYPE_PTR: + pType = UnparseType(pType, csig, psl); + psl->EmitUtf8("*"); + break; + + case ELEMENT_TYPE_BYREF: + pType = UnparseType(pType, csig, psl); + psl->EmitUtf8("&"); + break; + + case ELEMENT_TYPE_VALUETYPE: + case ELEMENT_TYPE_CLASS: + pName = (LPCUTF8)pType; + while (true) { + ValidateSigBytes(sizeof(CHAR)); + if (*(pType++) == '\0') + break; + } + psl->EmitUtf8(pName); + break; + + case ELEMENT_TYPE_SZARRAY: + { + pType = UnparseType(pType, csig, psl); + psl->EmitUtf8("[]"); + } + break; + + case ELEMENT_TYPE_ARRAY: + { + pType = UnparseType(pType, csig, psl); + ValidateSigBytes(sizeof(DWORD)); + DWORD rank = GET_UNALIGNED_VAL32(pType); + pType += sizeof(DWORD); + if (rank) + { + ValidateSigBytes(sizeof(UINT32)); + UINT32 nsizes = GET_UNALIGNED_VAL32(pType); // Get # of sizes + ValidateSigBytes(nsizes * sizeof(UINT32)); + pType += 4 + nsizes*4; + ValidateSigBytes(sizeof(UINT32)); + UINT32 nlbounds = GET_UNALIGNED_VAL32(pType); // Get # of lower bounds + ValidateSigBytes(nlbounds * sizeof(UINT32)); + pType += 4 + nlbounds*4; + + + while (rank--) { + psl->EmitUtf8("[]"); + } + +} + + } + break; + + case ELEMENT_TYPE_TYPEDBYREF: + psl->EmitUtf8("&"); + break; + + case ELEMENT_TYPE_FNPTR: + psl->EmitUtf8("ftnptr"); + break; + + default: + psl->EmitUtf8("?"); + break; + } + + return pType; + } + + + +//========================================================================== +// Helper for MissingMemberException. +//========================================================================== +static STRINGREF MissingMemberException_FormatSignature_Internal(I1ARRAYREF* ppPersistedSig) +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_COOPERATIVE; + INJECT_FAULT(ThrowOutOfMemory();); + } + CONTRACTL_END; + + STRINGREF pString = NULL; + + DWORD csig = 0; + const BYTE *psig = 0; + StubLinker *psl = NULL; + StubHolder<Stub> pstub; + + if ((*ppPersistedSig) != NULL) + csig = (*ppPersistedSig)->GetNumComponents(); + + if (csig == 0) + { + return StringObject::NewString("Unknown signature"); + } + + psig = (const BYTE*)_alloca(csig); + CopyMemory((BYTE*)psig, + (*ppPersistedSig)->GetDirectPointerToNonObjectElements(), + csig); + + { + GCX_PREEMP(); + + StubLinker sl; + psl = &sl; + pstub = NULL; + + ValidateSigBytes(sizeof(UINT32)); + UINT32 cconv = GET_UNALIGNED_VAL32(psig); + psig += 4; + + if (cconv == IMAGE_CEE_CS_CALLCONV_FIELD) { + psig = UnparseType(psig, csig, psl); + } else { + ValidateSigBytes(sizeof(UINT32)); + UINT32 nargs = GET_UNALIGNED_VAL32(psig); + psig += 4; + + // Unparse return type + psig = UnparseType(psig, csig, psl); + psl->EmitUtf8("("); + while (nargs--) { + psig = UnparseType(psig, csig, psl); + if (nargs) { + psl->EmitUtf8(", "); + } + } + psl->EmitUtf8(")"); + } + psl->Emit8('\0'); + pstub = psl->Link(); + } + + pString = StringObject::NewString( (LPCUTF8)(pstub->GetEntryPoint()) ); + return pString; +} + +FCIMPL1(Object*, MissingMemberException_FormatSignature, I1Array* pPersistedSigUNSAFE) +{ + FCALL_CONTRACT; + + STRINGREF pString = NULL; + I1ARRAYREF pPersistedSig = (I1ARRAYREF) pPersistedSigUNSAFE; + HELPER_METHOD_FRAME_BEGIN_RET_1(pPersistedSig); + + pString = MissingMemberException_FormatSignature_Internal(&pPersistedSig); + + HELPER_METHOD_FRAME_END(); + return OBJECTREFToObject(pString); + } +FCIMPLEND + +// Check if the Win32 Error code is an IO error. +BOOL IsWin32IOError(SCODE scode) +{ + LIMITED_METHOD_CONTRACT; + + switch (scode) + { + case ERROR_FILE_NOT_FOUND: + case ERROR_PATH_NOT_FOUND: + case ERROR_TOO_MANY_OPEN_FILES: + case ERROR_ACCESS_DENIED: + case ERROR_INVALID_HANDLE: + case ERROR_INVALID_DRIVE: + case ERROR_WRITE_PROTECT: + case ERROR_NOT_READY: + case ERROR_WRITE_FAULT: + case ERROR_SHARING_VIOLATION: + case ERROR_LOCK_VIOLATION: + case ERROR_SHARING_BUFFER_EXCEEDED: + case ERROR_HANDLE_DISK_FULL: + case ERROR_BAD_NETPATH: + case ERROR_DEV_NOT_EXIST: + case ERROR_FILE_EXISTS: + case ERROR_CANNOT_MAKE: + case ERROR_NET_WRITE_FAULT: + case ERROR_DRIVE_LOCKED: + case ERROR_OPEN_FAILED: + case ERROR_BUFFER_OVERFLOW: + case ERROR_DISK_FULL: + case ERROR_INVALID_NAME: + case ERROR_FILENAME_EXCED_RANGE: + case ERROR_IO_DEVICE: + case ERROR_DISK_OPERATION_FAILED: + return TRUE; + + default: + return FALSE; + } +} + + +// Check if there is a pending exception or the thread is already aborting. Returns 0 if yes. +// Otherwise, sets the thread up for generating an abort and returns address of ThrowControlForThread +// It is the caller's responsibility to set up Thread::m_OSContext prior to this call. This is used as +// the context for checking if a ThreadAbort is allowed, and also as the context for the ThreadAbortException +// itself. +LPVOID COMPlusCheckForAbort(UINT_PTR uTryCatchResumeAddress) +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + MODE_ANY; + SO_TOLERANT; + } + CONTRACTL_END; + + // Initialize the return address + LPVOID pRetAddress = 0; + + Thread* pThread = GetThread(); + + if ((!pThread->IsAbortRequested()) || // if no abort has been requested + (!pThread->IsRudeAbort() && + (pThread->GetThrowable() != NULL)) ) // or if there is a pending exception + { + goto exit; + } + + // Reverse COM interop IL stubs map all exceptions to HRESULTs and must not propagate Thread.Abort + // to their unmanaged callers. + if (uTryCatchResumeAddress != NULL) + { + MethodDesc * pMDResumeMethod = ExecutionManager::GetCodeMethodDesc((PCODE)uTryCatchResumeAddress); + if (pMDResumeMethod->IsILStub()) + goto exit; + } + + // else we must produce an abort + if ((pThread->GetThrowable() == NULL) && + (pThread->IsAbortInitiated())) + { + // Oops, we just swallowed an abort, must restart the process + pThread->ResetAbortInitiated(); + } + + // Question: Should we also check for (pThread->m_PreventAsync == 0) + +#if !defined(WIN64EXCEPTIONS) && defined(FEATURE_STACK_PROBE) + // On Win64, this function is called by our exception handling code which has probed. + // But on X86, this is called from JIT code directly. We probe here so that + // we can restore the state of the thread below. + if (GetEEPolicy()->GetActionOnFailure(FAIL_StackOverflow) == eRudeUnloadAppDomain) + { + // In case of SO, we will skip the managed code. + CONTRACT_VIOLATION(ThrowsViolation); + RetailStackProbe(ADJUST_PROBE(DEFAULT_ENTRY_PROBE_AMOUNT), pThread); + } +#endif // !WIN64EXCEPTIONS && FEATURE_STACK_PROBE + + pThread->SetThrowControlForThread(Thread::InducedThreadRedirectAtEndOfCatch); + if (!pThread->ReadyForAbort()) + { + pThread->ResetThrowControlForThread(); + goto exit; + } + pThread->SetThrowControlForThread(Thread::InducedThreadStop); + + pRetAddress = (LPVOID)THROW_CONTROL_FOR_THREAD_FUNCTION; + +exit: + +#ifndef FEATURE_PAL + +#ifdef FEATURE_CORECLR + // Only proceed if Watson is enabled - CoreCLR may have it disabled. + if (IsWatsonEnabled()) +#endif // FEATURE_CORECLR + { + BOOL fClearUEWatsonBucketTracker = TRUE; + PTR_EHWatsonBucketTracker pUEWatsonBucketTracker = pThread->GetExceptionState()->GetUEWatsonBucketTracker(); + + if (pRetAddress && pThread->IsAbortRequested()) + { + // Since we are going to reraise the thread abort exception, we would like to assert that + // the buckets present in the UE tracker are the ones which were setup TAE was first raised. + // + // However, these buckets could come from across AD transition as well and thus, would be + // marked for "Captured at AD transition". Thus, we cannot just assert them to be only from + // TAE raise. + // + // We try to preserve buckets incase there is another catch that may catch the exception we reraise + // and it attempts to FailFast using the TA exception object. In such a case, + // we should maintain the original exception point's bucket details. + if (pUEWatsonBucketTracker->RetrieveWatsonBucketIp() != NULL) + { + _ASSERTE(pUEWatsonBucketTracker->CapturedForThreadAbort() || pUEWatsonBucketTracker->CapturedAtADTransition()); + fClearUEWatsonBucketTracker = FALSE; + } +#ifdef _DEBUG + else + { + // If we are here and UE Watson bucket tracker is empty, + // then it is possible that a thread abort was signalled when the catch was executing + // and thus, hijack for TA from here is not a reraise but an initial raise. + // + // However, if we have partial details, then something is really not right. + if (!((pUEWatsonBucketTracker->RetrieveWatsonBucketIp() == NULL) && + (pUEWatsonBucketTracker->RetrieveWatsonBuckets() == NULL))) + { + _ASSERTE(!"How come TA is being [re]raised and we have incomplete watson bucket details?"); + } + } +#endif // _DEBUG + } + + if (fClearUEWatsonBucketTracker) + { + // Clear the UE watson bucket tracker for future use since it does not have anything + // useful for us right now. + pUEWatsonBucketTracker->ClearWatsonBucketDetails(); + LOG((LF_EH, LL_INFO100, "COMPlusCheckForAbort - Cleared UE watson bucket tracker since TAE was not being reraised.\n")); + } + } + +#endif // !FEATURE_PAL + + return pRetAddress; +} + + +BOOL IsThreadHijackedForThreadStop(Thread* pThread, EXCEPTION_RECORD* pExceptionRecord) +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + MODE_ANY; + FORBID_FAULT; + SO_TOLERANT; + } + CONTRACTL_END; + + if (IsComPlusException(pExceptionRecord)) + { + if (pThread->ThrewControlForThread() == Thread::InducedThreadStop) + { + LOG((LF_EH, LL_INFO100, "Asynchronous Thread Stop or Abort\n")); + return TRUE; + } + } + else if (IsStackOverflowException(pThread, pExceptionRecord)) + { + // SO happens before we are able to change the state to InducedThreadStop, but + // we are still in our hijack routine. + if (pThread->ThrewControlForThread() == Thread::InducedThreadRedirect) + { + LOG((LF_EH, LL_INFO100, "Asynchronous Thread Stop or Abort caused by SO\n")); + return TRUE; + } + } + return FALSE; +} + +// We sometimes move a thread's execution so it will throw an exception for us. +// But then we have to treat the exception as if it came from the instruction +// the thread was originally running. +// +// NOTE: This code depends on the fact that there are no register-based data dependencies +// between a try block and a catch, fault, or finally block. If there were, then we need +// to preserve more of the register context. + +void AdjustContextForThreadStop(Thread* pThread, + CONTEXT* pContext) +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + MODE_ANY; + FORBID_FAULT; + SO_TOLERANT; + } + CONTRACTL_END; + + _ASSERTE(pThread->m_OSContext); + +#ifndef WIN64EXCEPTIONS + SetIP(pContext, GetIP(pThread->m_OSContext)); + SetSP(pContext, (GetSP(pThread->m_OSContext))); + + if (GetFP(pThread->m_OSContext) != 0) // ebp = 0 implies that we got here with the right values for ebp + { + SetFP(pContext, GetFP(pThread->m_OSContext)); + } + + // We might have been interrupted execution at a point where the jit has roots in + // registers. We just need to store a "safe" value in here so that the collector + // doesn't trap. We're not going to use these objects after the exception. + // + // Only callee saved registers are going to be reported by the faulting excepiton frame. +#if defined(_TARGET_X86_) + // Ebx,esi,edi are important. Eax,ecx,edx are not. + pContext->Ebx = 0; + pContext->Edi = 0; + pContext->Esi = 0; +#else + PORTABILITY_ASSERT("AdjustContextForThreadStop"); +#endif + +#else // !WIN64EXCEPTIONS + CopyOSContext(pContext, pThread->m_OSContext); +#if defined(_TARGET_ARM_) && defined(_DEBUG) + // Make sure that the thumb bit is set on the IP of the original abort context we just restored. + PCODE controlPC = GetIP(pContext); + _ASSERTE(controlPC & THUMB_CODE); +#endif // _TARGET_ARM_ +#endif // !WIN64EXCEPTIONS + + pThread->ResetThrowControlForThread(); + + // Should never get here if we're already throwing an exception. + _ASSERTE(!pThread->IsExceptionInProgress() || pThread->IsRudeAbort()); + + // Should never get here if we're already abort initiated. + _ASSERTE(!pThread->IsAbortInitiated() || pThread->IsRudeAbort()); + + if (pThread->IsAbortRequested()) + { + pThread->SetAbortInitiated(); // to prevent duplicate aborts + } +} + +// Create a COM+ exception , stick it in the thread. +OBJECTREF +CreateCOMPlusExceptionObject(Thread *pThread, EXCEPTION_RECORD *pExceptionRecord, BOOL bAsynchronousThreadStop) +{ + CONTRACTL + { + NOTHROW; + GC_TRIGGERS; + MODE_COOPERATIVE; + FORBID_FAULT; + SO_TOLERANT; + } + CONTRACTL_END; + + _ASSERTE(GetThread() == pThread); + + DWORD exceptionCode = pExceptionRecord->ExceptionCode; + + OBJECTREF result = 0; + + DWORD COMPlusExceptionCode = (bAsynchronousThreadStop + ? kThreadAbortException + : MapWin32FaultToCOMPlusException(pExceptionRecord)); + + if (exceptionCode == STATUS_NO_MEMORY) + { + result = CLRException::GetBestOutOfMemoryException(); + } + else if (IsStackOverflowException(pThread, pExceptionRecord)) + { + result = CLRException::GetPreallocatedStackOverflowException(); + } + else if (bAsynchronousThreadStop && pThread->IsAbortRequested() && pThread->IsRudeAbort()) + { + result = CLRException::GetPreallocatedRudeThreadAbortException(); + } + else + { + EX_TRY + { + // We need to disable the backout stack validation at this point since CreateThrowable can + // take arbitrarily large amounts of stack for different exception types; however we know + // for a fact that we will never go through this code path if the exception is a stack + // overflow exception since we already handled that case above with the pre-allocated SO exception. + DISABLE_BACKOUT_STACK_VALIDATION; + + FAULT_NOT_FATAL(); + + ThreadPreventAsyncHolder preventAsync; + ResetProcessorStateHolder procState; + + INSTALL_UNWIND_AND_CONTINUE_HANDLER; + + GCPROTECT_BEGIN(result) + + EEException e((RuntimeExceptionKind)COMPlusExceptionCode); + result = e.CreateThrowable(); + + // EEException is "one size fits all". But AV needs some more information. + if (COMPlusExceptionCode == kAccessViolationException) + { + SetExceptionAVParameters(result, pExceptionRecord); + } + + GCPROTECT_END(); + + UNINSTALL_UNWIND_AND_CONTINUE_HANDLER; + } + EX_CATCH + { + // If we get an exception trying to build the managed exception object, then go ahead and return the + // thrown object as the result of this function. This is preferable to letting the exception try to + // percolate up through the EH code, and it effectively replaces the thrown exception with this + // exception. + result = GET_THROWABLE(); + } + EX_END_CATCH(SwallowAllExceptions); + } + + return result; +} + +LONG FilterAccessViolation(PEXCEPTION_POINTERS pExceptionPointers, LPVOID lpvParam) +{ + CONTRACTL + { + THROWS; + GC_NOTRIGGER; + MODE_ANY; + FORBID_FAULT; + } + CONTRACTL_END; + + if (pExceptionPointers->ExceptionRecord->ExceptionCode == EXCEPTION_ACCESS_VIOLATION) + return EXCEPTION_EXECUTE_HANDLER; + + return EXCEPTION_CONTINUE_SEARCH; +} + +/* + * IsContinuableException + * + * Returns whether this is an exception the EE knows how to intercept and continue from. + * + * Parameters: + * pThread - The thread the exception occurred on. + * + * Returns: + * TRUE if the exception on the thread is interceptable or not. + * + * Notes: + * Conditions for an interceptable exception: + * 1) must be on a managed thread + * 2) an exception must be in progress + * 3) a managed exception object must have been created + * 4) the thread must not be aborting + * 5) the exception must not be a breakpoint, a single step, or a stack overflow + * 6) the exception dispatch must be in the first pass + * 7) the exception must not be a fatal error, as determined by the EE policy (see LogFatalError()) + */ +bool IsInterceptableException(Thread *pThread) +{ + CONTRACTL + { + MODE_ANY; + NOTHROW; + GC_NOTRIGGER; + } + CONTRACTL_END; + + return ((pThread != NULL) && + (!pThread->IsAbortRequested()) && + (pThread->IsExceptionInProgress()) && + (!pThread->IsThrowableNull()) + +#ifdef DEBUGGING_SUPPORTED + && + pThread->GetExceptionState()->IsDebuggerInterceptable() +#endif + + ); +} + +// Determines whether we hit an DO_A_GC_HERE marker in JITted code, and returns the +// appropriate exception code, or zero if the code is not a GC marker. +DWORD GetGcMarkerExceptionCode(LPVOID ip) +{ +#if defined(HAVE_GCCOVER) && defined(FEATURE_CORECLR) + WRAPPER_NO_CONTRACT; + + if (GCStress<cfg_any>::IsEnabled() && IsGcCoverageInterrupt(ip)) + { + return STATUS_CLR_GCCOVER_CODE; + } +#else // !(defined(HAVE_GCCOVER) && defined(FEATURE_CORECLR)) + LIMITED_METHOD_CONTRACT; +#endif // defined(HAVE_GCCOVER) && defined(FEATURE_CORECLR) + return 0; +} + +// Did we hit an DO_A_GC_HERE marker in JITted code? +bool IsGcMarker(DWORD exceptionCode, CONTEXT *pContext) +{ +#ifdef HAVE_GCCOVER + WRAPPER_NO_CONTRACT; + + if (GCStress<cfg_any>::IsEnabled()) + { +#ifdef _TARGET_X86_ + // on x86 we can't suspend EE to update the GC marker instruction so + // we update it directly without suspending. this can sometimes yield + // a STATUS_ACCESS_VIOLATION instead of STATUS_CLR_GCCOVER_CODE. in + // this case we let the AV through and retry the instruction. we'll + // track the IP of the instruction that generated an AV so we don't + // mix up a real AV with a "fake" AV. + // see comments in function DoGcStress for more details on this race. + // also make sure that the thread is actually in managed code since AVs + // outside of of JIT code will never be potential GC markers + Thread* pThread = GetThread(); + if (exceptionCode == STATUS_ACCESS_VIOLATION && + GCStress<cfg_instr>::IsEnabled() && + pThread->GetLastAVAddress() != (LPVOID)GetIP(pContext) && + pThread->PreemptiveGCDisabled() && + !IsIPInEE((LPVOID)GetIP(pContext))) + { + pThread->SetLastAVAddress((LPVOID)GetIP(pContext)); + return true; + } +#endif // _TARGET_X86_ + + if (exceptionCode == STATUS_CLR_GCCOVER_CODE) + { + if (OnGcCoverageInterrupt(pContext)) + { + return true; + } + + { + // ExecutionManager::IsManagedCode takes a spinlock. Since this is in a debug-only + // check, we'll allow the lock. + CONTRACT_VIOLATION(TakesLockViolation); + + // Should never be in managed code. + CONSISTENCY_CHECK_MSG(!ExecutionManager::IsManagedCode(GetIP(pContext)), "hit privileged instruction!"); + } + } + } +#else + LIMITED_METHOD_CONTRACT; +#endif // HAVE_GCCOVER + return false; +} + +// Return true if the access violation is well formed (has two info parameters +// at the end) +static inline BOOL +IsWellFormedAV(EXCEPTION_RECORD *pExceptionRecord) +{ + LIMITED_METHOD_CONTRACT; + + #define NUM_AV_PARAMS 2 + + if (pExceptionRecord->NumberParameters == NUM_AV_PARAMS) + { + return TRUE; + } + else + { + return FALSE; + } +} + +static inline BOOL +IsDebuggerFault(EXCEPTION_RECORD *pExceptionRecord, + CONTEXT *pContext, + DWORD exceptionCode, + Thread *pThread) +{ + LIMITED_METHOD_CONTRACT; + +#ifdef DEBUGGING_SUPPORTED + SO_NOT_MAINLINE_FUNCTION; + +#ifdef _TARGET_ARM_ + // On ARM we don't have any reliable hardware support for single stepping so it is emulated in software. + // The implementation will end up throwing an EXCEPTION_BREAKPOINT rather than an EXCEPTION_SINGLE_STEP + // and leaves other aspects of the thread context in an invalid state. Therefore we use this opportunity + // to fixup the state before any other part of the system uses it (we do it here since only the debugger + // uses single step functionality). + + // First ask the emulation itself whether this exception occurred while single stepping was enabled. If so + // it will fix up the context to be consistent again and return true. If so and the exception was + // EXCEPTION_BREAKPOINT then we translate it to EXCEPTION_SINGLE_STEP (otherwise we leave it be, e.g. the + // instruction stepped caused an access violation). since this is called from our VEH there might not + // be a thread object so we must check pThread first. + if ((pThread != NULL) && pThread->HandleSingleStep(pContext, exceptionCode) && (exceptionCode == EXCEPTION_BREAKPOINT)) + { + exceptionCode = EXCEPTION_SINGLE_STEP; + pExceptionRecord->ExceptionCode = EXCEPTION_SINGLE_STEP; + pExceptionRecord->ExceptionAddress = (PVOID)pContext->Pc; + } +#endif // _TARGET_ARM_ + + // Is this exception really meant for the COM+ Debugger? Note: we will let the debugger have a chance if there + // is a debugger attached to any part of the process. It is incorrect to consider whether or not the debugger + // is attached the the thread's current app domain at this point. + + // Even if a debugger is not attached, we must let the debugger handle the exception in case it's coming from a + // patch-skipper. + if ((!IsComPlusException(pExceptionRecord)) && + (GetThread() != NULL) && + (g_pDebugInterface != NULL) && + g_pDebugInterface->FirstChanceNativeException(pExceptionRecord, + pContext, + exceptionCode, + pThread)) + { + LOG((LF_EH | LF_CORDB, LL_INFO1000, "IsDebuggerFault - it's the debugger's fault\n")); + return true; + } +#endif // DEBUGGING_SUPPORTED + return false; +} + +#ifdef WIN64EXCEPTIONS + +EXTERN_C void JIT_MemSet_End(); +EXTERN_C void JIT_MemCpy_End(); + +EXTERN_C void JIT_WriteBarrier_End(); +EXTERN_C void JIT_CheckedWriteBarrier_End(); + +#if defined(_TARGET_AMD64_) && defined(_DEBUG) +EXTERN_C void JIT_WriteBarrier_Debug(); +EXTERN_C void JIT_WriteBarrier_Debug_End(); +#endif + +#ifdef _TARGET_ARM_ +EXTERN_C void FCallMemcpy_End(); +#endif + +// Check if the passed in instruction pointer is in one of the +// JIT helper functions. +bool IsIPInMarkedJitHelper(UINT_PTR uControlPc) +{ + LIMITED_METHOD_CONTRACT; + +#define CHECK_RANGE(name) \ + if (GetEEFuncEntryPoint(name) <= uControlPc && uControlPc < GetEEFuncEntryPoint(name##_End)) return true; + + CHECK_RANGE(JIT_MemSet) + CHECK_RANGE(JIT_MemCpy) + + CHECK_RANGE(JIT_WriteBarrier) + CHECK_RANGE(JIT_CheckedWriteBarrier) + +#if defined(_TARGET_AMD64_) && defined(_DEBUG) + CHECK_RANGE(JIT_WriteBarrier_Debug) +#endif + +#ifdef _TARGET_ARM_ + CHECK_RANGE(FCallMemcpy) +#endif + + return false; +} +#endif // WIN64EXCEPTIONS + +// Returns TRUE if caller should resume execution. +BOOL +AdjustContextForWriteBarrier( + EXCEPTION_RECORD *pExceptionRecord, + CONTEXT *pContext) +{ + WRAPPER_NO_CONTRACT; + +#ifdef _TARGET_X86_ + + void* f_IP = (void *)GetIP(pContext); + + if (f_IP >= (void *) JIT_WriteBarrierStart && f_IP <= (void *) JIT_WriteBarrierLast || + f_IP >= (void *) JIT_PatchedWriteBarrierStart && f_IP <= (void *) JIT_PatchedWriteBarrierLast) + { + // set the exception IP to be the instruction that called the write barrier + void* callsite = (void *)GetAdjustedCallAddress(*dac_cast<PTR_PCODE>(GetSP(pContext))); + pExceptionRecord->ExceptionAddress = callsite; + SetIP(pContext, (PCODE)callsite); + + // put ESP back to what it was before the call. + SetSP(pContext, PCODE((BYTE*)GetSP(pContext) + sizeof(void*))); + } + + return FALSE; + +#elif defined(WIN64EXCEPTIONS) + + void* f_IP = dac_cast<PTR_VOID>(GetIP(pContext)); + + CONTEXT tempContext; + CONTEXT* pExceptionContext = pContext; + + BOOL fExcluded = IsIPInMarkedJitHelper((UINT_PTR)f_IP); + + if (fExcluded) + { + bool fShouldHandleManagedFault = false; + + if (pContext != &tempContext) + { + tempContext = *pContext; + pContext = &tempContext; + } + + Thread::VirtualUnwindToFirstManagedCallFrame(pContext); + +#if defined(_TARGET_ARM_) || defined(_TARGET_ARM64_) + // We had an AV in the writebarrier that needs to be treated + // as originating in managed code. At this point, the stack (growing + // from left->right) looks like this: + // + // ManagedFunc -> Native_WriteBarrierInVM -> AV + // + // We just performed an unwind from the write-barrier + // and now have the context in ManagedFunc. Since + // ManagedFunc called into the write-barrier, the return + // address in the unwound context corresponds to the + // instruction where the call will return. + // + // On ARM, just like we perform ControlPC adjustment + // during exception dispatch (refer to ExceptionTracker::InitializeCrawlFrame), + // we will need to perform the corresponding adjustment of IP + // we got from unwind above, so as to indicate that the AV + // happened "before" the call to the writebarrier and not at + // the instruction at which the control will return. + PCODE ControlPCPostAdjustment = GetIP(pContext) - STACKWALK_CONTROLPC_ADJUST_OFFSET; + + // Now we save the address back into the context so that it gets used + // as the faulting address. + SetIP(pContext, ControlPCPostAdjustment); +#endif // _TARGET_ARM_ + + // Unwind the frame chain - On Win64, this is required since we may handle the managed fault and to do so, + // we will replace the exception context with the managed context and "continue execution" there. Thus, we do not + // want any explicit frames active below the resumption SP. + // + // Question: Why do we unwind before determining whether we will handle the exception or not? + UnwindFrameChain(GetThread(), (Frame*)GetSP(pContext)); + fShouldHandleManagedFault = ShouldHandleManagedFault(pExceptionRecord,pContext, + NULL, // establisher frame (x86 only) + NULL // pThread (x86 only) + ); + + if (fShouldHandleManagedFault) + { + ReplaceExceptionContextRecord(pExceptionContext, pContext); + pExceptionRecord->ExceptionAddress = dac_cast<PTR_VOID>(GetIP(pContext)); + return TRUE; + } + } + + return FALSE; + +#else // ! _X86_ && !WIN64EXCEPTIONS + + PORTABILITY_WARNING("AdjustContextForWriteBarrier() not implemented on this platform"); + return FALSE; + +#endif +} + +struct SavedExceptionInfo +{ + EXCEPTION_RECORD m_ExceptionRecord; + CONTEXT m_ExceptionContext; + CrstStatic m_Crst; + + void SaveExceptionRecord(EXCEPTION_RECORD *pExceptionRecord) + { + LIMITED_METHOD_CONTRACT; + size_t erSize = offsetof(EXCEPTION_RECORD, ExceptionInformation) + + pExceptionRecord->NumberParameters * sizeof(pExceptionRecord->ExceptionInformation[0]); + memcpy(&m_ExceptionRecord, pExceptionRecord, erSize); + + } + + void SaveContext(CONTEXT *pContext) + { + LIMITED_METHOD_CONTRACT; +#ifdef CONTEXT_EXTENDED_REGISTERS + + size_t contextSize = offsetof(CONTEXT, ExtendedRegisters); + if ((pContext->ContextFlags & CONTEXT_EXTENDED_REGISTERS) == CONTEXT_EXTENDED_REGISTERS) + contextSize += sizeof(pContext->ExtendedRegisters); + memcpy(&m_ExceptionContext, pContext, contextSize); + +#else // !CONTEXT_EXTENDED_REGISTERS + + size_t contextSize = sizeof(CONTEXT); + memcpy(&m_ExceptionContext, pContext, contextSize); + +#endif // !CONTEXT_EXTENDED_REGISTERS + } + + DEBUG_NOINLINE void Enter() + { + WRAPPER_NO_CONTRACT; + ANNOTATION_SPECIAL_HOLDER_CALLER_NEEDS_DYNAMIC_CONTRACT; + m_Crst.Enter(); + } + + DEBUG_NOINLINE void Leave() + { + WRAPPER_NO_CONTRACT; + ANNOTATION_SPECIAL_HOLDER_CALLER_NEEDS_DYNAMIC_CONTRACT; + m_Crst.Leave(); + } + + void Init() + { + WRAPPER_NO_CONTRACT; + m_Crst.Init(CrstSavedExceptionInfo, CRST_UNSAFE_ANYMODE); + } +}; + + +#if defined(USE_FEF) + +SavedExceptionInfo g_SavedExceptionInfo; // Globals are guaranteed zero-init; + +void InitSavedExceptionInfo() +{ + g_SavedExceptionInfo.Init(); +} + +EXTERN_C VOID FixContextForFaultingExceptionFrame ( + EXCEPTION_RECORD* pExceptionRecord, + CONTEXT *pContextRecord) +{ + WRAPPER_NO_CONTRACT; + + // don't copy parm args as have already supplied them on the throw + memcpy((void*) pExceptionRecord, + (void*) &g_SavedExceptionInfo.m_ExceptionRecord, + offsetof(EXCEPTION_RECORD, ExceptionInformation) + ); + + ReplaceExceptionContextRecord(pContextRecord, &g_SavedExceptionInfo.m_ExceptionContext); + + g_SavedExceptionInfo.Leave(); + + GetThread()->ResetThreadStateNC(Thread::TSNC_DebuggerIsManagedException); +} + +EXTERN_C VOID __fastcall +LinkFrameAndThrow(FaultingExceptionFrame* pFrame) +{ + WRAPPER_NO_CONTRACT; + + *(TADDR*)pFrame = FaultingExceptionFrame::GetMethodFrameVPtr(); + *pFrame->GetGSCookiePtr() = GetProcessGSCookie(); + + pFrame->InitAndLink(&g_SavedExceptionInfo.m_ExceptionContext); + + GetThread()->SetThreadStateNC(Thread::TSNC_DebuggerIsManagedException); + + ULONG argcount = g_SavedExceptionInfo.m_ExceptionRecord.NumberParameters; + ULONG flags = g_SavedExceptionInfo.m_ExceptionRecord.ExceptionFlags; + ULONG code = g_SavedExceptionInfo.m_ExceptionRecord.ExceptionCode; + ULONG_PTR* args = &g_SavedExceptionInfo.m_ExceptionRecord.ExceptionInformation[0]; + + RaiseException(code, flags, argcount, args); +} + +void SetNakedThrowHelperArgRegistersInContext(CONTEXT* pContext) +{ +#if defined(_TARGET_AMD64_) + pContext->Rcx = (UINT_PTR)GetIP(pContext); +#elif defined(_TARGET_ARM_) || defined(_TARGET_ARM64_) + // Save the original IP in LR + pContext->Lr = (DWORD)GetIP(pContext); +#else + PORTABILITY_WARNING("NakedThrowHelper argument not defined"); +#endif +} + +EXTERN_C VOID STDCALL NakedThrowHelper(VOID); + +void HandleManagedFault(EXCEPTION_RECORD* pExceptionRecord, + CONTEXT* pContext, + EXCEPTION_REGISTRATION_RECORD* pEstablisherFrame, + Thread* pThread) +{ + WRAPPER_NO_CONTRACT; + + // Ok. Now we have a brand new fault in jitted code. + g_SavedExceptionInfo.Enter(); + g_SavedExceptionInfo.SaveExceptionRecord(pExceptionRecord); + g_SavedExceptionInfo.SaveContext(pContext); + + SetNakedThrowHelperArgRegistersInContext(pContext); + + SetIP(pContext, GetEEFuncEntryPoint(NakedThrowHelper)); +} + +#else // USE_FEF + +void InitSavedExceptionInfo() +{ +} + +#endif // USE_FEF + +// +// Init a new frame +// +void FaultingExceptionFrame::Init(CONTEXT *pContext) +{ + WRAPPER_NO_CONTRACT; +#if defined(_TARGET_X86_) + CalleeSavedRegisters *pRegs = GetCalleeSavedRegisters(); + pRegs->ebp = pContext->Ebp; + pRegs->ebx = pContext->Ebx; + pRegs->esi = pContext->Esi; + pRegs->edi = pContext->Edi; + m_ReturnAddress = ::GetIP(pContext); + m_Esp = (DWORD)GetSP(pContext); +#elif defined(WIN64EXCEPTIONS) + m_ReturnAddress = ::GetIP(pContext); + CopyOSContext(&m_ctx, pContext); +#else + PORTABILITY_ASSERT("FaultingExceptionFrame::InitAndLink"); +#endif +} + +// +// Init and Link in a new frame +// +void FaultingExceptionFrame::InitAndLink(CONTEXT *pContext) +{ + WRAPPER_NO_CONTRACT; + + Init(pContext); + + Push(); +} + + +bool ShouldHandleManagedFault( + EXCEPTION_RECORD* pExceptionRecord, + CONTEXT* pContext, + EXCEPTION_REGISTRATION_RECORD* pEstablisherFrame, + Thread* pThread) +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + SO_TOLERANT; + MODE_ANY; + } + CONTRACTL_END; + + // If we get a faulting instruction inside managed code, we're going to + // 1. Allocate the correct exception object, store it in the thread. + // 2. Save the EIP in the thread. + // 3. Change the EIP to our throw helper + // 4. Resume execution. + // + // The helper will push a frame for us, and then throw the correct managed exception. + // + // Is this exception really meant for the COM+ Debugger? Note: we will let the debugger have a chance if there is a + // debugger attached to any part of the process. It is incorrect to consider whether or not the debugger is attached + // the the thread's current app domain at this point. + + + // A managed exception never comes from managed code, and we can ignore all breakpoint + // exceptions. + // + DWORD exceptionCode = pExceptionRecord->ExceptionCode; + if (IsComPlusException(pExceptionRecord) + || exceptionCode == STATUS_BREAKPOINT + || exceptionCode == STATUS_SINGLE_STEP) + { + return false; + } + +#ifdef _DEBUG + // This is a workaround, but it's debug-only as is gc stress 4. + // The problem is that if we get an exception with this code that + // didn't come from GCStress=4, then we won't push a FeF and will + // end up with a gc hole and potential crash. + if (exceptionCode == STATUS_CLR_GCCOVER_CODE) + return false; +#endif // _DEBUG + +#ifndef WIN64EXCEPTIONS + // If there's any frame below the ESP of the exception, then we can forget it. + if (pThread->m_pFrame < dac_cast<PTR_VOID>(GetSP(pContext))) + return false; + + // If we're a subsequent handler forget it. + EXCEPTION_REGISTRATION_RECORD* pBottomMostHandler = pThread->GetExceptionState()->m_currentExInfo.m_pBottomMostHandler; + if (pBottomMostHandler != NULL && pEstablisherFrame > pBottomMostHandler) + { + return false; + } +#endif // WIN64EXCEPTIONS + + { + // If it's not a fault in jitted code, forget it. + + // ExecutionManager::IsManagedCode takes a spinlock. Since we're in the middle of throwing, + // we'll allow the lock, even if a caller didn't expect it. + CONTRACT_VIOLATION(TakesLockViolation); + + if (!ExecutionManager::IsManagedCode(GetIP(pContext))) + return false; + } + + // caller should call HandleManagedFault and resume execution. + return true; +} + +LONG WINAPI CLRVectoredExceptionHandlerPhase2(PEXCEPTION_POINTERS pExceptionInfo); + +enum VEH_ACTION +{ + VEH_NO_ACTION = 0, + VEH_EXECUTE_HANDLE_MANAGED_EXCEPTION, + VEH_CONTINUE_EXECUTION, + VEH_CONTINUE_SEARCH, + VEH_EXECUTE_HANDLER +}; + + +VEH_ACTION WINAPI CLRVectoredExceptionHandlerPhase3(PEXCEPTION_POINTERS pExceptionInfo); + +LONG WINAPI CLRVectoredExceptionHandler(PEXCEPTION_POINTERS pExceptionInfo) +{ + // It is not safe to execute code inside VM after we shutdown EE. One example is DisablePreemptiveGC + // will block forever. + if (g_fForbidEnterEE) + { + return EXCEPTION_CONTINUE_SEARCH; + } + + // + // For images ngen'd with FEATURE_LAZY_COW_PAGES, the .data section will be read-only. Any writes to that data need to be + // preceded by a call to EnsureWritablePages. This code is here to catch the ones we forget. + // +#ifdef FEATURE_LAZY_COW_PAGES + if (pExceptionInfo->ExceptionRecord->ExceptionCode == STATUS_ACCESS_VIOLATION && + IsWellFormedAV(pExceptionInfo->ExceptionRecord) && + pExceptionInfo->ExceptionRecord->ExceptionInformation[0] == 1 /* means this was a failed write */) + { + void* location = (void*)pExceptionInfo->ExceptionRecord->ExceptionInformation[1]; + + if (IsInReadOnlyLazyCOWPage(location)) + { +#ifdef _DEBUG + if (CLRConfig::GetConfigValue(CLRConfig::INTERNAL_DebugAssertOnMissedCOWPage)) + _ASSERTE_MSG(false, "Writes to NGen'd data must be protected by EnsureWritablePages."); +#endif + +#pragma push_macro("VirtualQuery") +#undef VirtualQuery + MEMORY_BASIC_INFORMATION mbi; + if (!::VirtualQuery(location, &mbi, sizeof(mbi))) + { + EEPOLICY_HANDLE_FATAL_ERROR(COR_E_OUTOFMEMORY); + } +#pragma pop_macro("VirtualQuery") + + bool executable = (mbi.Protect == PAGE_EXECUTE_READ) || + (mbi.Protect == PAGE_EXECUTE_READWRITE) || + (mbi.Protect == PAGE_EXECUTE_READ) || + (mbi.Protect == PAGE_EXECUTE_WRITECOPY); + + if (!(executable ? EnsureWritableExecutablePagesNoThrow(location, 1) : EnsureWritablePagesNoThrow(location, 1))) + { + // Note that this failfast is very rare. It will only be hit in the theoretical cases there is + // missing EnsureWritablePages probe (there should be none when we ship), and the OS run into OOM + // exactly at the point when we executed the code with the missing probe. + EEPOLICY_HANDLE_FATAL_ERROR(COR_E_OUTOFMEMORY); + } + + return EXCEPTION_CONTINUE_EXECUTION; + } + } +#endif //FEATURE_LAZY_COW_PAGES + + + // + // DO NOT USE CONTRACTS HERE AS THIS ROUTINE MAY NEVER RETURN. You can use + // static contracts, but currently this is all WRAPPER_NO_CONTRACT. + // + + + // + // READ THIS! + // + // + // You cannot put any code in here that allocates during an out-of-memory handling. + // This routine runs before *any* other handlers, including __try. Thus, if you + // allocate anything in this routine then it will throw out-of-memory and end up + // right back here. + // + // There are various things that allocate that you may not expect to allocate. One + // instance of this is STRESS_LOG. It allocates the log buffer if the thread does + // not already have one allocated. Thus, if we OOM during the setting up of the + // thread, the log buffer will not be allocated and this will try to do so. Thus, + // all STRESS_LOGs in here need to be after you have guaranteed the allocation has + // already occurred. + // + + Thread *pThread; + + { + MAYBE_FAULT_FORBID_NO_ALLOC((pExceptionInfo->ExceptionRecord->ExceptionCode == STATUS_NO_MEMORY)); + + pThread = GetThread(); + + // + // Since we are in an OOM situation, we test the thread object before logging since if the + // thread exists we know the log buffer has been allocated already. + // + if (pThread != NULL) + { + CantAllocHolder caHolder; + STRESS_LOG4(LF_EH, LL_INFO100, "In CLRVectoredExceptionHandler, Exception = %x, Context = %p, IP = %p SP = %p\n", + pExceptionInfo->ExceptionRecord->ExceptionCode, pExceptionInfo->ContextRecord, + GetIP(pExceptionInfo->ContextRecord), GetSP(pExceptionInfo->ContextRecord)); + } + + } + + // We need to unhijack the thread here if it is not unhijacked already. On x86 systems, + // we do this in Thread::StackWalkFramesEx, but on amd64 systems we have the OS walk the + // stack for us. If we leave CLRVectoredExceptionHandler with a thread still hijacked, + // the operating system will not be able to walk the stack and not find the handlers for + // the exception. It is safe to unhijack the thread in this case for two reasons: + // 1. pThread refers to *this* thread. + // 2. If another thread tries to hijack this thread, it will see we are not in managed + // code (and thus won't try to hijack us). +#if defined(WIN64EXCEPTIONS) && defined(FEATURE_HIJACK) + if (pThread != NULL) + { + pThread->UnhijackThreadNoAlloc(); + } +#endif // defined(WIN64EXCEPTIONS) && defined(FEATURE_HIJACK) + +#ifndef FEATURE_PAL + if (IsSOExceptionCode(pExceptionInfo->ExceptionRecord->ExceptionCode)) + { + // + // Not an Out-of-memory situation, so no need for a forbid fault region here + // + return EXCEPTION_CONTINUE_SEARCH; + } + + LONG retVal = 0; + +#ifdef FEATURE_STACK_PROBE + // See if we've got enough stack to handle this exception + + // There isn't much stack left to attempt to report an exception. 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 && !pThread->IsStackSpaceAvailable(ADJUST_PROBE(1))) + { + DontCallDirectlyForceStackOverflow(); + } +#endif // FEATURE_STACK_PROBE + + // We can't probe here, because we won't return from the CLRVectoredExceptionHandlerPhase2 + // on WIN64 + // + + if (pThread) + { + FAULT_FORBID_NO_ALLOC(); + CantAllocHolder caHolder; + } + + retVal = CLRVectoredExceptionHandlerPhase2(pExceptionInfo); + + // + //END_ENTRYPOINT_VOIDRET; + // + return retVal; +#else // !FEATURE_PAL + return CLRVectoredExceptionHandlerPhase2(pExceptionInfo); +#endif // !FEATURE_PAL +} + +LONG WINAPI CLRVectoredExceptionHandlerPhase2(PEXCEPTION_POINTERS pExceptionInfo) +{ + // + // DO NOT USE CONTRACTS HERE AS THIS ROUTINE MAY NEVER RETURN. You can use + // static contracts, but currently this is all WRAPPER_NO_CONTRACT. + // + + // + // READ THIS! + // + // + // You cannot put any code in here that allocates during an out-of-memory handling. + // This routine runs before *any* other handlers, including __try. Thus, if you + // allocate anything in this routine then it will throw out-of-memory and end up + // right back here. + // + // There are various things that allocate that you may not expect to allocate. One + // instance of this is STRESS_LOG. It allocates the log buffer if the thread does + // not already have one allocated. Thus, if we OOM during the setting up of the + // thread, the log buffer will not be allocated and this will try to do so. Thus, + // all STRESS_LOGs in here need to be after you have guaranteed the allocation has + // already occurred. + // + + PEXCEPTION_RECORD pExceptionRecord = pExceptionInfo->ExceptionRecord; + VEH_ACTION action; + + { + MAYBE_FAULT_FORBID_NO_ALLOC((pExceptionRecord->ExceptionCode == STATUS_NO_MEMORY)); + CantAllocHolder caHolder; + + action = CLRVectoredExceptionHandlerPhase3(pExceptionInfo); + } + + if (action == VEH_CONTINUE_EXECUTION) + { + return EXCEPTION_CONTINUE_EXECUTION; + } + + if (action == VEH_CONTINUE_SEARCH) + { + return EXCEPTION_CONTINUE_SEARCH; + } + + if (action == VEH_EXECUTE_HANDLER) + { + return EXCEPTION_EXECUTE_HANDLER; + } + +#if defined(WIN64EXCEPTIONS) + + if (action == VEH_EXECUTE_HANDLE_MANAGED_EXCEPTION) + { + // + // If the exception context was unwound by Phase3 then + // we'll jump here to save the managed context and resume execution at + // NakedThrowHelper. This needs to be done outside of any holder's + // scope, because HandleManagedFault may not return. + // + HandleManagedFault(pExceptionInfo->ExceptionRecord, + pExceptionInfo->ContextRecord, + NULL, // establisher frame (x86 only) + NULL // pThread (x86 only) + ); + return EXCEPTION_CONTINUE_EXECUTION; + } + +#endif // defined(WIN64EXCEPTIONS) + + + // + // In OOM situations, this call better not fault. + // + { + MAYBE_FAULT_FORBID_NO_ALLOC((pExceptionRecord->ExceptionCode == STATUS_NO_MEMORY)); + CantAllocHolder caHolder; + + // Give the debugger a chance. Note that its okay for this call to trigger a GC, since the debugger will take + // special steps to make that okay. + // + // @TODO: I'd love a way to call into the debugger with GCX_NOTRIGGER still in scope, and force them to make + // the choice to break the no-trigger region after taking all necessary precautions. + if (IsDebuggerFault(pExceptionRecord, pExceptionInfo->ContextRecord, pExceptionRecord->ExceptionCode, GetThread())) + { + return EXCEPTION_CONTINUE_EXECUTION; + } + } + + // + // No reason to put a forbid fault region here as the exception code is not STATUS_NO_MEMORY. + // + + // Handle a user breakpoint. Note that its okay for the UserBreakpointFilter to trigger a GC, since we're going + // to either a) terminate the process, or b) let a user attach an unmanaged debugger, and debug knowing that + // managed state may be messed up. + if ((pExceptionRecord->ExceptionCode == STATUS_BREAKPOINT) || + (pExceptionRecord->ExceptionCode == STATUS_SINGLE_STEP)) + { + // A breakpoint outside managed code and outside the runtime will have to be handled by some + // other piece of code. + + BOOL fExternalException = FALSE; + + BEGIN_SO_INTOLERANT_CODE_NOPROBE; + + { + // ExecutionManager::IsManagedCode takes a spinlock. Since we're in the middle of throwing, + // we'll allow the lock, even if a caller didn't expect it. + CONTRACT_VIOLATION(TakesLockViolation); + + fExternalException = (!ExecutionManager::IsManagedCode(GetIP(pExceptionInfo->ContextRecord)) && + !IsIPInModule(g_pMSCorEE, GetIP(pExceptionInfo->ContextRecord))); + } + + END_SO_INTOLERANT_CODE_NOPROBE; + + if (fExternalException) + { + // The breakpoint was not ours. Someone else can handle it. (Or if not, we'll get it again as + // an unhandled exception.) + return EXCEPTION_CONTINUE_SEARCH; + } + + // The breakpoint was from managed or the runtime. Handle it. Or, + // this may be a Rotor build. + return UserBreakpointFilter(pExceptionInfo); + } + +#if defined(WIN64EXCEPTIONS) + BOOL fShouldHandleManagedFault; + + { + MAYBE_FAULT_FORBID_NO_ALLOC((pExceptionRecord->ExceptionCode == STATUS_NO_MEMORY)); + CantAllocHolder caHolder; + fShouldHandleManagedFault = ShouldHandleManagedFault(pExceptionInfo->ExceptionRecord, + pExceptionInfo->ContextRecord, + NULL, // establisher frame (x86 only) + NULL // pThread (x86 only) + ); + } + + if (fShouldHandleManagedFault) + { + // + // HandleManagedFault may never return, so we cannot use a forbid fault region around it. + // + HandleManagedFault(pExceptionInfo->ExceptionRecord, + pExceptionInfo->ContextRecord, + NULL, // establisher frame (x86 only) + NULL // pThread (x86 only) + ); + return EXCEPTION_CONTINUE_EXECUTION; +} +#endif // defined(WIN64EXCEPTIONS) + + return EXCEPTION_EXECUTE_HANDLER; +} + +/* + * CLRVectoredExceptionHandlerPhase3 + * + * This routine does some basic processing on the exception, making decisions about common + * exception types and whether to continue them or not. It has side-effects, in that it may + * adjust the context in the exception. + * + * Parameters: + * pExceptionInfo - pointer to the exception + * + * Returns: + * VEH_NO_ACTION - This indicates that Phase3 has no specific action to take and that further + * processing of this exception should continue. + * VEH_EXECUTE_HANDLE_MANAGED_EXCEPTION - This indicates that the caller should call HandleMandagedException + * immediately. + * VEH_CONTINUE_EXECUTION - Caller should return EXCEPTION_CONTINUE_EXECUTION. + * VEH_CONTINUE_SEARCH - Caller should return EXCEPTION_CONTINUE_SEARCH; + * VEH_EXECUTE_HANDLER - Caller should return EXCEPTION_EXECUTE_HANDLER. + * + * Note that in all cases the context in the exception may have been adjusted. + * + */ + +VEH_ACTION WINAPI CLRVectoredExceptionHandlerPhase3(PEXCEPTION_POINTERS pExceptionInfo) +{ + // + // DO NOT USE CONTRACTS HERE AS THIS ROUTINE MAY NEVER RETURN. You can use + // static contracts, but currently this is all WRAPPER_NO_CONTRACT. + // + + // + // READ THIS! + // + // + // You cannot put any code in here that allocates during an out-of-memory handling. + // This routine runs before *any* other handlers, including __try. Thus, if you + // allocate anything in this routine then it will throw out-of-memory and end up + // right back here. + // + // There are various things that allocate that you may not expect to allocate. One + // instance of this is STRESS_LOG. It allocates the log buffer if the thread does + // not already have one allocated. Thus, if we OOM during the setting up of the + // thread, the log buffer will not be allocated and this will try to do so. Thus, + // all STRESS_LOGs in here need to be after you have guaranteed the allocation has + // already occurred. + // + + // Handle special cases which are common amongst all filters. + PEXCEPTION_RECORD pExceptionRecord = pExceptionInfo->ExceptionRecord; + PCONTEXT pContext = pExceptionInfo->ContextRecord; + DWORD exceptionCode = pExceptionRecord->ExceptionCode; + + // Its extremely important that no one trigger a GC in here. This is called from CPFH_FirstPassHandler, in + // cases where we've taken an unmanaged exception on a managed thread (AV, divide by zero, etc.) but + // _before_ we've done our work to erect a FaultingExceptionFrame. Thus, the managed frames are + // unprotected. We setup a GCX_NOTRIGGER holder in this scope to prevent us from messing this up. Note + // that the scope of this is limited, since there are times when its okay to trigger even in this special + // case. The debugger is a good example: if it gets a breakpoint in managed code, it has the smarts to + // prevent the GC before enabling GC, thus its okay for it to trigger. + + GCX_NOTRIGGER(); + +#ifdef USE_REDIRECT_FOR_GCSTRESS + // NOTE: this is effectively ifdef (_TARGET_AMD64_ || _TARGET_ARM_), and does not actually trigger + // a GC. This will redirect the exception context to a stub which will + // push a frame and cause GC. + if (IsGcMarker(exceptionCode, pContext)) + { + return VEH_CONTINUE_EXECUTION;; + } +#endif // USE_REDIRECT_FOR_GCSTRESS + +#if defined(FEATURE_HIJACK) && !defined(PLATFORM_UNIX) +#ifdef _TARGET_X86_ + CPFH_AdjustContextForThreadSuspensionRace(pContext, GetThread()); +#endif // _TARGET_X86_ +#endif // FEATURE_HIJACK && !PLATFORM_UNIX + + // Some other parts of the EE use exceptions in their own nefarious ways. We do some up-front processing + // here to fix up the exception if needed. + if (exceptionCode == STATUS_ACCESS_VIOLATION) + { + if (IsWellFormedAV(pExceptionRecord)) + { + if (AdjustContextForWriteBarrier(pExceptionRecord, pContext)) + { + // On x86, AdjustContextForWriteBarrier simply backs up AV's + // in write barrier helpers into the calling frame, so that + // the subsequent logic here sees a managed fault. + // + // On 64-bit, some additional work is required.. +#ifdef WIN64EXCEPTIONS + return VEH_EXECUTE_HANDLE_MANAGED_EXCEPTION; +#endif // defined(WIN64EXCEPTIONS) + } + else if (AdjustContextForVirtualStub(pExceptionRecord, pContext)) + { +#ifdef WIN64EXCEPTIONS + return VEH_EXECUTE_HANDLE_MANAGED_EXCEPTION; +#endif + } + + // Remember the EIP for stress debugging purposes. + g_LastAccessViolationEIP = (void*) ::GetIP(pContext); + +#ifndef FEATURE_PAL + // Note: we have a holder, called AVInRuntimeImplOkayHolder, that tells us that its okay to have an + // AV in the Runtime's implementation in certain places. So, if its okay to have an AV at this + // time, then skip the check for whether or not the AV is in our impl. + // AVs are ok on the Helper thread (for which there is no pThread object, + // and so the AVInRuntime holder doesn't work. + Thread *pThread = GetThread(); + + bool fAVisOk = + (IsDbgHelperSpecialThread() || IsETWRundownSpecialThread() || + ((pThread != NULL) && (pThread->AVInRuntimeImplOkay())) ); + + + // It is unnecessary to check this on second pass as we would have torn down + // the process on the first pass. Also, the context record is not reliable + // on second pass and this subjects us to false positives. + if ((!fAVisOk) && !(pExceptionRecord->ExceptionFlags & EXCEPTION_UNWINDING)) + { + if (IsIPInModule(g_pMSCorEE, (PCODE)GetIP(pContext))) + { + CONTRACT_VIOLATION(ThrowsViolation|FaultViolation|SOToleranceViolation); + + // + // If you're debugging, set the debugger to catch first-chance AV's, then simply hit F5 or + // 'go' and continue after the assert. We'll recgonize that a debugger is attached, and + // return EXCEPTION_CONTINUE_EXECUTION. You'll re-execute the faulting instruction, and the + // debugger will stop at the AV. The value of EXCEPTION_CONTINUE_EXECUTION is -1, just in + // case you need to verify the return value for some reason. If you need to actually debug + // the failure path, then set your IP around the check below. + // + // You can also use Windbg's .cxr command to set the context to pContext. + // +#if defined(_DEBUG) + const char * pStack = "<stack not available>"; + StackScratchBuffer buffer; + SString sStack; + if (GetStackTraceAtContext(sStack, pContext)) + { + pStack = sStack.GetANSI(buffer); + } + + DWORD tid = GetCurrentThreadId(); + + BOOL debuggerPresentBeforeAssert = IsDebuggerPresent(); + + + CONSISTENCY_CHECK_MSGF(false, ("AV in clr at this callstack:\n------\n%s\n-----\n.AV on tid=0x%x (%d), cxr=%p, exr=%p\n", + pStack, tid, tid, pContext, pExceptionRecord)); + + // @todo - this may not be what we want for interop-debugging... + // + // If there was no debugger before the assert, but there is one now, then go ahead and + // return EXCEPTION_CONTINUE_EXECUTION to re-execute the faulting instruction. This is + // supposed to be a nice little feature for CLR devs who attach debuggers on the "Av in + // mscorwks" assert above. Since this is only for that case, its only in debug builds. + if (!debuggerPresentBeforeAssert && IsDebuggerPresent()) + { + return VEH_CONTINUE_EXECUTION;; + } +#endif // defined(_DEBUG) + + EEPOLICY_HANDLE_FATAL_ERROR_USING_EXCEPTION_INFO(COR_E_EXECUTIONENGINE, pExceptionInfo); + } + } +#endif // !FEATURE_PAL + } + } + else if (exceptionCode == BOOTUP_EXCEPTION_COMPLUS) + { + // Don't handle a boot exception + return VEH_CONTINUE_SEARCH; + } + + return VEH_NO_ACTION; +} + +BOOL IsIPInEE(void *ip) +{ + WRAPPER_NO_CONTRACT; + +#if defined(FEATURE_PREJIT) && !defined(FEATURE_PAL) + if ((TADDR)ip > g_runtimeLoadedBaseAddress && + (TADDR)ip < g_runtimeLoadedBaseAddress + g_runtimeVirtualSize) + { + return TRUE; + } + else +#endif // FEATURE_PREJIT && !FEATURE_PAL + { + return FALSE; + } +} + +#if defined(_TARGET_AMD64_) && defined(FEATURE_HIJACK) + +// This function is used to check if the specified IP is in the prolog or not. +bool IsIPInProlog(EECodeInfo *pCodeInfo) +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + MODE_ANY; + } + CONTRACTL_END; + + bool fInsideProlog = true; + + _ASSERTE(pCodeInfo->IsValid()); + + PTR_RUNTIME_FUNCTION funcEntry = pCodeInfo->GetFunctionEntry(); + + // We should always get a function entry for a managed method + _ASSERTE(funcEntry != NULL); + + // Get the unwindInfo from the function entry + PUNWIND_INFO pUnwindInfo = (PUNWIND_INFO)(pCodeInfo->GetModuleBase() + funcEntry->UnwindData); + + // Check if the specified IP is beyond the prolog or not. + DWORD dwPrologLen = pUnwindInfo->SizeOfProlog; + if (pCodeInfo->GetRelOffset() >= dwPrologLen) + { + fInsideProlog = false; + } + + return fInsideProlog; +} + +// This function is used to check if the specified IP is in the epilog or not. +bool IsIPInEpilog(PTR_CONTEXT pContextToCheck, EECodeInfo *pCodeInfo, BOOL *pSafeToInjectThreadAbort) +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + MODE_ANY; + PRECONDITION(pContextToCheck != NULL); + PRECONDITION(ExecutionManager::IsManagedCode(GetIP(pContextToCheck))); + PRECONDITION(pSafeToInjectThreadAbort != NULL); + } + CONTRACTL_END; + + TADDR ipToCheck = GetIP(pContextToCheck); + + _ASSERTE(pCodeInfo->IsValid()); + + // The Codeinfo should correspond to the IP we are interested in. + _ASSERTE(ipToCheck == pCodeInfo->GetCodeAddress()); + + // By default, assume its safe to inject the abort. + *pSafeToInjectThreadAbort = TRUE; + + // If we are inside a prolog, then we are obviously not in the epilog. + // Its safe to inject the abort here. + if (IsIPInProlog(pCodeInfo)) + { + return false; + } + + // We are not inside the prolog. We could either be in the middle of the method body or + // inside the epilog. While unwindInfo contains the prolog length, it does not contain the + // epilog length. + // + // Thus, to determine if we are inside the epilog, we use a property of RtlVirtualUnwind. + // When invoked for an IP, it will return a NULL for personality routine in only two scenarios: + // + // 1) The unwindInfo does not contain any personality routine information, OR + // 2) The IP is in prolog or epilog. + // + // For jitted code, (1) is not applicable since we *always* emit details of the managed personality routine + // in the unwindInfo. Thus, since we have already determined that we are not inside the prolog, if performing + // RtlVirtualUnwind against "ipToCheck" results in a NULL personality routine, it implies that we are inside + // the epilog. + + DWORD64 imageBase = 0; + PUNWIND_INFO pUnwindInfo = NULL; + CONTEXT tempContext; + PVOID HandlerData; + DWORD64 establisherFrame = 0; + PEXCEPTION_ROUTINE personalityRoutine = NULL; + + // Lookup the function entry for the IP + PTR_RUNTIME_FUNCTION funcEntry = pCodeInfo->GetFunctionEntry(); + + // We should always get a function entry for a managed method + _ASSERTE(funcEntry != NULL); + + imageBase = pCodeInfo->GetModuleBase(); + pUnwindInfo = (PUNWIND_INFO)(imageBase+ funcEntry->UnwindData); + + ZeroMemory(&tempContext, sizeof(CONTEXT)); + CopyOSContext(&tempContext, pContextToCheck); + KNONVOLATILE_CONTEXT_POINTERS ctxPtrs; + ZeroMemory(&ctxPtrs, sizeof(ctxPtrs)); + + personalityRoutine = RtlVirtualUnwind(UNW_FLAG_EHANDLER, // HandlerType + imageBase, + ipToCheck, + funcEntry, + &tempContext, + &HandlerData, + &establisherFrame, + &ctxPtrs); + + bool fIsInEpilog = false; + + if (personalityRoutine == NULL) + { + // We are in epilog. + fIsInEpilog = true; + + // Check if context pointers has returned the address of the stack location in the hijacked function + // from where RBP was restored. If the address is NULL, then it implies that RBP has been popped off. + // Since JIT64 ensures that pop of RBP is the last instruction before ret/jmp, it implies its not safe + // to inject an abort @ this point as EstablisherFrame (which will be based + // of RBP for managed code since that is the FramePointer register, as indicated in the UnwindInfo) + // will be off and can result in bad managed exception dispatch. + if (ctxPtrs.Rbp == NULL) + { + *pSafeToInjectThreadAbort = FALSE; + } + } + + return fIsInEpilog; +} + +#endif // defined(_TARGET_AMD64_) && defined(FEATURE_HIJACK) + +#define EXCEPTION_VISUALCPP_DEBUGGER ((DWORD) (1<<30 | 0x6D<<16 | 5000)) + +#if defined(_TARGET_X86_) + +// This holder is used to capture the FPU state, reset it to what the CLR expects +// and then restore the original state that was captured. +// +// FPU has a set of exception masks which the CLR expects to be always set, +// implying that any corresponding condition will *not* result in FPU raising +// an exception. +// +// However, native code (e.g. high precision math libs) can change this mask. +// Thus, when control enters the CLR (e.g. via exception dispatch into the VEH), +// we will end up using floating point instructions that could satify the exception mask +// condition and raise an exception. This could result in an infinite loop, resulting in +// SO. +// +// We use this holder to protect applicable parts of the runtime from running into such cases. +extern "C" void CaptureFPUContext(BYTE *pFPBUBuf); +extern "C" void RestoreFPUContext(BYTE *pFPBUBuf); + +// This is FPU specific and only applicable to x86 on Windows. +class FPUStateHolder +{ + // Capturing FPU state requires a 28byte buffer + BYTE m_bufFPUState[28]; + +public: + FPUStateHolder() + { + LIMITED_METHOD_CONTRACT; + + BYTE *pFPUBuf = m_bufFPUState; + + // Save the FPU state using the non-waiting instruction + // so that FPU may not raise an exception incase the + // exception masks are unset in the FPU Control Word + CaptureFPUContext(pFPUBuf); + + // Reset the FPU state + ResetCurrentContext(); + } + + ~FPUStateHolder() + { + LIMITED_METHOD_CONTRACT; + + BYTE *pFPUBuf = m_bufFPUState; + + // Restore the capture FPU state + RestoreFPUContext(pFPUBuf); + } +}; + +#endif // defined(_TARGET_X86_) + +LONG WINAPI CLRVectoredExceptionHandlerShim(PEXCEPTION_POINTERS pExceptionInfo) +{ + // + // HandleManagedFault will take a Crst that causes an unbalanced + // notrigger scope, and this contract will whack the thread's + // ClrDebugState to what it was on entry in the dtor, which causes + // us to assert when we finally release the Crst later on. + // +// CONTRACTL +// { +// NOTHROW; +// GC_NOTRIGGER; +// MODE_ANY; +// } +// CONTRACTL_END; + + // + // WARNING WARNING WARNING WARNING WARNING WARNING WARNING + // + // o This function should not call functions that acquire + // synchronization objects or allocate memory, because this + // can cause problems. <-- quoteth MSDN -- probably for + // the same reason as we cannot use LOG(); we'll recurse + // into a stack overflow. + // + // o You cannot use LOG() in here because that will trigger an + // exception which will cause infinite recursion with this + // function. We work around this by ignoring all non-error + // exception codes, which serves as the base of the recursion. + // That way, we can LOG() from the rest of the function + // + // The same goes for any function called by this + // function. + // + // WARNING WARNING WARNING WARNING WARNING WARNING WARNING + // + + // If exceptions (or runtime) have been disabled, then simply return. + if (g_fForbidEnterEE || g_fNoExceptions) + { + return EXCEPTION_CONTINUE_SEARCH; + } + + // WARNING + // + // We must preserve this so that GCStress=4 eh processing doesnt kill last error. + // Note that even GetThread below can affect the LastError. + // Keep this in mind when adding code above this line! + // + // WARNING + DWORD dwLastError = GetLastError(); + +#if defined(_TARGET_X86_) + // Capture the FPU state before we do anything involving floating point instructions + FPUStateHolder captureFPUState; +#endif // defined(_TARGET_X86_) + +#ifdef FEATURE_INTEROP_DEBUGGING + // For interop debugging we have a fancy exception queueing stunt. When the debugger + // initially gets the first chance exception notification it may not know whether to + // continue it handled or unhandled, but it must continue the process to allow the + // in-proc helper thread to work. What it does is continue the exception unhandled which + // will let the thread immediately execute to this point. Inside this worker the thread + // will block until the debugger knows how to continue the exception. If it decides the + // exception was handled then we immediately resume execution as if the exeption had never + // even been allowed to run into this handler. If it is unhandled then we keep processing + // this handler + // + // WARNING: This function could potentially throw an exception, however it should only + // be able to do so when an interop debugger is attached + if(g_pDebugInterface != NULL) + { + if(g_pDebugInterface->FirstChanceSuspendHijackWorker(pExceptionInfo->ContextRecord, + pExceptionInfo->ExceptionRecord) == EXCEPTION_CONTINUE_EXECUTION) + return EXCEPTION_CONTINUE_EXECUTION; + } +#endif + + + DWORD dwCode = pExceptionInfo->ExceptionRecord->ExceptionCode; + if (dwCode == DBG_PRINTEXCEPTION_C || dwCode == EXCEPTION_VISUALCPP_DEBUGGER) + { + return EXCEPTION_CONTINUE_SEARCH; + } + +#if defined(_TARGET_X86_) + if (dwCode == EXCEPTION_BREAKPOINT || dwCode == EXCEPTION_SINGLE_STEP) + { + // For interop debugging, debugger bashes our managed exception handler. + // Interop debugging does not work with real vectored exception handler :( + return EXCEPTION_CONTINUE_SEARCH; + } +#endif + + bool bIsGCMarker = false; + +#ifdef USE_REDIRECT_FOR_GCSTRESS + // This is AMD64 & ARM specific as the macro above is defined for AMD64 & ARM only + bIsGCMarker = IsGcMarker(dwCode, pExceptionInfo->ContextRecord); +#elif defined(_TARGET_X86_) && defined(HAVE_GCCOVER) + // This is the equivalent of the check done in COMPlusFrameHandler, incase the exception is + // seen by VEH first on x86. + bIsGCMarker = IsGcMarker(dwCode, pExceptionInfo->ContextRecord); +#endif // USE_REDIRECT_FOR_GCSTRESS + + // Do not update the TLS with exception details for exceptions pertaining to GCStress + // as they are continueable in nature. + if (!bIsGCMarker) + { + SaveCurrentExceptionInfo(pExceptionInfo->ExceptionRecord, pExceptionInfo->ContextRecord); + } + + + LONG result = EXCEPTION_CONTINUE_SEARCH; + + // If we cannot obtain a Thread object, then we have no business processing any + // exceptions on this thread. Indeed, even checking to see if the faulting + // address is in JITted code is problematic if we have no Thread object, since + // this thread will bypass all our locks. + Thread *pThread = GetThread(); + + // Also check if the exception was in the EE or not + BOOL fExceptionInEE = FALSE; + if (!pThread) + { + // Check if the exception was in EE only if Thread object isnt available. + // This will save us from unnecessary checks + fExceptionInEE = IsIPInEE(pExceptionInfo->ExceptionRecord->ExceptionAddress); + } + + // We are going to process the exception only if one of the following conditions is true: + // + // 1) We have a valid Thread object (implies exception on managed thread) + // 2) Not a valid Thread object but the IP is in the execution engine (implies native thread within EE faulted) + if (pThread || fExceptionInEE) + { + if (!bIsGCMarker) + result = CLRVectoredExceptionHandler(pExceptionInfo); + else + result = EXCEPTION_CONTINUE_EXECUTION; + + if (EXCEPTION_EXECUTE_HANDLER == result) + { + result = EXCEPTION_CONTINUE_SEARCH; + } + +#ifdef _DEBUG +#ifndef FEATURE_PAL +#ifndef WIN64EXCEPTIONS + { + CantAllocHolder caHolder; + + PEXCEPTION_REGISTRATION_RECORD pRecord = GetCurrentSEHRecord(); + while (pRecord != EXCEPTION_CHAIN_END) + { + STRESS_LOG2(LF_EH, LL_INFO10000, "CLRVectoredExceptionHandlerShim: FS:0 %p:%p\n", + pRecord, pRecord->Handler); + pRecord = pRecord->Next; + } + } +#endif // WIN64EXCEPTIONS + + { + // The call to "CLRVectoredExceptionHandler" above can return EXCEPTION_CONTINUE_SEARCH + // for different scenarios like StackOverFlow/SOFT_SO, or if it is forbidden to enter the EE. + // Thus, if we dont have a Thread object for the thread that has faulted and we came this far + // because the fault was in MSCORWKS, then we work with the frame chain below only if we have + // valid Thread object. + + if (pThread) + { + CantAllocHolder caHolder; + + TADDR* sp; + sp = (TADDR*)&sp; + DWORD count = 0; + void* stopPoint = pThread->GetCachedStackBase(); + // If Frame chain is corrupted, we may get AV while accessing frames, and this function will be + // called recursively. We use Frame chain to limit our search range. It is not disaster if we + // can not use it. + if (!(dwCode == STATUS_ACCESS_VIOLATION && + IsIPInEE(pExceptionInfo->ExceptionRecord->ExceptionAddress))) + { + // Find the stop point (most jitted function) + Frame* pFrame = pThread->GetFrame(); + for(;;) + { + // skip GC frames + if (pFrame == 0 || pFrame == (Frame*) -1) + break; + + Frame::ETransitionType type = pFrame->GetTransitionType(); + if (type == Frame::TT_M2U || type == Frame::TT_InternalCall) + { + stopPoint = pFrame; + break; + } + pFrame = pFrame->Next(); + } + } + STRESS_LOG0(LF_EH, LL_INFO100, "CLRVectoredExceptionHandlerShim: stack"); + while (count < 20 && sp < stopPoint) + { + if (IsIPInEE((BYTE*)*sp)) + { + STRESS_LOG1(LF_EH, LL_INFO100, "%pK\n", *sp); + count ++; + } + sp += 1; + } + } + } +#endif // !FEATURE_PAL +#endif // _DEBUG + +#ifndef WIN64EXCEPTIONS + { + CantAllocHolder caHolder; + STRESS_LOG1(LF_EH, LL_INFO1000, "CLRVectoredExceptionHandlerShim: returning %d\n", result); + } +#endif // WIN64EXCEPTIONS + + } + + SetLastError(dwLastError); + + return result; +} + + +// Contains the handle to the registered VEH +static PVOID g_hVectoredExceptionHandler = NULL; + +void CLRAddVectoredHandlers(void) +{ +#ifndef FEATURE_PAL + + // We now install a vectored exception handler on all supporting Windows architectures. + g_hVectoredExceptionHandler = AddVectoredExceptionHandler(TRUE, (PVECTORED_EXCEPTION_HANDLER)CLRVectoredExceptionHandlerShim); + if (g_hVectoredExceptionHandler == NULL) + { + LOG((LF_EH, LL_INFO100, "CLRAddVectoredHandlers: AddVectoredExceptionHandler() failed\n")); + COMPlusThrowHR(E_FAIL); + } + + LOG((LF_EH, LL_INFO100, "CLRAddVectoredHandlers: AddVectoredExceptionHandler() succeeded\n")); +#endif // !FEATURE_PAL +} + +// This function removes the vectored exception and continue handler registration +// from the OS. +void CLRRemoveVectoredHandlers(void) +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + MODE_ANY; + } + CONTRACTL_END; +#ifndef FEATURE_PAL + + // Unregister the vectored exception handler if one is registered (and we can). + if (g_hVectoredExceptionHandler != NULL) + { + // Unregister the vectored exception handler + if (RemoveVectoredExceptionHandler(g_hVectoredExceptionHandler) == FALSE) + { + LOG((LF_EH, LL_INFO100, "CLRRemoveVectoredHandlers: RemoveVectoredExceptionHandler() failed.\n")); + } + else + { + LOG((LF_EH, LL_INFO100, "CLRRemoveVectoredHandlers: RemoveVectoredExceptionHandler() succeeded.\n")); + } + } +#endif // !FEATURE_PAL +} + +// +// This does the work of the Unwind and Continue Hanlder inside the catch clause of that handler. The stack has not +// been unwound when this is called. Keep that in mind when deciding where to put new code :) +// +void UnwindAndContinueRethrowHelperInsideCatch(Frame* pEntryFrame, Exception* pException) +{ + STATIC_CONTRACT_NOTHROW; + STATIC_CONTRACT_GC_TRIGGERS; + STATIC_CONTRACT_MODE_ANY; + STATIC_CONTRACT_SO_TOLERANT; + + Thread* pThread = GetThread(); + + GCX_COOP(); + + LOG((LF_EH, LL_INFO1000, "UNWIND_AND_CONTINUE inside catch, unwinding frame chain\n")); + + // This SetFrame is OK because we will not have frames that require ExceptionUnwind in strictly unmanaged EE + // code chunks which is all that an UnC handler can guard. + // + // @todo: we'd rather use UnwindFrameChain, but there is a concern: some of the ExceptionUnwind methods on some + // of the Frame types do a great deal of work; load classes, throw exceptions, etc. We need to decide on some + // policy here. Do we want to let such funcitons throw, etc.? Right now, we believe that there are no such + // frames on the stack to be unwound, so the SetFrame is alright (see the first comment above.) At the very + // least, we should add some way to assert that. + pThread->SetFrame(pEntryFrame); + +#ifdef _DEBUG + if (!NingenEnabled()) + { + CONTRACT_VIOLATION(ThrowsViolation); + BEGIN_SO_INTOLERANT_CODE(pThread); + // Call CLRException::GetThrowableFromException to force us to retrieve the THROWABLE + // while we are still within the context of the catch block. This will help diagnose + // cases where the last thrown object is NULL. + OBJECTREF orThrowable = CLRException::GetThrowableFromException(pException); + CONSISTENCY_CHECK(orThrowable != NULL); + END_SO_INTOLERANT_CODE; + } +#endif +} + +// +// This does the work of the Unwind and Continue Hanlder after the catch clause of that handler. The stack has been +// unwound by the time this is called. Keep that in mind when deciding where to put new code :) +// +VOID DECLSPEC_NORETURN UnwindAndContinueRethrowHelperAfterCatch(Frame* pEntryFrame, Exception* pException) +{ + STATIC_CONTRACT_THROWS; + STATIC_CONTRACT_GC_TRIGGERS; + STATIC_CONTRACT_MODE_ANY; + STATIC_CONTRACT_SO_TOLERANT; + + // We really should probe before switching to cooperative mode, although there's no chance + // we'll SO in doing that as we've just caught an exception. We can't probe just + // yet though, because we want to avoid reprobing on an SO exception and we need to switch + // to cooperative to check the throwable for an SO as well as the pException object (as the + // pException could be a LastThrownObjectException.) Blech. + CONTRACT_VIOLATION(SOToleranceViolation); + + GCX_COOP(); + + LOG((LF_EH, LL_INFO1000, "UNWIND_AND_CONTINUE caught and will rethrow\n")); + + OBJECTREF orThrowable = NingenEnabled() ? NULL : CLRException::GetThrowableFromException(pException); + LOG((LF_EH, LL_INFO1000, "UNWIND_AND_CONTINUE got throwable %p\n", + OBJECTREFToObject(orThrowable))); + + Exception::Delete(pException); + + if (orThrowable != NULL && g_CLRPolicyRequested) + { + if (orThrowable->GetMethodTable() == g_pOutOfMemoryExceptionClass) + { + EEPolicy::HandleOutOfMemory(); + } + else if (orThrowable->GetMethodTable() == g_pStackOverflowExceptionClass) + { +#ifdef FEATURE_STACK_PROBE + EEPolicy::HandleSoftStackOverflow(); +#else + /* The parameters of the function do not matter here */ + EEPolicy::HandleStackOverflow(SOD_UnmanagedFrameHandler, NULL); +#endif + } + } + + RaiseTheExceptionInternalOnly(orThrowable, FALSE); +} + +void SaveCurrentExceptionInfo(PEXCEPTION_RECORD pRecord, PCONTEXT pContext) +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + MODE_ANY; + SO_TOLERANT; + } + CONTRACTL_END; + + if ((pRecord->ExceptionFlags & (EXCEPTION_UNWINDING | EXCEPTION_EXIT_UNWIND))) + { + // If exception is unwinding the stack, the ExceptionCode may have been changed to + // STATUS_UNWIND if RtlUnwind is called with a NULL ExceptionRecord. + // Since we have captured exception info in the first pass, we don't need to capture it again. + return; + } + + if (CExecutionEngine::CheckThreadStateNoCreate(TlsIdx_PEXCEPTION_RECORD)) + { + BOOL fSave = TRUE; + if (!IsSOExceptionCode(pRecord->ExceptionCode)) + { + DWORD dwLastExceptionCode = (DWORD)(SIZE_T) (ClrFlsGetValue(TlsIdx_EXCEPTION_CODE)); + if (IsSOExceptionCode(dwLastExceptionCode)) + { + PEXCEPTION_RECORD lastRecord = + static_cast<PEXCEPTION_RECORD> (ClrFlsGetValue(TlsIdx_PEXCEPTION_RECORD)); + + // We are trying to see if C++ is attempting a rethrow of a SO exception. If so, + // we want to prevent updating the exception details in the TLS. This is a workaround, + // as explained below. + if (pRecord->ExceptionCode == EXCEPTION_MSVC) + { + // This is a workaround. + // When C++ rethrows, C++ internally gets rid of the new exception record after + // unwinding stack, and present the original exception record to the thread. + // When we get VC's support to obtain exception record in try/catch, we will replace + // this code. + if (pRecord < lastRecord) + { + // For the C++ rethrow workaround, ensure that the last exception record is still valid and as we expect it to be. + // + // Its possible that we are still below the address of last exception record + // but since the execution stack could have changed, simply comparing its address + // with the address of the current exception record may not be enough. + // + // Thus, ensure that its still valid and holds the exception code we expect it to + // have (i.e. value in dwLastExceptionCode). + if ((lastRecord != NULL) && (lastRecord->ExceptionCode == dwLastExceptionCode)) + { + fSave = FALSE; + } + } + } + } + } + if (fSave) + { + ClrFlsSetValue(TlsIdx_EXCEPTION_CODE, (void*)(size_t)(pRecord->ExceptionCode)); + ClrFlsSetValue(TlsIdx_PEXCEPTION_RECORD, pRecord); + ClrFlsSetValue(TlsIdx_PCONTEXT, pContext); + } + } +} + +#ifndef DACCESS_COMPILE +//****************************************************************************** +// +// NotifyOfCHFFilterWrapper +// +// Helper function to deliver notifications of CatchHandlerFound inside a +// EX_TRY/EX_CATCH. +// +// Parameters: +// pExceptionInfo - the pExceptionInfo passed to a filter function. +// pCatcherStackAddr - a Frame* from the PAL_TRY/PAL_EXCEPT_FILTER site. +// +// Return: +// always returns EXCEPTION_CONTINUE_SEARCH. +// +//****************************************************************************** +LONG NotifyOfCHFFilterWrapper( + EXCEPTION_POINTERS *pExceptionInfo, // the pExceptionInfo passed to a filter function. + PVOID pParam) // contains a Frame* from the PAL_TRY/PAL_EXCEPT_FILTER site. +{ + LIMITED_METHOD_CONTRACT; + + PVOID pCatcherStackAddr = ((NotifyOfCHFFilterWrapperParam *)pParam)->pFrame; + ULONG ret = EXCEPTION_CONTINUE_SEARCH; + + // We are here to send an event notification to the debugger and to the appdomain. To + // determine if it is safe to send these notifications, check the following: + // 1) The thread object has been set up. + // 2) The thread has an exception on it. + // 3) The exception is the same as the one this filter is called on. + Thread *pThread = GetThread(); + if ( (pThread == NULL) || + (pThread->GetExceptionState()->GetContextRecord() == NULL) || + (GetSP(pThread->GetExceptionState()->GetContextRecord()) != GetSP(pExceptionInfo->ContextRecord) ) ) + { + LOG((LF_EH, LL_INFO1000, "NotifyOfCHFFilterWrapper: not sending notices. pThread: %0x8", pThread)); + if (pThread) + { + LOG((LF_EH, LL_INFO1000, ", Thread SP: %0x8, Exception SP: %08x", + pThread->GetExceptionState()->GetContextRecord() ? GetSP(pThread->GetExceptionState()->GetContextRecord()) : NULL, + pExceptionInfo->ContextRecord ? GetSP(pExceptionInfo->ContextRecord) : NULL )); + } + LOG((LF_EH, LL_INFO1000, "\n")); + return ret; + } + + if (g_pDebugInterface) + { + // It looks safe, so make the debugger notification. + ret = g_pDebugInterface->NotifyOfCHFFilter(pExceptionInfo, pCatcherStackAddr); + } + + return ret; +} // LONG NotifyOfCHFFilterWrapper() + +// This filter will be used process exceptions escaping out of AD transition boundaries +// that are not at the base of the managed thread. Those are handled in ThreadBaseRedirectingFilter. +// This will be invoked when an exception is going unhandled from the called AppDomain. +// +// This can be used to do last moment work before the exception gets caught by the EX_CATCH setup +// at the AD transition point. +LONG AppDomainTransitionExceptionFilter( + EXCEPTION_POINTERS *pExceptionInfo, // the pExceptionInfo passed to a filter function. + PVOID pParam) +{ + // Ideally, we would be NOTHROW here. However, NotifyOfCHFFilterWrapper calls into + // NotifyOfCHFFilter that is THROWS. Thus, to prevent contract violation, + // we abide by the rules and be THROWS. + // + // Same rationale for GC_TRIGGERS as well. + CONTRACTL + { + GC_TRIGGERS; + MODE_ANY; + THROWS; + } + CONTRACTL_END; + + ULONG ret = EXCEPTION_CONTINUE_SEARCH; + + // First, call into NotifyOfCHFFilterWrapper + ret = NotifyOfCHFFilterWrapper(pExceptionInfo, pParam); + +#ifndef FEATURE_PAL + // Setup the watson bucketing details if the escaping + // exception is preallocated. + if (SetupWatsonBucketsForEscapingPreallocatedExceptions()) + { + // Set the flag that these were captured at AD Transition + DEBUG_STMT(GetThread()->GetExceptionState()->GetUEWatsonBucketTracker()->SetCapturedAtADTransition()); + } + + // Attempt to capture buckets for non-preallocated exceptions just before the AppDomain transition boundary + { + GCX_COOP(); + OBJECTREF oThrowable = GetThread()->GetThrowable(); + if ((oThrowable != NULL) && (CLRException::IsPreallocatedExceptionObject(oThrowable) == FALSE)) + { + SetupWatsonBucketsForNonPreallocatedExceptions(); + } + } +#endif // !FEATURE_PAL + + return ret; +} // LONG AppDomainTransitionExceptionFilter() + +// This filter will be used process exceptions escaping out of dynamic reflection invocation as +// unhandled and will eventually be caught in the VM to be made as inner exception of +// TargetInvocationException that will be thrown from the VM. +LONG ReflectionInvocationExceptionFilter( + EXCEPTION_POINTERS *pExceptionInfo, // the pExceptionInfo passed to a filter function. + PVOID pParam) +{ + // Ideally, we would be NOTHROW here. However, NotifyOfCHFFilterWrapper calls into + // NotifyOfCHFFilter that is THROWS. Thus, to prevent contract violation, + // we abide by the rules and be THROWS. + // + // Same rationale for GC_TRIGGERS as well. + CONTRACTL + { + GC_TRIGGERS; + MODE_ANY; + THROWS; + } + CONTRACTL_END; + + ULONG ret = EXCEPTION_CONTINUE_SEARCH; + + // First, call into NotifyOfCHFFilterWrapper + ret = NotifyOfCHFFilterWrapper(pExceptionInfo, pParam); + +#ifndef FEATURE_PAL + // Setup the watson bucketing details if the escaping + // exception is preallocated. + if (SetupWatsonBucketsForEscapingPreallocatedExceptions()) + { + // Set the flag that these were captured during Reflection Invocation + DEBUG_STMT(GetThread()->GetExceptionState()->GetUEWatsonBucketTracker()->SetCapturedAtReflectionInvocation()); + } + + // Attempt to capture buckets for non-preallocated exceptions just before the ReflectionInvocation boundary + { + GCX_COOP(); + OBJECTREF oThrowable = GetThread()->GetThrowable(); + if ((oThrowable != NULL) && (CLRException::IsPreallocatedExceptionObject(oThrowable) == FALSE)) + { + SetupWatsonBucketsForNonPreallocatedExceptions(); + } + } +#endif // !FEATURE_PAL + + // If the application has opted into triggering a failfast when a CorruptedStateException enters the Reflection system, + // then do the needful. + if (CLRConfig::GetConfigValue(CLRConfig::UNSUPPORTED_FailFastOnCorruptedStateException) == 1) + { + // Get the thread and the managed exception object - they must exist at this point + Thread *pCurThread = GetThread(); + _ASSERTE(pCurThread != NULL); + + // Get the thread exception state + ThreadExceptionState * pCurTES = pCurThread->GetExceptionState(); + _ASSERTE(pCurTES != NULL); + + // Get the exception tracker for the current exception +#ifdef WIN64EXCEPTIONS + PTR_ExceptionTracker pEHTracker = pCurTES->GetCurrentExceptionTracker(); +#elif _TARGET_X86_ + PTR_ExInfo pEHTracker = pCurTES->GetCurrentExceptionTracker(); +#else // !(_WIN64 || _TARGET_X86_) +#error Unsupported platform +#endif // _WIN64 + + if (pEHTracker->GetCorruptionSeverity() == ProcessCorrupting) + { + EEPolicy::HandleFatalError(COR_E_FAILFAST, reinterpret_cast<UINT_PTR>(pExceptionInfo->ExceptionRecord->ExceptionAddress), NULL, pExceptionInfo); + } + } + + return ret; +} // LONG ReflectionInvocationExceptionFilter() + +#endif // !DACCESS_COMPILE + +#ifdef _DEBUG +bool DebugIsEECxxExceptionPointer(void* pv) +{ + CONTRACTL + { + DISABLED(NOTHROW); + GC_NOTRIGGER; + MODE_ANY; + DEBUG_ONLY; + } + CONTRACTL_END; + + if (pv == NULL) + { + return false; + } + + // check whether the memory is readable in no-throw way + if (!isMemoryReadable((TADDR)pv, sizeof(UINT_PTR))) + { + return false; + } + + bool retVal = false; + + EX_TRY + { + UINT_PTR vtbl = *(UINT_PTR*)pv; + + // ex.h + + HRException boilerplate1; + COMException boilerplate2; + SEHException boilerplate3; + + // clrex.h + + CLRException boilerplate4; + CLRLastThrownObjectException boilerplate5; + EEException boilerplate6; + EEMessageException boilerplate7; + EEResourceException boilerplate8; + + // EECOMException::~EECOMException calls FreeExceptionData, which is GC_TRIGGERS, + // but it won't trigger in this case because EECOMException's members remain NULL. + CONTRACT_VIOLATION(GCViolation); + EECOMException boilerplate9; + + EEFieldException boilerplate10; + EEMethodException boilerplate11; + EEArgumentException boilerplate12; + EETypeLoadException boilerplate13; + EEFileLoadException boilerplate14; + ObjrefException boilerplate15; + + UINT_PTR ValidVtbls[] = + { + *((TADDR*)&boilerplate1), + *((TADDR*)&boilerplate2), + *((TADDR*)&boilerplate3), + *((TADDR*)&boilerplate4), + *((TADDR*)&boilerplate5), + *((TADDR*)&boilerplate6), + *((TADDR*)&boilerplate7), + *((TADDR*)&boilerplate8), + *((TADDR*)&boilerplate9), + *((TADDR*)&boilerplate10), + *((TADDR*)&boilerplate11), + *((TADDR*)&boilerplate12), + *((TADDR*)&boilerplate13), + *((TADDR*)&boilerplate14), + *((TADDR*)&boilerplate15) + }; + + const int nVtbls = sizeof(ValidVtbls) / sizeof(ValidVtbls[0]); + + for (int i = 0; i < nVtbls; i++) + { + if (vtbl == ValidVtbls[i]) + { + retVal = true; + break; + } + } + } + EX_CATCH + { + // Swallow any exception out of the exception constructors above and simply return false. + } + EX_END_CATCH(SwallowAllExceptions); + + return retVal; +} + +void *DebugGetCxxException(EXCEPTION_RECORD* pExceptionRecord); + +bool DebugIsEECxxException(EXCEPTION_RECORD* pExceptionRecord) +{ + return DebugIsEECxxExceptionPointer(DebugGetCxxException(pExceptionRecord)); +} + +// +// C++ EH cracking material gleaned from the debugger: +// (DO NOT USE THIS KNOWLEDGE IN NON-DEBUG CODE!!!) +// +// EHExceptionRecord::EHParameters +// [0] magicNumber : uint +// [1] pExceptionObject : void* +// [2] pThrowInfo : ThrowInfo* + +#ifdef _WIN64 +#define NUM_CXX_EXCEPTION_PARAMS 4 +#else +#define NUM_CXX_EXCEPTION_PARAMS 3 +#endif + +void *DebugGetCxxException(EXCEPTION_RECORD* pExceptionRecord) +{ + WRAPPER_NO_CONTRACT; + + bool fExCodeIsCxx = (EXCEPTION_MSVC == pExceptionRecord->ExceptionCode); + bool fExHasCorrectNumParams = (NUM_CXX_EXCEPTION_PARAMS == pExceptionRecord->NumberParameters); + + if (fExCodeIsCxx && fExHasCorrectNumParams) + { + void** ppException = (void**)pExceptionRecord->ExceptionInformation[1]; + + if (NULL == ppException) + { + return NULL; + } + + return *ppException; + + } + + CONSISTENCY_CHECK_MSG(!fExCodeIsCxx || fExHasCorrectNumParams, "We expected an EXCEPTION_MSVC exception to have 3 parameters. Did the CRT change its exception format?"); + + return NULL; +} + +#endif // _DEBUG + +#endif // #ifndef DACCESS_COMPILE + +BOOL IsException(MethodTable *pMT) { + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + MODE_ANY; + SO_TOLERANT; + SUPPORTS_DAC; + } + CONTRACTL_END; + + ASSERT(g_pExceptionClass != NULL); + + while (pMT != NULL && pMT != g_pExceptionClass) { + pMT = pMT->GetParentMethodTable(); + } + + return pMT != NULL; +} // BOOL IsException() + +// Returns TRUE iff calling get_StackTrace on an exception of the given type ends up +// executing some other code than just Exception.get_StackTrace. +BOOL ExceptionTypeOverridesStackTraceGetter(PTR_MethodTable pMT) +{ + CONTRACTL + { + THROWS; + GC_NOTRIGGER; + MODE_ANY; + SO_TOLERANT; + SUPPORTS_DAC; + } + CONTRACTL_END; + + _ASSERTE(IsException(pMT)); + + if (pMT == g_pExceptionClass) + { + // if the type is System.Exception, it certainly doesn't override anything + return FALSE; + } + + // find the slot corresponding to get_StackTrace + for (DWORD slot = g_pObjectClass->GetNumVirtuals(); slot < g_pExceptionClass->GetNumVirtuals(); slot++) + { + MethodDesc *pMD = g_pExceptionClass->GetMethodDescForSlot(slot); + LPCUTF8 name = pMD->GetName(); + + if (name != NULL && strcmp(name, "get_StackTrace") == 0) + { + // see if the slot is overriden by pMT + MethodDesc *pDerivedMD = pMT->GetMethodDescForSlot(slot); + return (pDerivedMD != pMD); + } + } + + // there must be get_StackTrace on System.Exception + UNREACHABLE(); +} + +// Removes source file names/paths and line information from a stack trace generated +// by Environment.GetStackTrace. +void StripFileInfoFromStackTrace(SString &ssStackTrace) +{ + CONTRACTL + { + THROWS; + GC_NOTRIGGER; + MODE_ANY; + SUPPORTS_DAC; + } + CONTRACTL_END; + + SString::Iterator i = ssStackTrace.Begin(); + SString::Iterator end; + int countBracket = 0; + int position = 0; + + while (i < ssStackTrace.End()) + { + if (i[0] == W('(')) + { + countBracket ++; + } + else if (i[0] == W(')')) + { + if (countBracket == 1) + { + end = i + 1; + SString::Iterator j = i + 1; + while (j < ssStackTrace.End()) + { + if (j[0] == W('\r') || j[0] == W('\n')) + { + break; + } + j++; + } + if (j > end) + { + ssStackTrace.Delete(end,j-end); + i = ssStackTrace.Begin() + position; + } + } + countBracket --; + } + i ++; + position ++; + } + ssStackTrace.Truncate(end); +} + +#ifdef _DEBUG +//============================================================================== +// This function will set a thread state indicating if an exception is escaping +// the last CLR personality routine on the stack in a reverse pinvoke scenario. +// +// If the exception continues to go unhandled, it will eventually reach the OS +// that will start invoking the UEFs. Since CLR registers its UEF only to handle +// unhandled exceptions on such reverse pinvoke threads, we will assert this +// state in our UEF to ensure it does not get called for any other reason. +// +// This function should be called only if the personality routine returned +// EXCEPTION_CONTINUE_SEARCH. +//============================================================================== +void SetReversePInvokeEscapingUnhandledExceptionStatus(BOOL fIsUnwinding, +#if defined(_TARGET_X86_) + EXCEPTION_REGISTRATION_RECORD * pEstablisherFrame +#elif defined(WIN64EXCEPTIONS) + ULONG64 pEstablisherFrame +#else +#error Unsupported platform +#endif + ) +{ +#ifndef DACCESS_COMPILE + + LIMITED_METHOD_CONTRACT; + + Thread *pCurThread = GetThread(); + _ASSERTE(pCurThread); + + if (pCurThread->GetExceptionState()->IsExceptionInProgress()) + { + if (!fIsUnwinding) + { + // Get the top-most Frame of this thread. + Frame* pCurFrame = pCurThread->GetFrame(); + Frame* pTopMostFrame = pCurFrame; + while (pCurFrame && (pCurFrame != FRAME_TOP)) + { + pTopMostFrame = pCurFrame; + pCurFrame = pCurFrame->PtrNextFrame(); + } + + // Is the exception escaping the last CLR personality routine on the stack of a + // reverse pinvoke thread? + if (((pTopMostFrame == NULL) || (pTopMostFrame == FRAME_TOP)) || + ((void *)(pEstablisherFrame) > (void *)(pTopMostFrame))) + { + LOG((LF_EH, LL_INFO100, "SetReversePInvokeEscapingUnhandledExceptionStatus: setting Ex_RPInvokeEscapingException\n")); + // Set the flag on the thread indicating the exception is escaping the + // top most reverse pinvoke exception handler. + pCurThread->GetExceptionState()->GetFlags()->SetReversePInvokeEscapingException(); + } + } + else + { + // Since we are unwinding, simply unset the flag indicating escaping unhandled exception + // if it was set. + if (pCurThread->GetExceptionState()->GetFlags()->ReversePInvokeEscapingException()) + { + LOG((LF_EH, LL_INFO100, "SetReversePInvokeEscapingUnhandledExceptionStatus: unsetting Ex_RPInvokeEscapingException\n")); + pCurThread->GetExceptionState()->GetFlags()->ResetReversePInvokeEscapingException(); + } + } + } + else + { + LOG((LF_EH, LL_INFO100, "SetReversePInvokeEscapingUnhandledExceptionStatus: not setting Ex_RPInvokeEscapingException since no exception is in progress.\n")); + } +#endif // !DACCESS_COMPILE +} + +#endif // _DEBUG + +#ifndef FEATURE_PAL + +// This function will capture the watson buckets for the current exception object that is: +// +// 1) Non-preallocated +// 2) Already contains the IP for watson bucketing +BOOL SetupWatsonBucketsForNonPreallocatedExceptions(OBJECTREF oThrowable /* = NULL */) +{ +#ifndef DACCESS_COMPILE + +#ifdef FEATURE_CORECLR + // CoreCLR may have watson bucketing conditionally enabled. + if (!IsWatsonEnabled()) + { + return FALSE; + } +#endif // FEATURE_CORECLR + + CONTRACTL + { + GC_TRIGGERS; + MODE_COOPERATIVE; + NOTHROW; + PRECONDITION(GetThread() != NULL); + } + CONTRACTL_END; + + // By default, assume we didnt get the buckets + BOOL fSetupWatsonBuckets = FALSE; + + Thread * pThread = GetThread(); + + struct + { + OBJECTREF oThrowable; + } gc; + ZeroMemory(&gc, sizeof(gc)); + GCPROTECT_BEGIN(gc); + + // Get the throwable to be used + gc.oThrowable = (oThrowable != NULL) ? oThrowable : pThread->GetThrowable(); + if (gc.oThrowable == NULL) + { + // If we have no throwable, then simply return back. + // + // We could be here because the VM may have raised an exception, + // and not managed code, for its internal usage (e.g. TA to end the + // threads when unloading an AppDomain). Thus, there would be no throwable + // present since the exception has not been seen by the runtime's + // personality routine. + // + // Hence, we have no work to do here. + LOG((LF_EH, LL_INFO100, "SetupWatsonBucketsForNonPreallocatedExceptions - No throwable available.\n")); + goto done; + } + + // The exception object should be non-preallocated + _ASSERTE(!CLRException::IsPreallocatedExceptionObject(gc.oThrowable)); + + if (((EXCEPTIONREF)gc.oThrowable)->AreWatsonBucketsPresent() == FALSE) + { + // Attempt to capture the watson buckets since they are not present. + UINT_PTR ip = ((EXCEPTIONREF)gc.oThrowable)->GetIPForWatsonBuckets(); + if (ip != NULL) + { + // Attempt to capture the buckets + PTR_VOID pBuckets = GetBucketParametersForManagedException(ip, TypeOfReportedError::UnhandledException, pThread, &gc.oThrowable); + if (pBuckets != NULL) + { + // Got the buckets - save them to the exception object + fSetupWatsonBuckets = FALSE; + EX_TRY + { + fSetupWatsonBuckets = CopyWatsonBucketsToThrowable(pBuckets, gc.oThrowable); + } + EX_CATCH + { + // OOM can bring us here + fSetupWatsonBuckets = FALSE; + } + EX_END_CATCH(SwallowAllExceptions); + + if (!fSetupWatsonBuckets) + { + LOG((LF_EH, LL_INFO100, "SetupWatsonBucketsForNonPreallocatedExceptions - Unable to copy buckets to throwable likely due to OOM.\n")); + } + else + { + // Clear the saved IP since we have captured the buckets + ((EXCEPTIONREF)gc.oThrowable)->SetIPForWatsonBuckets(NULL); + LOG((LF_EH, LL_INFO100, "SetupWatsonBucketsForNonPreallocatedExceptions - Buckets copied to throwable.\n")); + } + FreeBucketParametersForManagedException(pBuckets); + } + else + { + LOG((LF_EH, LL_INFO100, "SetupWatsonBucketsForNonPreallocatedExceptions - Unable to capture buckets from IP likely due to OOM.\n")); + } + } + else + { + LOG((LF_EH, LL_INFO100, "SetupWatsonBucketsForNonPreallocatedExceptions - No IP available to capture buckets from.\n")); + } + } + +done:; + GCPROTECT_END(); + + return fSetupWatsonBuckets; +#else // DACCESS_COMPILE + return FALSE; +#endif // !DACCESS_COMPILE +} + +// When exceptions are escaping out of various transition boundaries, +// we will need to capture bucket details for the original exception +// before the exception goes across the boundary to the caller. +// +// Examples of such boundaries include: +// +// 1) AppDomain transition boundaries (these are physical transition boundaries) +// 2) Dynamic method invocation in Reflection (these are logical transition boundaries). +// +// This function will capture the bucketing details in the UE tracker so that +// they can be used once we cross over. +BOOL SetupWatsonBucketsForEscapingPreallocatedExceptions() +{ +#ifndef DACCESS_COMPILE + +#ifdef FEATURE_CORECLR + // CoreCLR may have watson bucketing conditionally enabled. + if (!IsWatsonEnabled()) + { + return FALSE; + } +#endif // FEATURE_CORECLR + + CONTRACTL + { + GC_NOTRIGGER; + MODE_ANY; + NOTHROW; + PRECONDITION(GetThread() != NULL); + } + CONTRACTL_END; + + // By default, assume we didnt get the buckets + BOOL fSetupWatsonBuckets = FALSE; + PTR_EHWatsonBucketTracker pUEWatsonBucketTracker; + + Thread * pThread = GetThread(); + + // If the exception going unhandled is preallocated, then capture the Watson buckets in the UE Watson + // bucket tracker provided its not already populated. + // + // Switch to COOP mode + GCX_COOP(); + + struct + { + OBJECTREF oThrowable; + } gc; + ZeroMemory(&gc, sizeof(gc)); + GCPROTECT_BEGIN(gc); + + // Get the throwable corresponding to the escaping exception + gc.oThrowable = pThread->GetThrowable(); + if (gc.oThrowable == NULL) + { + // If we have no throwable, then simply return back. + // + // We could be here because the VM may have raised an exception, + // and not managed code, for its internal usage (e.g. TA to end the + // threads when unloading an AppDomain). Thus, there would be no throwable + // present since the exception has not been seen by the runtime's + // personality routine. + // + // Hence, we have no work to do here. + LOG((LF_EH, LL_INFO100, "SetupWatsonBucketsForEscapingPreallocatedExceptions - No throwable available.\n")); + goto done; + } + + // Is the exception preallocated? We are not going to process non-preallocated exception objects since + // they already have the watson buckets in them. + // + // We skip thread abort as well since we track them in the UE watson bucket tracker at + // throw time itself. + if (!((CLRException::IsPreallocatedExceptionObject(gc.oThrowable)) && + !IsThrowableThreadAbortException(gc.oThrowable))) + { + // Its either not preallocated or a thread abort exception, + // neither of which we need to process. + goto done; + } + + // The UE watson bucket tracker could be non-empty if there were earlier transitions + // on the threads stack before the exception got raised. + pUEWatsonBucketTracker = pThread->GetExceptionState()->GetUEWatsonBucketTracker(); + _ASSERTE(pUEWatsonBucketTracker != NULL); + + // Proceed to capture bucketing details only if the UE watson bucket tracker is empty. + if((pUEWatsonBucketTracker->RetrieveWatsonBucketIp() == NULL) && (pUEWatsonBucketTracker->RetrieveWatsonBuckets() == NULL)) + { + // Get the Watson Bucket tracker for this preallocated exception + PTR_EHWatsonBucketTracker pCurWatsonBucketTracker = GetWatsonBucketTrackerForPreallocatedException(gc.oThrowable, FALSE); + + if (pCurWatsonBucketTracker != NULL) + { + // If the tracker exists, we must have the throw site IP + _ASSERTE(pCurWatsonBucketTracker->RetrieveWatsonBucketIp() != NULL); + + // Init the UE Watson bucket tracker + pUEWatsonBucketTracker->ClearWatsonBucketDetails(); + + // Copy the Bucket details to the UE watson bucket tracker + pUEWatsonBucketTracker->CopyEHWatsonBucketTracker(*(pCurWatsonBucketTracker)); + + // If the buckets dont exist, capture them now + if (pUEWatsonBucketTracker->RetrieveWatsonBuckets() == NULL) + { + pUEWatsonBucketTracker->CaptureUnhandledInfoForWatson(TypeOfReportedError::UnhandledException, pThread, &gc.oThrowable); + } + + // If the IP was in managed code, we will have the buckets. + if(pUEWatsonBucketTracker->RetrieveWatsonBuckets() != NULL) + { + fSetupWatsonBuckets = TRUE; + LOG((LF_EH, LL_INFO100, "SetupWatsonBucketsForEscapingPreallocatedExceptions - Captured watson buckets for preallocated exception at transition.\n")); + } + else + { + // IP was likely in native code - hence, watson helper functions couldnt get us the buckets + LOG((LF_EH, LL_INFO100, "SetupWatsonBucketsForEscapingPreallocatedExceptions - Watson buckets not found for IP. IP likely in native code.\n")); + + // Clear the UE tracker + pUEWatsonBucketTracker->ClearWatsonBucketDetails(); + } + } + else + { + LOG((LF_EH, LL_INFO100, "SetupWatsonBucketsForEscapingPreallocatedExceptions - Watson bucket tracker for preallocated exception not found. Exception likely thrown in native code.\n")); + } + } + +done:; + GCPROTECT_END(); + + return fSetupWatsonBuckets; +#else // DACCESS_COMPILE + return FALSE; +#endif // !DACCESS_COMPILE +} + +// This function is invoked from the UEF worker to setup the watson buckets +// for the exception going unhandled, if details are available. See +// implementation below for specifics. +void SetupWatsonBucketsForUEF(BOOL fUseLastThrownObject) +{ +#ifndef DACCESS_COMPILE + +#ifdef FEATURE_CORECLR + // CoreCLR may have watson bucketing conditionally enabled. + if (!IsWatsonEnabled()) + { + return; + } +#endif // FEATURE_CORECLR + + CONTRACTL + { + GC_TRIGGERS; + MODE_ANY; + NOTHROW; + PRECONDITION(GetThread() != NULL); + } + CONTRACTL_END; + + Thread *pThread = GetThread(); + + PTR_EHWatsonBucketTracker pCurWatsonBucketTracker = NULL; + ThreadExceptionState *pExState = pThread->GetExceptionState(); + _ASSERTE(pExState != NULL); + + // If the exception tracker exists, then copy the bucketing details + // from it to the UE Watson Bucket tracker. + // + // On 64bit, the EH system allocates the EHTracker only in the case of an exception. + // Thus, assume a reverse pinvoke thread transitions to managed code from native, + // does some work in managed and returns back to native code. + // + // In the native code, it has an exception that goes unhandled and the OS + // ends up invoking our UEF, and thus, we land up here. + // + // In such a case, on 64bit, we wont have an exception tracker since there + // was no managed exception active. On 32bit, we will have a tracker + // but there wont be an IP corresponding to the throw site since exception + // was raised in native code. + // + // But if the tracker exists, simply copy the bucket details to the UE Watson Bucket + // tracker for use by the "WatsonLastChance" path. + BOOL fDoWeHaveWatsonBuckets = FALSE; + if (pExState->GetCurrentExceptionTracker() != NULL) + { + // Check the exception state if we have Watson bucket details + fDoWeHaveWatsonBuckets = pExState->GetFlags()->GotWatsonBucketDetails(); + } + + // Switch to COOP mode before working with the throwable + GCX_COOP(); + + // Get the throwable we are going to work with + struct + { + OBJECTREF oThrowable; + } gc; + ZeroMemory(&gc, sizeof(gc)); + GCPROTECT_BEGIN(gc); + + gc.oThrowable = fUseLastThrownObject ? pThread->LastThrownObject() : pThread->GetThrowable(); + BOOL fThrowableExists = (gc.oThrowable != NULL); + BOOL fIsThrowablePreallocated = !fThrowableExists ? FALSE : CLRException::IsPreallocatedExceptionObject(gc.oThrowable); + + if ((!fDoWeHaveWatsonBuckets) && fThrowableExists) + { + // Check the throwable if it has buckets - this could be the scenario + // of native code calling into a non-default domain and thus, have an AD + // transition in between that could reraise the exception but that would + // never be seen by our exception handler. Thus, there wont be any tracker + // or tracker state. + // + // Invocation of entry point on WLC via reverse pinvoke is an example. + if (!fIsThrowablePreallocated) + { + fDoWeHaveWatsonBuckets = ((EXCEPTIONREF)gc.oThrowable)->AreWatsonBucketsPresent(); + if (!fDoWeHaveWatsonBuckets) + { + // If buckets are not present, then we may have IP to capture the buckets from. + fDoWeHaveWatsonBuckets = ((EXCEPTIONREF)gc.oThrowable)->IsIPForWatsonBucketsPresent(); + } + } + else + { + // Get the watson bucket tracker for the preallocated exception + PTR_EHWatsonBucketTracker pCurWBTracker = GetWatsonBucketTrackerForPreallocatedException(gc.oThrowable, FALSE); + + // We would have buckets if we have the IP + if (pCurWBTracker && (pCurWBTracker->RetrieveWatsonBucketIp() != NULL)) + { + fDoWeHaveWatsonBuckets = TRUE; + } + } + } + + if (fDoWeHaveWatsonBuckets) + { + // Get the UE Watson bucket tracker + PTR_EHWatsonBucketTracker pUEWatsonBucketTracker = pExState->GetUEWatsonBucketTracker(); + + // Clear any existing information + pUEWatsonBucketTracker->ClearWatsonBucketDetails(); + + if (fIsThrowablePreallocated) + { + // Get the watson bucket tracker for the preallocated exception + PTR_EHWatsonBucketTracker pCurWBTracker = GetWatsonBucketTrackerForPreallocatedException(gc.oThrowable, FALSE); + + if (pCurWBTracker != NULL) + { + // We should be having an IP for this exception at this point + _ASSERTE(pCurWBTracker->RetrieveWatsonBucketIp() != NULL); + + // Copy the existing bucketing details to the UE tracker + pUEWatsonBucketTracker->CopyEHWatsonBucketTracker(*(pCurWBTracker)); + + // Get the buckets if we dont already have them since we + // dont want to overwrite existing bucket information (e.g. + // from an AD transition) + if (pUEWatsonBucketTracker->RetrieveWatsonBuckets() == NULL) + { + pUEWatsonBucketTracker->CaptureUnhandledInfoForWatson(TypeOfReportedError::UnhandledException, pThread, &gc.oThrowable); + if (pUEWatsonBucketTracker->RetrieveWatsonBuckets() != NULL) + { + LOG((LF_EH, LL_INFO100, "SetupWatsonBucketsForUEF: Collected watson bucket information for preallocated exception\n")); + } + else + { + // If we are here, then one of the following could have happened: + // + // 1) pCurWBTracker had buckets but we couldnt copy them over to pUEWatsonBucketTracker due to OOM, or + // 2) pCurWBTracker's IP was in native code; thus pUEWatsonBucketTracker->CaptureUnhandledInfoForWatson() + // couldnt get us the watson buckets. + LOG((LF_EH, LL_INFO100, "SetupWatsonBucketsForUEF: Unable to collect watson bucket information for preallocated exception due to OOM or IP being in native code.\n")); + } + } + } + else + { + // We likely had an OOM earlier (while copying the bucket information) if we are here + LOG((LF_EH, LL_INFO100, "SetupWatsonBucketsForUEF: Watson bucket tracker for preallocated exception not found.\n")); + } + } + else + { + // Throwable is not preallocated - get the bucket details from it for use by Watson + _ASSERTE_MSG(((EXCEPTIONREF)gc.oThrowable)->AreWatsonBucketsPresent() || + ((EXCEPTIONREF)gc.oThrowable)->IsIPForWatsonBucketsPresent(), + "How come we dont have watson buckets (or IP) for a non-preallocated exception in the UEF?"); + + if ((((EXCEPTIONREF)gc.oThrowable)->AreWatsonBucketsPresent() == FALSE) && + ((EXCEPTIONREF)gc.oThrowable)->IsIPForWatsonBucketsPresent()) + { + // Capture the buckets using the IP we have. + SetupWatsonBucketsForNonPreallocatedExceptions(gc.oThrowable); + } + + if (((EXCEPTIONREF)gc.oThrowable)->AreWatsonBucketsPresent()) + { + pUEWatsonBucketTracker->CopyBucketsFromThrowable(gc.oThrowable); + } + + if (pUEWatsonBucketTracker->RetrieveWatsonBuckets() == NULL) + { + LOG((LF_EH, LL_INFO100, "SetupWatsonBucketsForUEF: Unable to copy watson buckets from regular exception throwable (%p), likely due to OOM.\n", + OBJECTREFToObject(gc.oThrowable))); + } + } + } + else + { + // We dont have the watson buckets; exception was in native code that we dont care about + LOG((LF_EH, LL_INFO100, "SetupWatsonBucketsForUEF: We dont have watson buckets - likely an exception in native code.\n")); + } + + GCPROTECT_END(); +#endif // !DACCESS_COMPILE +} + +// Given a throwable, this function will return a BOOL indicating +// if it corresponds to any of the following thread abort exception +// objects: +// +// 1) Regular allocated ThreadAbortException +// 2) Preallocated ThreadAbortException +// 3) Preallocated RudeThreadAbortException +BOOL IsThrowableThreadAbortException(OBJECTREF oThrowable) +{ +#ifndef DACCESS_COMPILE + CONTRACTL + { + GC_NOTRIGGER; + MODE_COOPERATIVE; + NOTHROW; + PRECONDITION(GetThread() != NULL); + PRECONDITION(oThrowable != NULL); + } + CONTRACTL_END; + + BOOL fIsTAE = FALSE; + + struct + { + OBJECTREF oThrowable; + } gc; + ZeroMemory(&gc, sizeof(gc)); + GCPROTECT_BEGIN(gc); + + gc.oThrowable = oThrowable; + + fIsTAE = (IsExceptionOfType(kThreadAbortException,&(gc.oThrowable)) || // regular TAE + ((g_pPreallocatedThreadAbortException != NULL) && + (gc.oThrowable == CLRException::GetPreallocatedThreadAbortException())) || + ((g_pPreallocatedRudeThreadAbortException != NULL) && + (gc.oThrowable == CLRException::GetPreallocatedRudeThreadAbortException()))); + + GCPROTECT_END(); + + return fIsTAE; + +#else // DACCESS_COMPILE + return FALSE; +#endif // !DACCESS_COMPILE +} + +// Given a throwable, this function will walk the exception tracker +// list to return the tracker, if available, corresponding to the preallocated +// exception object. +// +// The caller can also specify the starting EHTracker to walk the list from. +// If not specified, this will default to the current exception tracker active +// on the thread. +#if defined(WIN64EXCEPTIONS) +PTR_ExceptionTracker GetEHTrackerForPreallocatedException(OBJECTREF oPreAllocThrowable, + PTR_ExceptionTracker pStartingEHTracker) +#elif _TARGET_X86_ +PTR_ExInfo GetEHTrackerForPreallocatedException(OBJECTREF oPreAllocThrowable, + PTR_ExInfo pStartingEHTracker) +#else +#error Unsupported platform +#endif +{ + CONTRACTL + { + GC_NOTRIGGER; + MODE_COOPERATIVE; + NOTHROW; + PRECONDITION(GetThread() != NULL); + PRECONDITION(oPreAllocThrowable != NULL); + PRECONDITION(CLRException::IsPreallocatedExceptionObject(oPreAllocThrowable)); + PRECONDITION(IsWatsonEnabled()); + } + CONTRACTL_END; + + // Get the reference to the current exception tracker +#if defined(WIN64EXCEPTIONS) + PTR_ExceptionTracker pEHTracker = (pStartingEHTracker != NULL) ? pStartingEHTracker : GetThread()->GetExceptionState()->GetCurrentExceptionTracker(); +#elif _TARGET_X86_ + PTR_ExInfo pEHTracker = (pStartingEHTracker != NULL) ? pStartingEHTracker : GetThread()->GetExceptionState()->GetCurrentExceptionTracker(); +#else // !(_WIN64 || _TARGET_X86_) +#error Unsupported platform +#endif // _WIN64 + + BOOL fFoundTracker = FALSE; + + struct + { + OBJECTREF oPreAllocThrowable; + } gc; + ZeroMemory(&gc, sizeof(gc)); + GCPROTECT_BEGIN(gc); + + gc.oPreAllocThrowable = oPreAllocThrowable; + + // Start walking the list to find the tracker correponding + // to the preallocated exception object. + while (pEHTracker != NULL) + { + if (pEHTracker->GetThrowable() == gc.oPreAllocThrowable) + { + // found the tracker - break out. + fFoundTracker = TRUE; + break; + } + + // move to the previous tracker... + pEHTracker = pEHTracker->GetPreviousExceptionTracker(); + } + + GCPROTECT_END(); + + return fFoundTracker ? pEHTracker : NULL; +} + +// This function will return the pointer to EHWatsonBucketTracker corresponding to the +// preallocated exception object. If none is found, it will return NULL. +PTR_EHWatsonBucketTracker GetWatsonBucketTrackerForPreallocatedException(OBJECTREF oPreAllocThrowable, + BOOL fCaptureBucketsIfNotPresent, + BOOL fStartSearchFromPreviousTracker /*= FALSE*/) +{ +#ifndef DACCESS_COMPILE + CONTRACTL + { + GC_NOTRIGGER; + MODE_COOPERATIVE; + NOTHROW; + PRECONDITION(GetThread() != NULL); + PRECONDITION(oPreAllocThrowable != NULL); + PRECONDITION(CLRException::IsPreallocatedExceptionObject(oPreAllocThrowable)); + PRECONDITION(IsWatsonEnabled()); + } + CONTRACTL_END; + + PTR_EHWatsonBucketTracker pWBTracker = NULL; + + struct + { + OBJECTREF oPreAllocThrowable; + } gc; + ZeroMemory(&gc, sizeof(gc)); + GCPROTECT_BEGIN(gc); + + gc.oPreAllocThrowable = oPreAllocThrowable; + + // Before doing anything, check if this is a thread abort exception. If it is, + // then simply return the reference to the UE watson bucket tracker since it + // tracks the bucketing details for all types of TAE. + if (IsThrowableThreadAbortException(gc.oPreAllocThrowable)) + { + pWBTracker = GetThread()->GetExceptionState()->GetUEWatsonBucketTracker(); + LOG((LF_EH, LL_INFO100, "GetWatsonBucketTrackerForPreallocatedException - Setting UE Watson Bucket Tracker to be returned for preallocated ThreadAbortException.\n")); + goto doValidation; + } + + // Find the reference to the exception tracker corresponding to the preallocated exception, + // starting the search from the current exception tracker (2nd arg of NULL specifies that). +#if defined(WIN64EXCEPTIONS) + PTR_ExceptionTracker pEHTracker = NULL; + PTR_ExceptionTracker pPreviousEHTracker = NULL; + +#elif _TARGET_X86_ + PTR_ExInfo pEHTracker = NULL; + PTR_ExInfo pPreviousEHTracker = NULL; +#else // !(_WIN64 || _TARGET_X86_) +#error Unsupported platform +#endif // _WIN64 + + if (fStartSearchFromPreviousTracker) + { + // Get the exception tracker previous to the current one + pPreviousEHTracker = GetThread()->GetExceptionState()->GetCurrentExceptionTracker()->GetPreviousExceptionTracker(); + + // If there is no previous tracker to start from, then simply abort the search attempt. + // If we couldnt find the exception tracker, then buckets are not available + if (pPreviousEHTracker == NULL) + { + LOG((LF_EH, LL_INFO100, "GetWatsonBucketTrackerForPreallocatedException - Couldnt find the previous EHTracker to start the search from.\n")); + pWBTracker = NULL; + goto done; + } + } + + pEHTracker = GetEHTrackerForPreallocatedException(gc.oPreAllocThrowable, pPreviousEHTracker); + + // If we couldnt find the exception tracker, then buckets are not available + if (pEHTracker == NULL) + { + LOG((LF_EH, LL_INFO100, "GetWatsonBucketTrackerForPreallocatedException - Couldnt find EHTracker for preallocated exception object.\n")); + pWBTracker = NULL; + goto done; + } + + // Get the Watson Bucket Tracker from the exception tracker + pWBTracker = pEHTracker->GetWatsonBucketTracker(); + +doValidation: + _ASSERTE(pWBTracker != NULL); + + // Incase of an OOM, we may not have an IP in the Watson bucket tracker. A scenario + // would be default domain calling to AD 2 that calls into AD 3. + // + // AD 3 has an exception that is represented by a preallocated exception object. The + // exception goes unhandled and reaches AD2/AD3 transition boundary. The bucketing details + // from AD3 are copied to UETracker and once the exception is reraised in AD2, we will + // enter SetupInitialThrowBucketingDetails to copy the bucketing details to the active + // exception tracker. + // + // This copy operation could fail due to OOM and the active exception tracker in AD 2, + // for the preallocated exception object, will not have any bucketing details. If the + // exception remains unhandled in AD 2, then just before it reaches DefDomain/AD2 boundary, + // we will attempt to capture the bucketing details in AppDomainTransitionExceptionFilter, + // that will bring us here. + // + // In such a case, the active exception tracker will not have any bucket details for the + // preallocated exception. In such a case, if the IP does not exist, we will return NULL + // indicating that we couldnt find the Watson bucket tracker, since returning a tracker + // that does not have any bucketing details will be of no use to the caller. + if (pWBTracker->RetrieveWatsonBucketIp() != NULL) + { + // Check if the buckets exist or not.. + PTR_VOID pBuckets = pWBTracker->RetrieveWatsonBuckets(); + + // If they dont exist and we have been asked to collect them, + // then do so. + if (pBuckets == NULL) + { + if (fCaptureBucketsIfNotPresent) + { + pWBTracker->CaptureUnhandledInfoForWatson(TypeOfReportedError::UnhandledException, GetThread(), &gc.oPreAllocThrowable); + + // Check if we have the buckets now + if (pWBTracker->RetrieveWatsonBuckets() != NULL) + { + LOG((LF_EH, LL_INFO100, "GetWatsonBucketTrackerForPreallocatedException - Captured watson buckets for preallocated exception object.\n")); + } + else + { + LOG((LF_EH, LL_INFO100, "GetWatsonBucketTrackerForPreallocatedException - Unable to capture watson buckets for preallocated exception object due to OOM.\n")); + } + } + else + { + LOG((LF_EH, LL_INFO100, "GetWatsonBucketTrackerForPreallocatedException - Found IP but no buckets for preallocated exception object.\n")); + } + } + else + { + LOG((LF_EH, LL_INFO100, "GetWatsonBucketTrackerForPreallocatedException - Buckets already exist for preallocated exception object.\n")); + } + } + else + { + LOG((LF_EH, LL_INFO100, "GetWatsonBucketTrackerForPreallocatedException - Returning NULL EHWatsonBucketTracker since bucketing IP does not exist. This is likely due to an earlier OOM.\n")); + pWBTracker = NULL; + } + +done:; + + GCPROTECT_END(); + + // Return the Watson bucket tracker + return pWBTracker; +#else // DACCESS_COMPILE + return NULL; +#endif // !DACCESS_COMPILE +} + +// Given an exception object, this function will attempt to look up +// the watson buckets for it and set them up against the thread +// for use by FailFast mechanism. +// Return TRUE when it succeeds or Waston is disabled on CoreCLR +// Return FALSE when refException neither has buckets nor has inner exception +BOOL SetupWatsonBucketsForFailFast(EXCEPTIONREF refException) +{ + BOOL fResult = TRUE; + +#ifndef DACCESS_COMPILE +#ifdef FEATURE_CORECLR + // On CoreCLR, Watson may not be enabled. Thus, we should + // skip this. + if (!IsWatsonEnabled()) + { + return fResult; + } +#endif // FEATURE_CORECLR + + CONTRACTL + { + GC_TRIGGERS; + MODE_ANY; + NOTHROW; + PRECONDITION(GetThread() != NULL); + PRECONDITION(refException != NULL); + PRECONDITION(IsWatsonEnabled()); + } + CONTRACTL_END; + + // Switch to COOP mode + GCX_COOP(); + + struct + { + OBJECTREF refException; + OBJECTREF oInnerMostExceptionThrowable; + } gc; + ZeroMemory(&gc, sizeof(gc)); + GCPROTECT_BEGIN(gc); + gc.refException = refException; + + Thread *pThread = GetThread(); + + // If we dont already have the bucketing details for the exception + // being thrown, then get them. + ThreadExceptionState *pExState = pThread->GetExceptionState(); + + // Check if the exception object is preallocated or not + BOOL fIsPreallocatedException = CLRException::IsPreallocatedExceptionObject(gc.refException); + + // Get the WatsonBucketTracker where bucketing details will be copied to + PTR_EHWatsonBucketTracker pUEWatsonBucketTracker = pExState->GetUEWatsonBucketTracker(); + + // Check if this is a thread abort exception of any kind. + // See IsThrowableThreadAbortException implementation for details. + BOOL fIsThreadAbortException = IsThrowableThreadAbortException(gc.refException); + + if (fIsPreallocatedException) + { + // If the exception being used to FailFast is preallocated, + // then it cannot have any inner exception. Thus, try to + // find the watson bucket tracker corresponding to this exception. + // + // Also, capture the buckets if we dont have them already. + PTR_EHWatsonBucketTracker pTargetWatsonBucketTracker = GetWatsonBucketTrackerForPreallocatedException(gc.refException, TRUE); + if ((pTargetWatsonBucketTracker != NULL) && (!fIsThreadAbortException)) + { + // Buckets are not captured proactively for preallocated exception objects. We only + // save the IP in the watson bucket tracker (see SetupInitialThrowBucketingDetails for + // details). + // + // Thus, if, say in DefDomain, a preallocated exception is thrown and we enter + // the catch block and invoke the FailFast API with the reference to the preallocated + // exception object, we will have the IP but not the buckets. In such a case, + // capture the buckets before proceeding ahead. + if (pTargetWatsonBucketTracker->RetrieveWatsonBuckets() == NULL) + { + LOG((LF_EH, LL_INFO100, "SetupWatsonBucketsForFailFast - Collecting watson bucket details for preallocated exception.\n")); + pTargetWatsonBucketTracker->CaptureUnhandledInfoForWatson(TypeOfReportedError::UnhandledException, pThread, &gc.refException); + } + + // Copy the buckets to the UE tracker + pUEWatsonBucketTracker->ClearWatsonBucketDetails(); + pUEWatsonBucketTracker->CopyEHWatsonBucketTracker(*pTargetWatsonBucketTracker); + if (pUEWatsonBucketTracker->RetrieveWatsonBuckets() != NULL) + { + LOG((LF_EH, LL_INFO100, "SetupWatsonBucketsForFailFast - Collected watson bucket details for preallocated exception in UE tracker.\n")); + } + else + { + // If we are here, then the copy operation above had an OOM, resulting + // in no buckets for us. + LOG((LF_EH, LL_INFO100, "SetupWatsonBucketsForFailFast - Unable to collect watson bucket details for preallocated exception due to out of memory.\n")); + + // Make sure the tracker is clean. + pUEWatsonBucketTracker->ClearWatsonBucketDetails(); + } + } + else + { + // For TAE, UE watson bucket tracker is the one that tracks the buckets. It *may* + // not have the bucket details if FailFast is being invoked from outside the + // managed EH clauses. But if invoked from within the active EH clause for the exception, + // UETracker will have the bucketing details (see SetupInitialThrowBucketingDetails for details). + if (fIsThreadAbortException && (pUEWatsonBucketTracker->RetrieveWatsonBuckets() != NULL)) + { + _ASSERTE(pTargetWatsonBucketTracker == pUEWatsonBucketTracker); + LOG((LF_EH, LL_INFO100, "SetupWatsonBucketsForFailFast - UE tracker already watson bucket details for preallocated thread abort exception.\n")); + } + else + { + LOG((LF_EH, LL_INFO100, "SetupWatsonBucketsForFailFast - Unable to find bucket details for preallocated %s exception.\n", + fIsThreadAbortException?"rude/thread abort":"")); + + // Make sure the tracker is clean. + pUEWatsonBucketTracker->ClearWatsonBucketDetails(); + } + } + } + else + { + // Since the exception object is not preallocated, start by assuming + // that we dont need to check it for watson buckets + BOOL fCheckThrowableForWatsonBuckets = FALSE; + + // Get the innermost exception object (if any) + gc.oInnerMostExceptionThrowable = ((EXCEPTIONREF)gc.refException)->GetBaseException(); + if (gc.oInnerMostExceptionThrowable != NULL) + { + if (CLRException::IsPreallocatedExceptionObject(gc.oInnerMostExceptionThrowable)) + { + // If the inner most exception being used to FailFast is preallocated, + // try to find the watson bucket tracker corresponding to it. + // + // Also, capture the buckets if we dont have them already. + PTR_EHWatsonBucketTracker pTargetWatsonBucketTracker = + GetWatsonBucketTrackerForPreallocatedException(gc.oInnerMostExceptionThrowable, TRUE); + + if (pTargetWatsonBucketTracker != NULL) + { + if (pTargetWatsonBucketTracker->RetrieveWatsonBuckets() == NULL) + { + LOG((LF_EH, LL_INFO1000, "SetupWatsonBucketsForFailFast - Capturing Watson bucket details for preallocated inner exception.\n")); + pTargetWatsonBucketTracker->CaptureUnhandledInfoForWatson(TypeOfReportedError::UnhandledException, pThread, &gc.oInnerMostExceptionThrowable); + } + + // Copy the details to the UE tracker + pUEWatsonBucketTracker->ClearWatsonBucketDetails(); + pUEWatsonBucketTracker->CopyEHWatsonBucketTracker(*pTargetWatsonBucketTracker); + if (pUEWatsonBucketTracker->RetrieveWatsonBuckets() != NULL) + { + LOG((LF_EH, LL_INFO1000, "SetupWatsonBucketsForFailFast - Watson bucket details collected for preallocated inner exception.\n")); + } + else + { + // If we are here, copy operation failed likely due to OOM + LOG((LF_EH, LL_INFO1000, "SetupWatsonBucketsForFailFast - Unable to copy watson bucket details for preallocated inner exception.\n")); + + // Keep the UETracker clean + pUEWatsonBucketTracker->ClearWatsonBucketDetails(); + } + } + else + { + LOG((LF_EH, LL_INFO1000, "SetupWatsonBucketsForFailFast - Unable to find bucket details for preallocated inner exception.\n")); + + // Keep the UETracker clean + pUEWatsonBucketTracker->ClearWatsonBucketDetails(); + + // Since we couldnt find the watson bucket tracker for the the inner most exception, + // try to look for the buckets in the throwable. + fCheckThrowableForWatsonBuckets = TRUE; + } + } + else + { + // Inner most exception is not preallocated. + // + // If it has the IP but not the buckets, then capture them now. + if ((((EXCEPTIONREF)gc.oInnerMostExceptionThrowable)->AreWatsonBucketsPresent() == FALSE) && + (((EXCEPTIONREF)gc.oInnerMostExceptionThrowable)->IsIPForWatsonBucketsPresent())) + { + SetupWatsonBucketsForNonPreallocatedExceptions(gc.oInnerMostExceptionThrowable); + } + + // If it has the buckets, copy them over to the current Watson bucket tracker + if (((EXCEPTIONREF)gc.oInnerMostExceptionThrowable)->AreWatsonBucketsPresent()) + { + pUEWatsonBucketTracker->ClearWatsonBucketDetails(); + pUEWatsonBucketTracker->CopyBucketsFromThrowable(gc.oInnerMostExceptionThrowable); + if (pUEWatsonBucketTracker->RetrieveWatsonBuckets() != NULL) + { + LOG((LF_EH, LL_INFO1000, "SetupWatsonBucketsForFailFast - Got watson buckets from regular innermost exception.\n")); + } + else + { + // Copy operation can fail due to OOM + LOG((LF_EH, LL_INFO1000, "SetupWatsonBucketsForFailFast - Unable to copy watson buckets from regular innermost exception, likely due to OOM.\n")); + } + } + else + { + // Since the inner most exception didnt have the buckets, + // try to look for them in the throwable + fCheckThrowableForWatsonBuckets = TRUE; + LOG((LF_EH, LL_INFO1000, "SetupWatsonBucketsForFailFast - Neither exception object nor its inner exception has watson buckets.\n")); + } + } + } + else + { + // There is no innermost exception - try to look for buckets + // in the throwable + fCheckThrowableForWatsonBuckets = TRUE; + LOG((LF_EH, LL_INFO1000, "SetupWatsonBucketsForFailFast - Innermost exception does not exist\n")); + } + + if (fCheckThrowableForWatsonBuckets) + { + // Since we have not found buckets anywhere, try to look for them + // in the throwable. + if ((((EXCEPTIONREF)gc.refException)->AreWatsonBucketsPresent() == FALSE) && + (((EXCEPTIONREF)gc.refException)->IsIPForWatsonBucketsPresent())) + { + // Capture the buckets from the IP. + SetupWatsonBucketsForNonPreallocatedExceptions(gc.refException); + } + + if (((EXCEPTIONREF)gc.refException)->AreWatsonBucketsPresent()) + { + // Copy the buckets to the current watson bucket tracker + pUEWatsonBucketTracker->ClearWatsonBucketDetails(); + pUEWatsonBucketTracker->CopyBucketsFromThrowable(gc.refException); + if (pUEWatsonBucketTracker->RetrieveWatsonBuckets() != NULL) + { + LOG((LF_EH, LL_INFO1000, "SetupWatsonBucketsForFailFast - Watson buckets copied from the exception object.\n")); + } + else + { + LOG((LF_EH, LL_INFO1000, "SetupWatsonBucketsForFailFast - Unable to copy Watson buckets copied from the exception object, likely due to OOM.\n")); + } + } + else + { + fResult = FALSE; + LOG((LF_EH, LL_INFO1000, "SetupWatsonBucketsForFailFast - Exception object neither has buckets nor has inner exception.\n")); + } + } + } + + GCPROTECT_END(); + +#endif // !DACCESS_COMPILE + + return fResult; +} + +// This function will setup the bucketing details in the exception +// tracker or the throwable, if they are not already setup. +// +// This is called when an exception is thrown (or raised): +// +// 1) from outside the confines of managed EH clauses, OR +// 2) from within the confines of managed EH clauses but the +// exception does not have bucketing details with it, OR +// 3) When an exception is reraised at AD transition boundary +// after it has been marshalled over to the returning AD. +void SetupInitialThrowBucketDetails(UINT_PTR adjustedIp) +{ +#ifndef DACCESS_COMPILE + +#ifdef FEATURE_CORECLR + // On CoreCLR, Watson may not be enabled. Thus, we should + // skip this. + if (!IsWatsonEnabled()) + { + return; + } +#endif // FEATURE_CORECLR + + CONTRACTL + { + GC_TRIGGERS; + MODE_ANY; + NOTHROW; + PRECONDITION(GetThread() != NULL); + PRECONDITION(!(GetThread()->GetExceptionState()->GetFlags()->GotWatsonBucketDetails())); + PRECONDITION(adjustedIp != NULL); + PRECONDITION(IsWatsonEnabled()); + } + CONTRACTL_END; + + Thread *pThread = GetThread(); + + // If we dont already have the bucketing details for the exception + // being thrown, then get them. + ThreadExceptionState *pExState = pThread->GetExceptionState(); + + // Ensure that the exception tracker exists + _ASSERTE(pExState->GetCurrentExceptionTracker() != NULL); + + // Switch to COOP mode + GCX_COOP(); + + // Get the throwable for the exception being thrown + struct + { + OBJECTREF oCurrentThrowable; + OBJECTREF oInnerMostExceptionThrowable; + } gc; + ZeroMemory(&gc, sizeof(gc)); + + GCPROTECT_BEGIN(gc); + + gc.oCurrentThrowable = pExState->GetThrowable(); + + // Check if the exception object is preallocated or not + BOOL fIsPreallocatedException = CLRException::IsPreallocatedExceptionObject(gc.oCurrentThrowable); + + // Get the WatsonBucketTracker for the current exception + PTR_EHWatsonBucketTracker pWatsonBucketTracker = pExState->GetCurrentExceptionTracker()->GetWatsonBucketTracker(); + + // Get the innermost exception object (if any) + gc.oInnerMostExceptionThrowable = ((EXCEPTIONREF)gc.oCurrentThrowable)->GetBaseException(); + + // By default, assume that no watson bucketing details are available and inner exception + // is not preallocated + BOOL fAreBucketingDetailsPresent = FALSE; + BOOL fIsInnerExceptionPreallocated = FALSE; + + // Check if this is a thread abort exception of any kind. See IsThrowableThreadAbortException implementation for details. + // We shouldnt use the thread state as well to determine if it is a TAE since, in cases like throwing a cached exception + // as part of type initialization failure, we could throw a TAE but the thread will not be in abort state (which is expected). + BOOL fIsThreadAbortException = IsThrowableThreadAbortException(gc.oCurrentThrowable); + + // If we are here, then this was a new exception raised + // from outside the managed EH clauses (fault/finally/catch). + // + // The throwable *may* have the bucketing details already + // if this exception was raised when it was crossing over + // an AD transition boundary. Those are stored in UE watson bucket + // tracker by AppDomainTransitionExceptionFilter. + if (fIsPreallocatedException) + { + PTR_EHWatsonBucketTracker pUEWatsonBucketTracker = pExState->GetUEWatsonBucketTracker(); + fAreBucketingDetailsPresent = ((pUEWatsonBucketTracker->RetrieveWatsonBucketIp() != NULL) && + (pUEWatsonBucketTracker->RetrieveWatsonBuckets() != NULL)); + + // If they are present, copy them over to the watson tracker for the exception + // being processed. + if (fAreBucketingDetailsPresent) + { +#ifdef _DEBUG + // Under OOM scenarios, its possible that when we are raising a threadabort, + // the throwable may get converted to preallocated OOM object when RaiseTheExceptionInternalOnly + // invokes Thread::SafeSetLastThrownObject. We check if this is the current case and use it in + // our validation below. + BOOL fIsPreallocatedOOMExceptionForTA = FALSE; + if ((!fIsThreadAbortException) && pUEWatsonBucketTracker->CapturedForThreadAbort()) + { + fIsPreallocatedOOMExceptionForTA = (gc.oCurrentThrowable == CLRException::GetPreallocatedOutOfMemoryException()); + if (fIsPreallocatedOOMExceptionForTA) + { + LOG((LF_EH, LL_INFO100, "SetupInitialThrowBucketDetails - Got preallocated OOM throwable for buckets captured for thread abort.\n")); + } + } +#endif // _DEBUG + // These should have been captured at AD transition OR + // could be bucketing details of preallocated [rude] thread abort exception. + _ASSERTE(pUEWatsonBucketTracker->CapturedAtADTransition() || + ((fIsThreadAbortException || fIsPreallocatedOOMExceptionForTA) && pUEWatsonBucketTracker->CapturedForThreadAbort())); + + if (!fIsThreadAbortException) + { + // The watson bucket tracker for the exceptiong being raised should be empty at this point + // since we are here because of a cross AD reraise of the original exception. + _ASSERTE((pWatsonBucketTracker->RetrieveWatsonBucketIp() == NULL) && (pWatsonBucketTracker->RetrieveWatsonBuckets() == NULL)); + + // Copy the buckets over to it + pWatsonBucketTracker->CopyEHWatsonBucketTracker(*(pUEWatsonBucketTracker)); + if (pWatsonBucketTracker->RetrieveWatsonBuckets() == NULL) + { + // If we dont have buckets after the copy operation, its due to us running out of + // memory. + LOG((LF_EH, LL_INFO100, "SetupInitialThrowBucketDetails - Unable to copy watson buckets from cross AD rethrow, likely due to out of memory.\n")); + } + else + { + LOG((LF_EH, LL_INFO100, "SetupInitialThrowBucketDetails - Copied watson buckets from cross AD rethrow.\n")); + } + } + else + { + // Thread abort watson bucket details are already present in the + // UE watson bucket tracker. + LOG((LF_EH, LL_INFO100, "SetupInitialThrowBucketDetails - Already have watson buckets for preallocated thread abort reraise.\n")); + } + } + else if (fIsThreadAbortException) + { + // This is a preallocated thread abort exception. + UINT_PTR ip = pUEWatsonBucketTracker->RetrieveWatsonBucketIp(); + if (ip != NULL) + { + // Since we have the IP, assert that this was the one setup + // for ThreadAbort. This is for the reraise scenario where + // the original exception was non-preallocated TA but the + // reraise resulted in preallocated TA. + // + // In this case, we will update the ip to be used as the + // one we have. The control flow below will automatically + // endup using it. + _ASSERTE(pUEWatsonBucketTracker->CapturedForThreadAbort()); + adjustedIp = ip; + LOG((LF_EH, LL_INFO100, "SetupInitialThrowBucketDetails - Setting an existing IP (%p) to be used for capturing buckets for preallocated thread abort.\n", ip)); + goto phase1; + } + } + + if (!fAreBucketingDetailsPresent || !fIsThreadAbortException) + { + // Clear the UE Watson bucket tracker so that its usable + // in future. We dont clear this for ThreadAbort since + // the UE watson bucket tracker carries bucketing details + // for the same, unless the UE tracker is not containing them + // already. + pUEWatsonBucketTracker->ClearWatsonBucketDetails(); + } + } + else + { + // The exception object is not preallocated + fAreBucketingDetailsPresent = ((EXCEPTIONREF)gc.oCurrentThrowable)->AreWatsonBucketsPresent(); + if (!fAreBucketingDetailsPresent) + { + // If buckets are not present, check if the bucketing IP is present. + fAreBucketingDetailsPresent = ((EXCEPTIONREF)gc.oCurrentThrowable)->IsIPForWatsonBucketsPresent(); + } + + // If throwable does not have buckets and this is a thread abort exception, + // then this maybe a reraise of the original thread abort. + // + // We can also be here if an exception was caught at AppDomain transition and + // in the returning domain, a non-preallocated TAE was raised. In such a case, + // the UE tracker flags could indicate the exception is from AD transition. + // This is similar to preallocated case above. + // + // Check the UE Watson bucket tracker if it has the buckets and if it does, + // copy them over to the current throwable. + if (!fAreBucketingDetailsPresent && fIsThreadAbortException) + { + PTR_EHWatsonBucketTracker pUEWatsonBucketTracker = pExState->GetUEWatsonBucketTracker(); + UINT_PTR ip = pUEWatsonBucketTracker->RetrieveWatsonBucketIp(); + if (ip != NULL) + { + // Confirm that we had the buckets captured for thread abort + _ASSERTE(pUEWatsonBucketTracker->CapturedForThreadAbort() || pUEWatsonBucketTracker->CapturedAtADTransition()); + + if (pUEWatsonBucketTracker->RetrieveWatsonBuckets() != NULL) + { + // Copy the buckets to the current throwable - CopyWatsonBucketsToThrowable + // can throw in OOM. However, since the current function is called as part of + // setting up the stack trace, where we bail out incase of OOM, we will do + // no different here as well. + BOOL fCopiedBuckets = TRUE; + EX_TRY + { + CopyWatsonBucketsToThrowable(pUEWatsonBucketTracker->RetrieveWatsonBuckets()); + _ASSERTE(((EXCEPTIONREF)gc.oCurrentThrowable)->AreWatsonBucketsPresent()); + } + EX_CATCH + { + fCopiedBuckets = FALSE; + } + EX_END_CATCH(SwallowAllExceptions); + + if (fCopiedBuckets) + { + // Since the throwable has the buckets, set the flag that indicates so + fAreBucketingDetailsPresent = TRUE; + LOG((LF_EH, LL_INFO100, "SetupInitialThrowBucketDetails - Setup watson buckets for thread abort reraise.\n")); + } + } + else + { + // Copy the faulting IP from the UE tracker to the exception object. This was setup in COMPlusCheckForAbort + // for non-preallocated exceptions. + ((EXCEPTIONREF)gc.oCurrentThrowable)->SetIPForWatsonBuckets(ip); + fAreBucketingDetailsPresent = TRUE; + LOG((LF_EH, LL_INFO100, "SetupInitialThrowBucketDetails - Setup watson bucket IP for thread abort reraise.\n")); + } + } + else + { + // Clear the UE Watson bucket tracker so that its usable + // in future. + pUEWatsonBucketTracker->ClearWatsonBucketDetails(); + LOG((LF_EH, LL_INFO100, "SetupInitialThrowBucketDetails - Didnt find watson buckets for thread abort - likely being raised.\n")); + } + } + } + +phase1: + if (fAreBucketingDetailsPresent) + { + // Since we already have the buckets, simply bail out + LOG((LF_EH, LL_INFO100, "SetupInitialThrowBucketDetails - Already had watson ip/buckets.\n")); + goto done; + } + + // Check if an inner most exception exists and if it does, examine + // it for watson bucketing details. + if (gc.oInnerMostExceptionThrowable != NULL) + { + // Preallocated exception objects do not have inner exception objects. + // Thus, if we are here, then the current throwable cannot be + // a preallocated exception object. + _ASSERTE(!fIsPreallocatedException); + + fIsInnerExceptionPreallocated = CLRException::IsPreallocatedExceptionObject(gc.oInnerMostExceptionThrowable); + + // If we are here, then this was a "throw" with inner exception + // outside of any managed EH clauses. + // + // If the inner exception object is preallocated, then we will need to create the + // watson buckets since we are outside the managed EH clauses with no exception tracking + // information relating to the inner exception. + // + // But if the inner exception object was not preallocated, create new watson buckets + // only if inner exception does not have them. + if (fIsInnerExceptionPreallocated) + { + fAreBucketingDetailsPresent = FALSE; + } + else + { + // Do we have either the IP for Watson buckets or the buckets themselves? + fAreBucketingDetailsPresent = (((EXCEPTIONREF)gc.oInnerMostExceptionThrowable)->AreWatsonBucketsPresent() || + ((EXCEPTIONREF)gc.oInnerMostExceptionThrowable)->IsIPForWatsonBucketsPresent()); + } + } + + if (!fAreBucketingDetailsPresent) + { + // Collect the bucketing details since they are not already present + pWatsonBucketTracker->SaveIpForWatsonBucket(adjustedIp); + + if (!fIsPreallocatedException || fIsThreadAbortException) + { + if (!fIsPreallocatedException) + { + // Save the IP for Watson bucketing in the exception object for non-preallocated exception + // objects + ((EXCEPTIONREF)gc.oCurrentThrowable)->SetIPForWatsonBuckets(adjustedIp); + + // Save the IP in the UE tracker as well for TAE if an abort is in progress + // since when we attempt reraise, the exception object is not available. Otherwise, + // treat the exception like a regular non-preallocated exception and not do anything else. + if (fIsThreadAbortException && pThread->IsAbortInitiated()) + { + PTR_EHWatsonBucketTracker pUEWatsonBucketTracker = pExState->GetUEWatsonBucketTracker(); + + pUEWatsonBucketTracker->ClearWatsonBucketDetails(); + pUEWatsonBucketTracker->SaveIpForWatsonBucket(adjustedIp); + + // Set the flag that we captured the IP for Thread abort + DEBUG_STMT(pUEWatsonBucketTracker->SetCapturedForThreadAbort()); + LOG((LF_EH, LL_INFO100, "SetupInitialThrowBucketDetails - Saved bucket IP for initial thread abort raise.\n")); + } + } + else + { + // Create the buckets proactively for preallocated threadabort exception + pWatsonBucketTracker->CaptureUnhandledInfoForWatson(TypeOfReportedError::UnhandledException, pThread, &gc.oCurrentThrowable); + PTR_VOID pUnmanagedBuckets = pWatsonBucketTracker->RetrieveWatsonBuckets(); + if(pUnmanagedBuckets != NULL) + { + // Copy the details over to the UE Watson bucket tracker so that we can use them if the exception + // is "reraised" after invoking the catch block. + // + // Since we can be here for preallocated threadabort exception when UE Tracker is simply + // carrying the IP (that has been copied to pWatsonBucketTracker and buckets captured for it), + // we will need to clear UE tracker so that we can copy over the captured buckets. + PTR_EHWatsonBucketTracker pUEWatsonBucketTracker = pExState->GetUEWatsonBucketTracker(); + pUEWatsonBucketTracker->ClearWatsonBucketDetails(); + + // Copy over the buckets from the current tracker that captured them. + pUEWatsonBucketTracker->CopyEHWatsonBucketTracker(*(pWatsonBucketTracker)); + + // Buckets should be present now (unless the copy operation had an OOM) + if (pUEWatsonBucketTracker->RetrieveWatsonBuckets() != NULL) + { + // Set the flag that we captured buckets for Thread abort + DEBUG_STMT(pUEWatsonBucketTracker->SetCapturedForThreadAbort()); + LOG((LF_EH, LL_INFO100, "SetupInitialThrowBucketDetails - Saved buckets for Watson Bucketing for initial thread abort raise.\n")); + } + else + { + // If we are here, then the bucket copy operation (above) failed due to OOM. + LOG((LF_EH, LL_INFO100, "SetupInitialThrowBucketDetails - Unable to save buckets for Watson Bucketing for initial thread abort raise, likely due to OOM.\n")); + pUEWatsonBucketTracker->ClearWatsonBucketDetails(); + } + } + else + { + // Watson helper function can bail out on us under OOM scenarios and return a NULL. + // We cannot do much in such a case. + LOG((LF_EH, LL_INFO100, "SetupInitialThrowBucketDetails - No buckets were captured and returned to us for initial thread abort raise. Likely encountered an OOM.\n")); + } + + // Clear the buckets since we no longer need them + pWatsonBucketTracker->ClearWatsonBucketDetails(); + } + } + else + { + // We have already saved the throw site IP for bucketing the non-ThreadAbort preallocated exceptions + LOG((LF_EH, LL_INFO100, "SetupInitialThrowBucketDetails - Saved IP (%p) for Watson Bucketing for a preallocated exception\n", adjustedIp)); + } + } + else + { + // The inner exception object should be having either the IP for watson bucketing or the buckets themselves. + // We shall copy over, whatever is available, to the current exception object. + _ASSERTE(gc.oInnerMostExceptionThrowable != NULL); + _ASSERTE(((EXCEPTIONREF)gc.oInnerMostExceptionThrowable)->AreWatsonBucketsPresent() || + ((EXCEPTIONREF)gc.oInnerMostExceptionThrowable)->IsIPForWatsonBucketsPresent()); + + if (((EXCEPTIONREF)gc.oInnerMostExceptionThrowable)->AreWatsonBucketsPresent()) + { + EX_TRY + { + // Copy the bucket details from innermost exception to the current exception object. + CopyWatsonBucketsFromThrowableToCurrentThrowable(gc.oInnerMostExceptionThrowable); + } + EX_CATCH + { + } + EX_END_CATCH(SwallowAllExceptions); + + LOG((LF_EH, LL_INFO100, "SetupInitialThrowBucketDetails - Copied watson bucket details from the innermost exception\n")); + } + else + { + // Copy the IP to the current exception object + ((EXCEPTIONREF)gc.oCurrentThrowable)->SetIPForWatsonBuckets(((EXCEPTIONREF)gc.oInnerMostExceptionThrowable)->GetIPForWatsonBuckets()); + LOG((LF_EH, LL_INFO100, "SetupInitialThrowBucketDetails - Copied watson bucket IP from the innermost exception\n")); + } + } + +done: + // Set the flag that we have got the bucketing details + pExState->GetFlags()->SetGotWatsonBucketDetails(); + + GCPROTECT_END(); + +#endif // !DACCESS_COMPILE +} + +// This function is a wrapper to copy the watson bucket byte[] from the specified +// throwable to the current throwable. +void CopyWatsonBucketsFromThrowableToCurrentThrowable(OBJECTREF oThrowableFrom) +{ +#ifndef DACCESS_COMPILE + + CONTRACTL + { + GC_TRIGGERS; + MODE_COOPERATIVE; + THROWS; + PRECONDITION(oThrowableFrom != NULL); + PRECONDITION(!CLRException::IsPreallocatedExceptionObject(oThrowableFrom)); + PRECONDITION(((EXCEPTIONREF)oThrowableFrom)->AreWatsonBucketsPresent()); + PRECONDITION(IsWatsonEnabled()); + } + CONTRACTL_END; + + struct + { + OBJECTREF oThrowableFrom; + } _gc; + + ZeroMemory(&_gc, sizeof(_gc)); + GCPROTECT_BEGIN(_gc); + _gc.oThrowableFrom = oThrowableFrom; + + // Copy the watson buckets to the current throwable by NOT passing + // the second argument that will default to NULL. + // + // CopyWatsonBucketsBetweenThrowables will pass that NULL to + // CopyWatsonBucketsToThrowables that will make it copy the buckets + // to the current throwable. + CopyWatsonBucketsBetweenThrowables(_gc.oThrowableFrom); + + GCPROTECT_END(); + +#endif // !DACCESS_COMPILE +} + +// This function will copy the watson bucket byte[] from the source +// throwable to the destination throwable. +// +// If the destination throwable is NULL, it will result in the buckets +// being copied to the current throwable. +void CopyWatsonBucketsBetweenThrowables(OBJECTREF oThrowableFrom, OBJECTREF oThrowableTo /*=NULL*/) +{ +#ifndef DACCESS_COMPILE + + CONTRACTL + { + GC_TRIGGERS; + MODE_COOPERATIVE; + THROWS; + PRECONDITION(oThrowableFrom != NULL); + PRECONDITION(!CLRException::IsPreallocatedExceptionObject(oThrowableFrom)); + PRECONDITION(((EXCEPTIONREF)oThrowableFrom)->AreWatsonBucketsPresent()); + PRECONDITION(IsWatsonEnabled()); + } + CONTRACTL_END; + + BOOL fRetVal = FALSE; + + struct + { + OBJECTREF oFrom; + OBJECTREF oTo; + OBJECTREF oWatsonBuckets; + } _gc; + + ZeroMemory(&_gc, sizeof(_gc)); + GCPROTECT_BEGIN(_gc); + + _gc.oFrom = oThrowableFrom; + _gc.oTo = (oThrowableTo == NULL)?GetThread()->GetThrowable():oThrowableTo; + _ASSERTE(_gc.oTo != NULL); + + // The target throwable to which Watson buckets are going to be copied + // shouldnt be preallocated exception object. + _ASSERTE(!CLRException::IsPreallocatedExceptionObject(_gc.oTo)); + + // Size of a watson bucket + DWORD size = sizeof(GenericModeBlock); + + // Create the managed byte[] to hold the bucket details + _gc.oWatsonBuckets = AllocatePrimitiveArray(ELEMENT_TYPE_U1, size); + if (_gc.oWatsonBuckets == NULL) + { + // return failure if failed to create bucket array + fRetVal = FALSE; + } + else + { + // Get the raw array data pointer of the source array + U1ARRAYREF refSourceWatsonBucketArray = ((EXCEPTIONREF)_gc.oFrom)->GetWatsonBucketReference(); + PTR_VOID pRawSourceWatsonBucketArray = dac_cast<PTR_VOID>(refSourceWatsonBucketArray->GetDataPtr()); + + // Get the raw array data pointer to the destination array + U1ARRAYREF refDestWatsonBucketArray = (U1ARRAYREF)_gc.oWatsonBuckets; + PTR_VOID pRawDestWatsonBucketArray = dac_cast<PTR_VOID>(refDestWatsonBucketArray->GetDataPtr()); + + // Deep copy the bucket information to the managed array + memcpyNoGCRefs(pRawDestWatsonBucketArray, pRawSourceWatsonBucketArray, size); + + // Setup the managed field reference to point to the byte array. + // + // The throwable, to which the buckets are being copied to, may be + // having existing buckets (e.g. when TypeInitialization exception + // maybe thrown again when attempt is made to load the originally + // failed type). + // + // This is also possible if exception object is used as singleton + // and thrown by multiple threads. + if (((EXCEPTIONREF)_gc.oTo)->AreWatsonBucketsPresent()) + { + LOG((LF_EH, LL_INFO1000, "CopyWatsonBucketsBetweenThrowables: Throwable (%p) being copied to had previous buckets.\n", OBJECTREFToObject(_gc.oTo))); + } + + ((EXCEPTIONREF)_gc.oTo)->SetWatsonBucketReference(_gc.oWatsonBuckets); + + fRetVal = TRUE; + } + + // We shouldn't be here when fRetVal is FALSE since failure to allocate the primitive + // array should throw an OOM. + _ASSERTE(fRetVal); + + GCPROTECT_END(); +#endif // !DACCESS_COMPILE +} + +// This function will copy the watson bucket information to the managed byte[] in +// the specified managed exception object. +// +// If throwable is not specified, it will be copied to the current throwable. +// +// pUnmanagedBuckets is a pointer to native memory that cannot be affected by GC. +BOOL CopyWatsonBucketsToThrowable(PTR_VOID pUnmanagedBuckets, OBJECTREF oTargetThrowable /*= NULL*/) +{ +#ifndef DACCESS_COMPILE + + CONTRACTL + { + GC_TRIGGERS; + MODE_COOPERATIVE; + THROWS; + PRECONDITION(GetThread() != NULL); + PRECONDITION(pUnmanagedBuckets != NULL); + PRECONDITION(!CLRException::IsPreallocatedExceptionObject((oTargetThrowable == NULL)?GetThread()->GetThrowable():oTargetThrowable)); + PRECONDITION(IsWatsonEnabled()); + } + CONTRACTL_END; + + BOOL fRetVal = TRUE; + struct + { + OBJECTREF oThrowable; + OBJECTREF oWatsonBuckets; + } _gc; + + ZeroMemory(&_gc, sizeof(_gc)); + GCPROTECT_BEGIN(_gc); + _gc.oThrowable = (oTargetThrowable == NULL)?GetThread()->GetThrowable():oTargetThrowable; + + // Throwable to which buckets should be copied to, must exist. + _ASSERTE(_gc.oThrowable != NULL); + + // Size of a watson bucket + DWORD size = sizeof(GenericModeBlock); + + _gc.oWatsonBuckets = AllocatePrimitiveArray(ELEMENT_TYPE_U1, size); + if (_gc.oWatsonBuckets == NULL) + { + // return failure if failed to create bucket array + fRetVal = FALSE; + } + else + { + // Get the raw array data pointer + U1ARRAYREF refWatsonBucketArray = (U1ARRAYREF)_gc.oWatsonBuckets; + PTR_VOID pRawWatsonBucketArray = dac_cast<PTR_VOID>(refWatsonBucketArray->GetDataPtr()); + + // Deep copy the bucket information to the managed array + memcpyNoGCRefs(pRawWatsonBucketArray, pUnmanagedBuckets, size); + + // Setup the managed field reference to point to the byte array. + // + // The throwable, to which the buckets are being copied to, may be + // having existing buckets (e.g. when TypeInitialization exception + // maybe thrown again when attempt is made to load the originally + // failed type). + // + // This is also possible if exception object is used as singleton + // and thrown by multiple threads. + if (((EXCEPTIONREF)_gc.oThrowable)->AreWatsonBucketsPresent()) + { + LOG((LF_EH, LL_INFO1000, "CopyWatsonBucketsToThrowable: Throwable (%p) being copied to had previous buckets.\n", OBJECTREFToObject(_gc.oThrowable))); + } + + ((EXCEPTIONREF)_gc.oThrowable)->SetWatsonBucketReference(_gc.oWatsonBuckets); + } + + GCPROTECT_END(); + + return fRetVal; +#else // DACCESS_COMPILE + return TRUE; +#endif // !DACCESS_COMPILE +} + +// This function will setup the bucketing information for nested exceptions +// raised. These would be any exceptions thrown from within the confines of +// managed EH clauses and include "rethrow" and "throw new ...". +// +// This is called from within CLR's personality routine for managed +// exceptions to preemptively setup the watson buckets from the ones that may +// already exist. If none exist already, we will automatically endup in the +// path (SetupInitialThrowBucketDetails) that will set up buckets for the +// exception being thrown. +void SetStateForWatsonBucketing(BOOL fIsRethrownException, OBJECTHANDLE ohOriginalException) +{ +#ifndef DACCESS_COMPILE + +#ifdef FEATURE_CORECLR + // On CoreCLR, Watson may not be enabled. Thus, we should + // skip this. + if (!IsWatsonEnabled()) + { + return; + } +#endif // FEATURE_CORECLR + + CONTRACTL + { + GC_TRIGGERS; + MODE_ANY; + NOTHROW; + PRECONDITION(GetThread() != NULL); + PRECONDITION(IsWatsonEnabled()); + } + CONTRACTL_END; + + // Switch to COOP mode + GCX_COOP(); + + struct + { + OBJECTREF oCurrentThrowable; + OBJECTREF oInnerMostExceptionThrowable; + } gc; + ZeroMemory(&gc, sizeof(gc)); + GCPROTECT_BEGIN(gc); + + Thread* pThread = GetThread(); + + // Get the current exception state of the thread + ThreadExceptionState* pCurExState = pThread->GetExceptionState(); + _ASSERTE(NULL != pCurExState); + + // Ensure that the exception tracker exists + _ASSERTE(pCurExState->GetCurrentExceptionTracker() != NULL); + + // Get the current throwable + gc.oCurrentThrowable = pThread->GetThrowable(); + _ASSERTE(gc.oCurrentThrowable != NULL); + + // Is the throwable a preallocated exception object? + BOOL fIsPreallocatedExceptionObject = CLRException::IsPreallocatedExceptionObject(gc.oCurrentThrowable); + + // Copy the bucketing details from the original exception tracker if the current exception is a rethrow + // AND the throwable is a preallocated exception object. + // + // For rethrown non-preallocated exception objects, the throwable would already have the bucketing + // details inside it. + if (fIsRethrownException) + { + if (fIsPreallocatedExceptionObject) + { + // Get the WatsonBucket tracker for the original exception, starting search from the previous EH tracker. + // This is required so that when a preallocated exception is rethrown, then the current tracker would have + // the same throwable as the original exception but no bucketing details. + // + // To ensure GetWatsonBucketTrackerForPreallocatedException uses the EH tracker corresponding to the original + // exception to get the bucketing details, we pass TRUE as the third parameter. + PTR_EHWatsonBucketTracker pPreallocWatsonBucketTracker = GetWatsonBucketTrackerForPreallocatedException(gc.oCurrentThrowable, FALSE, TRUE); + if (pPreallocWatsonBucketTracker != NULL) + { + if (!IsThrowableThreadAbortException(gc.oCurrentThrowable)) + { + // For non-thread abort preallocated exceptions, we copy the bucketing details + // from their corresponding watson bucket tracker to the one corresponding to the + // rethrow that is taking place. + // + // Bucketing details for preallocated exception may not be present if the exception came + // from across AD transition and we attempted to copy them over from the UETracker, when + // the exception was reraised in the calling AD, and the copy operation failed due to OOM. + // + // In such a case, when the reraised exception is caught and rethrown, we will not have + // any bucketing details. + if (NULL != pPreallocWatsonBucketTracker->RetrieveWatsonBucketIp()) + { + // Copy the bucketing details now + pCurExState->GetCurrentExceptionTracker()->GetWatsonBucketTracker()->CopyEHWatsonBucketTracker(*pPreallocWatsonBucketTracker); + } + else + { + LOG((LF_EH, LL_INFO1000, "SetStateForWatsonBucketing - Watson bucketing details for rethrown preallocated exception not found in the EH tracker corresponding to the original exception. This is likely due to a previous OOM.\n")); + LOG((LF_EH, LL_INFO1000, ">>>>>>>>>>>>>>>>>>>>>>>>>> Original WatsonBucketTracker = %p\n", pPreallocWatsonBucketTracker)); + + // Make the active tracker clear + pCurExState->GetCurrentExceptionTracker()->GetWatsonBucketTracker()->ClearWatsonBucketDetails(); + } + } + #ifdef _DEBUG + else + { + // For thread abort exceptions, the returned watson bucket tracker + // would correspond to UE Watson bucket tracker and it will have + // all the details. + _ASSERTE(pPreallocWatsonBucketTracker == pCurExState->GetUEWatsonBucketTracker()); + } + #endif // _DEBUG + } + else + { + // OOM can result in not having a Watson bucket tracker with valid bucketing details for a preallocated exception. + // Thus, we may end up here. For details, see implementation of GetWatsonBucketTrackerForPreallocatedException. + LOG((LF_EH, LL_INFO1000, "SetStateForWatsonBucketing - Watson bucketing tracker for rethrown preallocated exception not found. This is likely due to a previous OOM.\n")); + + // Make the active tracker clear + pCurExState->GetCurrentExceptionTracker()->GetWatsonBucketTracker()->ClearWatsonBucketDetails(); + } + } + else + { + // We dont need to do anything here since the throwable would already have the bucketing + // details inside it. Simply assert that the original exception object is the same as the current throwable. + // + // We cannot assert for Watson buckets since the original throwable may not have got them in + // SetupInitialThrowBucketDetails due to OOM + _ASSERTE((NULL != ohOriginalException) && (ObjectFromHandle(ohOriginalException) == gc.oCurrentThrowable)); + if ((((EXCEPTIONREF)gc.oCurrentThrowable)->AreWatsonBucketsPresent() == FALSE) && + (((EXCEPTIONREF)gc.oCurrentThrowable)->IsIPForWatsonBucketsPresent() == FALSE)) + { + LOG((LF_EH, LL_INFO1000, "SetStateForWatsonBucketing - Regular rethrown exception (%p) does not have Watson buckets, likely due to OOM.\n", + OBJECTREFToObject(gc.oCurrentThrowable))); + } + } + + // Set the flag that we have bucketing details for the exception + pCurExState->GetFlags()->SetGotWatsonBucketDetails(); + LOG((LF_EH, LL_INFO1000, "SetStateForWatsonBucketing - Using original exception details for Watson bucketing for rethrown exception.\n")); + } + else + { + // If we are here, then an exception is being thrown from within the + // managed EH clauses of fault, finally or catch, with an inner exception. + + // By default, we will create buckets based upon the exception being thrown unless + // thrown exception has an inner exception that has got bucketing details + BOOL fCreateBucketsForExceptionBeingThrown = TRUE; + + // Start off by assuming that inner exception object is not preallocated + BOOL fIsInnerExceptionPreallocated = FALSE; + + // Reference to the WatsonBucket tracker for the inner exception, if it is preallocated + PTR_EHWatsonBucketTracker pInnerExceptionWatsonBucketTracker = NULL; + + // Since this is a new exception being thrown, we will check if it has buckets already or not. + // This is possible when Reflection throws TargetInvocationException with an inner exception + // that is preallocated exception object. In such a case, we copy the inner exception details + // to the TargetInvocationException object already. This is done in InvokeImpl in ReflectionInvocation.cpp. + if (((EXCEPTIONREF)gc.oCurrentThrowable)->AreWatsonBucketsPresent() || + ((EXCEPTIONREF)gc.oCurrentThrowable)->IsIPForWatsonBucketsPresent()) + { + goto done; + } + + // If no buckets are present, then we will check if it has an innermost exception or not. + // If it does, then we will make the exception being thrown use the bucketing details of the + // innermost exception. + // + // If there is no innermost exception or if one is present without bucketing details, then + // we will have bucket details based upon the exception being thrown. + + // Get the innermost exception from the exception being thrown. + gc.oInnerMostExceptionThrowable = ((EXCEPTIONREF)gc.oCurrentThrowable)->GetBaseException(); + if (gc.oInnerMostExceptionThrowable != NULL) + { + fIsInnerExceptionPreallocated = CLRException::IsPreallocatedExceptionObject(gc.oInnerMostExceptionThrowable); + + // Preallocated exception objects do not have inner exception objects. + // Thus, if we are here, then the current throwable cannot be + // a preallocated exception object. + _ASSERTE(!fIsPreallocatedExceptionObject); + + // Create the new buckets only if the innermost exception object + // does not have them already. + if (fIsInnerExceptionPreallocated) + { + // If we are able to find the watson bucket tracker for the preallocated + // inner exception, then we dont need to create buckets for throw site. + pInnerExceptionWatsonBucketTracker = GetWatsonBucketTrackerForPreallocatedException(gc.oInnerMostExceptionThrowable, FALSE, TRUE); + fCreateBucketsForExceptionBeingThrown = ((pInnerExceptionWatsonBucketTracker != NULL) && + (pInnerExceptionWatsonBucketTracker->RetrieveWatsonBucketIp() != NULL)) ? FALSE : TRUE; + } + else + { + // Since the inner exception object is not preallocated, create + // watson buckets only if it does not have them. + fCreateBucketsForExceptionBeingThrown = !(((EXCEPTIONREF)gc.oInnerMostExceptionThrowable)->AreWatsonBucketsPresent() || + ((EXCEPTIONREF)gc.oInnerMostExceptionThrowable)->IsIPForWatsonBucketsPresent()); + } + } + + // If we are NOT going to create buckets for the thrown exception, + // then copy them over from the inner exception object. + // + // If we have to create the buckets for the thrown exception, + // we wont do that now - it will be done in StackTraceInfo::AppendElement + // when we get the IP for bucketing. + if (!fCreateBucketsForExceptionBeingThrown) + { + // Preallocated exception objects do not have inner exception objects. + // Thus, if we are here, then the current throwable cannot be + // a preallocated exception object. + _ASSERTE(!fIsPreallocatedExceptionObject); + + if (fIsInnerExceptionPreallocated) + { + + // We should have the inner exception watson bucket tracker + _ASSERTE((pInnerExceptionWatsonBucketTracker != NULL) && (pInnerExceptionWatsonBucketTracker->RetrieveWatsonBucketIp() != NULL)); + + // Capture the buckets for the innermost exception if they dont already exist. + // Since the current throwable cannot be preallocated (see the assert above), + // copy the buckets to the throwable. + PTR_VOID pInnerExceptionWatsonBuckets = pInnerExceptionWatsonBucketTracker->RetrieveWatsonBuckets(); + if (pInnerExceptionWatsonBuckets == NULL) + { + // Capture the buckets since they dont exist + pInnerExceptionWatsonBucketTracker->CaptureUnhandledInfoForWatson(TypeOfReportedError::UnhandledException, pThread, &gc.oInnerMostExceptionThrowable); + pInnerExceptionWatsonBuckets = pInnerExceptionWatsonBucketTracker->RetrieveWatsonBuckets(); + } + + if (pInnerExceptionWatsonBuckets == NULL) + { + // Couldnt capture details like due to OOM + LOG((LF_EH, LL_INFO1000, "SetStateForWatsonBucketing - Preallocated inner-exception's WBTracker (%p) has no bucketing details for the thrown exception, likely due to OOM.\n", pInnerExceptionWatsonBucketTracker)); + } + else + { + // Copy the buckets to the current throwable + BOOL fCopied = TRUE; + EX_TRY + { + fCopied = CopyWatsonBucketsToThrowable(pInnerExceptionWatsonBuckets); + _ASSERTE(fCopied); + } + EX_CATCH + { + // Dont do anything if we fail to copy the buckets - this is no different than + // the native watson helper functions failing under OOM + fCopied = FALSE; + } + EX_END_CATCH(SwallowAllExceptions); + } + } + else + { + // Assert that the inner exception has the Watson buckets + _ASSERTE(gc.oInnerMostExceptionThrowable != NULL); + _ASSERTE(((EXCEPTIONREF)gc.oInnerMostExceptionThrowable)->AreWatsonBucketsPresent() || + ((EXCEPTIONREF)gc.oInnerMostExceptionThrowable)->IsIPForWatsonBucketsPresent()); + + if (((EXCEPTIONREF)gc.oInnerMostExceptionThrowable)->AreWatsonBucketsPresent()) + { + // Copy the bucket information from the inner exception object to the current throwable + EX_TRY + { + CopyWatsonBucketsFromThrowableToCurrentThrowable(gc.oInnerMostExceptionThrowable); + } + EX_CATCH + { + // Dont do anything if we fail to copy the buckets - this is no different than + // the native watson helper functions failing under OOM + } + EX_END_CATCH(SwallowAllExceptions); + } + else + { + // Copy the IP for Watson bucketing to the exception object + ((EXCEPTIONREF)gc.oCurrentThrowable)->SetIPForWatsonBuckets(((EXCEPTIONREF)gc.oInnerMostExceptionThrowable)->GetIPForWatsonBuckets()); + } + } + + // Set the flag that we got bucketing details for the exception + pCurExState->GetFlags()->SetGotWatsonBucketDetails(); + LOG((LF_EH, LL_INFO1000, "SetStateForWatsonBucketing - Using innermost exception details for Watson bucketing for thrown exception.\n")); + } +done:; + } + + GCPROTECT_END(); + +#endif // !DACCESS_COMPILE +} + +// Constructor that will do the initialization of the object +EHWatsonBucketTracker::EHWatsonBucketTracker() +{ + LIMITED_METHOD_CONTRACT; + + Init(); +} + +// Reset the fields to default values +void EHWatsonBucketTracker::Init() +{ + LIMITED_METHOD_CONTRACT; + + m_WatsonUnhandledInfo.m_UnhandledIp = 0; + m_WatsonUnhandledInfo.m_pUnhandledBuckets = NULL; + + DEBUG_STMT(ResetFlags()); + + LOG((LF_EH, LL_INFO1000, "EHWatsonBucketTracker::Init - initializing watson bucket tracker (%p)\n", this)); +} + +// This method copies the bucketing details from the specified throwable +// to the current Watson Bucket tracker. +void EHWatsonBucketTracker::CopyBucketsFromThrowable(OBJECTREF oThrowable) +{ +#ifndef DACCESS_COMPILE + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + MODE_ANY; + PRECONDITION(oThrowable != NULL); + PRECONDITION(((EXCEPTIONREF)oThrowable)->AreWatsonBucketsPresent()); + PRECONDITION(IsWatsonEnabled()); + } + CONTRACTL_END; + + GCX_COOP(); + + struct + { + OBJECTREF oFrom; + } _gc; + + ZeroMemory(&_gc, sizeof(_gc)); + GCPROTECT_BEGIN(_gc); + + _gc.oFrom = oThrowable; + + LOG((LF_EH, LL_INFO1000, "EHWatsonBucketTracker::CopyEHWatsonBucketTracker - Copying bucketing details from throwable (%p) to tracker (%p)\n", + OBJECTREFToObject(_gc.oFrom), this)); + + // Watson bucket is a "GenericModeBlock" type. Set up an empty GenericModeBlock + // to hold the bucket parameters. + GenericModeBlock *pgmb = new (nothrow) GenericModeBlock; + if (pgmb == NULL) + { + // If we are unable to allocate memory to hold the WatsonBucket, then + // reset the IP and bucket pointer to NULL and bail out + SaveIpForWatsonBucket(NULL); + m_WatsonUnhandledInfo.m_pUnhandledBuckets = NULL; + } + else + { + // Get the raw array data pointer + U1ARRAYREF refWatsonBucketArray = ((EXCEPTIONREF)_gc.oFrom)->GetWatsonBucketReference(); + PTR_VOID pRawWatsonBucketArray = dac_cast<PTR_VOID>(refWatsonBucketArray->GetDataPtr()); + + // Copy over the details to our new allocation + memcpyNoGCRefs(pgmb, pRawWatsonBucketArray, sizeof(GenericModeBlock)); + + // and save the address where the buckets were copied + _ASSERTE(m_WatsonUnhandledInfo.m_pUnhandledBuckets == NULL); + m_WatsonUnhandledInfo.m_pUnhandledBuckets = pgmb; + } + + GCPROTECT_END(); + + LOG((LF_EH, LL_INFO1000, "EHWatsonBucketTracker::CopyEHWatsonBucketTracker - Copied Watson Buckets from throwable to (%p)\n", + m_WatsonUnhandledInfo.m_pUnhandledBuckets)); +#endif // !DACCESS_COMPILE +} + +// This method copies the bucketing details from the specified Watson Bucket tracker +// to the current one. +void EHWatsonBucketTracker::CopyEHWatsonBucketTracker(const EHWatsonBucketTracker& srcTracker) +{ +#ifndef DACCESS_COMPILE + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + MODE_ANY; + PRECONDITION(m_WatsonUnhandledInfo.m_UnhandledIp == 0); + PRECONDITION(m_WatsonUnhandledInfo.m_pUnhandledBuckets == NULL); + PRECONDITION(IsWatsonEnabled()); + } + CONTRACTL_END; + + LOG((LF_EH, LL_INFO1000, "EHWatsonBucketTracker::CopyEHWatsonBucketTracker - Copying bucketing details from %p to %p\n", &srcTracker, this)); + + // Copy the tracking details over from the specified tracker + SaveIpForWatsonBucket(srcTracker.m_WatsonUnhandledInfo.m_UnhandledIp); + + if (srcTracker.m_WatsonUnhandledInfo.m_pUnhandledBuckets != NULL) + { + // To save the bucket information, we will need to memcpy. + // This is to ensure that if the original watson bucket tracker + // (for original exception) is released and its memory deallocated, + // the new watson bucket tracker (for rethrown exception, for e.g.) + // would still have all the bucket details. + + // Watson bucket is a "GenericModeBlock" type. Set up an empty GenericModeBlock + // to hold the bucket parameters. + GenericModeBlock *pgmb = new (nothrow) GenericModeBlock; + if (pgmb == NULL) + { + // If we are unable to allocate memory to hold the WatsonBucket, then + // reset the IP and bucket pointer to NULL and bail out + SaveIpForWatsonBucket(NULL); + m_WatsonUnhandledInfo.m_pUnhandledBuckets = NULL; + + LOG((LF_EH, LL_INFO1000, "EHWatsonBucketTracker::CopyEHWatsonBucketTracker - Not copying buckets due to out of memory.\n")); + } + else + { + // Copy over the details to our new allocation + memcpyNoGCRefs(pgmb, srcTracker.m_WatsonUnhandledInfo.m_pUnhandledBuckets, sizeof(GenericModeBlock)); + + // and save the address where the buckets were copied + m_WatsonUnhandledInfo.m_pUnhandledBuckets = pgmb; + } + } + + LOG((LF_EH, LL_INFO1000, "EHWatsonBucketTracker::CopyEHWatsonBucketTracker - Copied Watson Bucket to (%p)\n", m_WatsonUnhandledInfo.m_pUnhandledBuckets)); +#endif // !DACCESS_COMPILE +} + +void EHWatsonBucketTracker::SaveIpForWatsonBucket( + UINT_PTR ip) // The new IP. +{ +#ifndef DACCESS_COMPILE + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + MODE_ANY; + SO_TOLERANT; + PRECONDITION(IsWatsonEnabled()); + } + CONTRACTL_END; + + LOG((LF_EH, LL_INFO1000, "EHWatsonBucketTracker::SaveIpForUnhandledInfo - this = %p, IP = %p\n", this, ip)); + + // Since we are setting a new IP for tracking buckets, + // clear any existing details we may hold + ClearWatsonBucketDetails(); + + // Save the new IP for bucketing + m_WatsonUnhandledInfo.m_UnhandledIp = ip; +#endif // !DACCESS_COMPILE +} + +UINT_PTR EHWatsonBucketTracker::RetrieveWatsonBucketIp() +{ + LIMITED_METHOD_CONTRACT; + + LOG((LF_EH, LL_INFO1000, "EHWatsonBucketTracker::RetrieveWatsonBucketIp - this = %p, IP = %p\n", this, m_WatsonUnhandledInfo.m_UnhandledIp)); + + return m_WatsonUnhandledInfo.m_UnhandledIp; +} + +// This function returns the reference to the Watson buckets tracked by the +// instance of WatsonBucket tracker. +// +// This is *also* invoked from the DAC when buckets are requested. +PTR_VOID EHWatsonBucketTracker::RetrieveWatsonBuckets() +{ +#if defined(FEATURE_CORECLR) && !defined(DACCESS_COMPILE) + if (!IsWatsonEnabled()) + { + return NULL; + } +#endif // defined(FEATURE_CORECLR) && !defined(DACCESS_COMPILE) + + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + MODE_ANY; + SO_TOLERANT; + PRECONDITION(IsWatsonEnabled()); + } + CONTRACTL_END; + + LOG((LF_EH, LL_INFO1000, "EHWatsonBucketTracker::RetrieveWatsonBuckets - this = %p, bucket address = %p\n", this, m_WatsonUnhandledInfo.m_pUnhandledBuckets)); + + return m_WatsonUnhandledInfo.m_pUnhandledBuckets; +} + +void EHWatsonBucketTracker::ClearWatsonBucketDetails() +{ +#ifndef DACCESS_COMPILE + +#ifdef FEATURE_CORECLR + if (!IsWatsonEnabled()) + { + return; + } +#endif // FEATURE_CORECLR + + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + MODE_ANY; + SO_TOLERANT; + PRECONDITION(IsWatsonEnabled()); + } + CONTRACTL_END; + + + LOG((LF_EH, LL_INFO1000, "EHWatsonBucketTracker::ClearWatsonBucketDetails for tracker (%p)\n", this)); + + if (m_WatsonUnhandledInfo.m_pUnhandledBuckets != NULL) + { + FreeBucketParametersForManagedException(m_WatsonUnhandledInfo.m_pUnhandledBuckets); + } + + Init(); +#endif // !DACCESS_COMPILE +} + +void EHWatsonBucketTracker::CaptureUnhandledInfoForWatson(TypeOfReportedError tore, Thread * pThread, OBJECTREF * pThrowable) +{ +#ifndef DACCESS_COMPILE + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + MODE_ANY; + PRECONDITION(IsWatsonEnabled()); + } + CONTRACTL_END; + + LOG((LF_EH, LL_INFO1000, "EHWatsonBucketTracker::CaptureUnhandledInfoForWatson capturing watson bucket details for (%p)\n", this)); + + // Only capture the bucket information if there is an IP AND we dont already have collected them. + // We could have collected them from a previous AD transition and wouldnt want to overwrite them. + if (m_WatsonUnhandledInfo.m_UnhandledIp != 0) + { + if (m_WatsonUnhandledInfo.m_pUnhandledBuckets == NULL) + { + // Get the bucket details since we dont have them + m_WatsonUnhandledInfo.m_pUnhandledBuckets = GetBucketParametersForManagedException(m_WatsonUnhandledInfo.m_UnhandledIp, tore, pThread, pThrowable); + LOG((LF_EH, LL_INFO1000, "EHWatsonBucketTracker::CaptureUnhandledInfoForWatson captured the following watson bucket details: (this = %p, bucket addr = %p)\n", + this, m_WatsonUnhandledInfo.m_pUnhandledBuckets)); + } + else + { + // We already have the bucket details - so no need to capture them again + LOG((LF_EH, LL_INFO1000, "EHWatsonBucketTracker::CaptureUnhandledInfoForWatson already have the watson bucket details: (this = %p, bucket addr = %p)\n", + this, m_WatsonUnhandledInfo.m_pUnhandledBuckets)); + } + } + else + { + LOG((LF_EH, LL_INFO1000, "EHWatsonBucketTracker::CaptureUnhandledInfoForWatson didnt have an IP to use for capturing watson buckets\n")); + } +#endif // !DACCESS_COMPILE +} +#endif // !FEATURE_PAL + +// Given a throwable, this function will attempt to find an active EH tracker corresponding to it. +// If none found, it will return NULL +#ifdef WIN64EXCEPTIONS +PTR_ExceptionTracker GetEHTrackerForException(OBJECTREF oThrowable, PTR_ExceptionTracker pStartingEHTracker) +#elif _TARGET_X86_ +PTR_ExInfo GetEHTrackerForException(OBJECTREF oThrowable, PTR_ExInfo pStartingEHTracker) +#else +#error Unsupported platform +#endif +{ + CONTRACTL + { + GC_NOTRIGGER; + MODE_COOPERATIVE; + NOTHROW; + SO_TOLERANT; + PRECONDITION(GetThread() != NULL); + PRECONDITION(oThrowable != NULL); + } + CONTRACTL_END; + + // Get the reference to the exception tracker to start with. If one has been provided to us, + // then use it. Otherwise, start from the current one. +#ifdef WIN64EXCEPTIONS + PTR_ExceptionTracker pEHTracker = (pStartingEHTracker != NULL) ? pStartingEHTracker : GetThread()->GetExceptionState()->GetCurrentExceptionTracker(); +#elif _TARGET_X86_ + PTR_ExInfo pEHTracker = (pStartingEHTracker != NULL) ? pStartingEHTracker : GetThread()->GetExceptionState()->GetCurrentExceptionTracker(); +#else +#error Unsupported platform +#endif + + BOOL fFoundTracker = FALSE; + + // Start walking the list to find the tracker correponding + // to the exception object. + while (pEHTracker != NULL) + { + if (pEHTracker->GetThrowable() == oThrowable) + { + // found the tracker - break out. + fFoundTracker = TRUE; + break; + } + + // move to the previous tracker... + pEHTracker = pEHTracker->GetPreviousExceptionTracker(); + } + + return fFoundTracker ? pEHTracker : NULL; +} + +#ifdef FEATURE_CORRUPTING_EXCEPTIONS +// ----------------------------------------------------------------------- +// Support for CorruptedState Exceptions +// ----------------------------------------------------------------------- + +// Given an exception code, this method returns a BOOL to indicate if the +// code belongs to a corrupting exception or not. +/* static */ +BOOL CEHelper::IsProcessCorruptedStateException(DWORD dwExceptionCode, BOOL fCheckForSO /*= TRUE*/) +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + MODE_ANY; + SO_TOLERANT; + } + CONTRACTL_END; + + if (g_pConfig->LegacyCorruptedStateExceptionsPolicy()) + { + return FALSE; + } + + // Call into the utilcode helper function to check if this + // is a CE or not. + return (::IsProcessCorruptedStateException(dwExceptionCode, fCheckForSO)); +} + +// This is used in the VM folder version of "SET_CE_RETHROW_FLAG_FOR_EX_CATCH" (in clrex.h) +// to check if the managed exception caught by EX_END_CATCH is CSE or not. +// +// If you are using it from rethrow boundaries (e.g. SET_CE_RETHROW_FLAG_FOR_EX_CATCH +// macro that is used to automatically rethrow corrupting exceptions), then you may +// want to set the "fMarkForReuseIfCorrupting" to TRUE to enable propagation of the +// corruption severity when the reraised exception is seen by managed code again. +/* static */ +BOOL CEHelper::IsLastActiveExceptionCorrupting(BOOL fMarkForReuseIfCorrupting /* = FALSE */) +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + MODE_ANY; + PRECONDITION(GetThread() != NULL); + } + CONTRACTL_END; + + if (g_pConfig->LegacyCorruptedStateExceptionsPolicy()) + { + return FALSE; + } + + BOOL fIsCorrupting = FALSE; + ThreadExceptionState *pCurTES = GetThread()->GetExceptionState(); + + // Check the corruption severity + CorruptionSeverity severity = pCurTES->GetLastActiveExceptionCorruptionSeverity(); + fIsCorrupting = (severity == ProcessCorrupting); + if (fIsCorrupting && fMarkForReuseIfCorrupting) + { + // Mark the corruption severity for reuse + CEHelper::MarkLastActiveExceptionCorruptionSeverityForReraiseReuse(); + } + + LOG((LF_EH, LL_INFO100, "CEHelper::IsLastActiveExceptionCorrupting - Using corruption severity from TES.\n")); + + return fIsCorrupting; +} + +// Given a MethodDesc, this method will return a BOOL to indicate if +// the containing assembly was built for PreV4 runtime or not. +/* static */ +BOOL CEHelper::IsMethodInPreV4Assembly(PTR_MethodDesc pMethodDesc) +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + MODE_ANY; + PRECONDITION(pMethodDesc != NULL); + } + CONTRACTL_END; + + // By default, assume that the containing assembly was not + // built for PreV4 runtimes. + BOOL fBuiltForPreV4Runtime = FALSE; + + if (g_pConfig->LegacyCorruptedStateExceptionsPolicy()) + { + return TRUE; + } + + LPCSTR pszVersion = NULL; + + // Retrieve the manifest metadata reference since that contains + // the "built-for" runtime details + IMDInternalImport *pImport = pMethodDesc->GetAssembly()->GetManifestImport(); + if (pImport && SUCCEEDED(pImport->GetVersionString(&pszVersion))) + { + if (pszVersion != NULL) + { + // If version begins with "v1.*" or "v2.*", it was built for preV4 runtime + if ((pszVersion[0] == 'v' || pszVersion[0] == 'V') && + IS_DIGIT(pszVersion[1]) && + (pszVersion[2] == '.') ) + { + // Looks like a version. Is it lesser than v4.0 major version where we start using new behavior? + fBuiltForPreV4Runtime = ((DIGIT_TO_INT(pszVersion[1]) != 0) && + (DIGIT_TO_INT(pszVersion[1]) <= HIGHEST_MAJOR_VERSION_OF_PREV4_RUNTIME)); + } + } + } + + return fBuiltForPreV4Runtime; +} + +// Given a MethodDesc and CorruptionSeverity, this method will return a +// BOOL indicating if the method can handle those kinds of CEs or not. +/* static */ +BOOL CEHelper::CanMethodHandleCE(PTR_MethodDesc pMethodDesc, CorruptionSeverity severity, BOOL fCalculateSecurityInfo /*= TRUE*/) +{ + BOOL fCanMethodHandleSeverity = FALSE; + +#ifndef DACCESS_COMPILE + CONTRACTL + { + if (fCalculateSecurityInfo) + { + GC_TRIGGERS; // CEHelper::CanMethodHandleCE will invoke Security::IsMethodCritical that could endup invoking MethodTable::LoadEnclosingMethodTable that is GC_TRIGGERS + } + else + { + // See comment in COMPlusUnwindCallback for details. + GC_NOTRIGGER; + } + // First pass requires THROWS and in 2nd we need to be due to the AppX check below where GetFusionAssemblyName can throw. + THROWS; + MODE_ANY; + PRECONDITION(pMethodDesc != NULL); + } + CONTRACTL_END; + +#ifdef FEATURE_APPX_BINDER + // In an Metro application, disallow application code to catch any corrupted state exception + if (AppX::IsAppXProcess()) + { + // This call to GetFusionAssemblyNameNoCreate will return a valid fusion assembly name + // in the second pass of exception dispatch as the name would have been created in the first pass, + // if not already existent. + IAssemblyName *pIAssemblyName = pMethodDesc->GetAssembly()->GetFusionAssemblyNameNoCreate(); + if (!pIAssemblyName) + { + pIAssemblyName = pMethodDesc->GetAssembly()->GetFusionAssemblyName(); + } + + if (Fusion::Util::IsAnyFrameworkAssembly(pIAssemblyName) != S_OK) + { + return FALSE; + } + } +#endif // FEATURE_APPX + + if (g_pConfig->LegacyCorruptedStateExceptionsPolicy()) + { + return TRUE; + } + + // Only SecurityCritical code can handle CE since only they can generate it. + // Even in full trusted assembly, transparent code cannot generate CE and thus, + // will not know how to handle it properly. + // + // Check if the method in question is SecurityCritical or not. + MethodSecurityDescriptor mdSec(pMethodDesc); + fCanMethodHandleSeverity = mdSec.IsCritical(); + + if (fCanMethodHandleSeverity) + { + // Reset the flag to FALSE + fCanMethodHandleSeverity = FALSE; + + // Since the method is Security Critical, now check if it is + // attributed to handle the CE or not. + IMDInternalImport *pImport = pMethodDesc->GetMDImport(); + if (pImport != NULL) + { + mdMethodDef methodDef = pMethodDesc->GetMemberDef(); + switch(severity) + { + case ProcessCorrupting: + fCanMethodHandleSeverity = (S_OK == pImport->GetCustomAttributeByName( + methodDef, + HANDLE_PROCESS_CORRUPTED_STATE_EXCEPTION_ATTRIBUTE, + NULL, + NULL)); + break; + default: + _ASSERTE(!"Unknown Exception Corruption Severity!"); + break; + } + } + } +#endif // !DACCESS_COMPILE + + return fCanMethodHandleSeverity; +} + +// Given a MethodDesc, this method will return a BOOL to indicate if the method should be examined for exception +// handlers for the specified exception. +// +// This method accounts for both corrupting and non-corrupting exceptions. +/* static */ +BOOL CEHelper::CanMethodHandleException(CorruptionSeverity severity, PTR_MethodDesc pMethodDesc, BOOL fCalculateSecurityInfo /*= TRUE*/) +{ + CONTRACTL + { + // CEHelper::CanMethodHandleCE will invoke Security::IsMethodCritical that could endup invoking MethodTable::LoadEnclosingMethodTable that is GC_TRIGGERS/THROWS + if (fCalculateSecurityInfo) + { + GC_TRIGGERS; + } + else + { + // See comment in COMPlusUnwindCallback for details. + GC_NOTRIGGER; + } + THROWS; + MODE_ANY; + PRECONDITION(pMethodDesc != NULL); + } + CONTRACTL_END; + + // By default, assume that the runtime shouldn't look for exception handlers + // in the method pointed by the MethodDesc + BOOL fLookForExceptionHandlersInMethod = FALSE; + + if (g_pConfig->LegacyCorruptedStateExceptionsPolicy()) + { + return TRUE; + } + + // If we have been asked to use the last active corruption severity (e.g. in cases of Reflection + // or COM interop), then retrieve it. + if (severity == UseLast) + { + LOG((LF_EH, LL_INFO100, "CEHelper::CanMethodHandleException - Using LastActiveExceptionCorruptionSeverity.\n")); + severity = GetThread()->GetExceptionState()->GetLastActiveExceptionCorruptionSeverity(); + } + + LOG((LF_EH, LL_INFO100, "CEHelper::CanMethodHandleException - Processing CorruptionSeverity: %d.\n", severity)); + + if (severity > NotCorrupting) + { + // If the method lies in an assembly built for pre-V4 runtime, allow the runtime + // to look for exception handler for the CE. + BOOL fIsMethodInPreV4Assembly = FALSE; + fIsMethodInPreV4Assembly = CEHelper::IsMethodInPreV4Assembly(pMethodDesc); + + if (!fIsMethodInPreV4Assembly) + { + // Method lies in an assembly built for V4 or later runtime. + LOG((LF_EH, LL_INFO100, "CEHelper::CanMethodHandleException - Method is in an assembly built for V4 or later runtime.\n")); + + // Depending upon the corruption severity of the exception, see if the + // method supports handling that. + LOG((LF_EH, LL_INFO100, "CEHelper::CanMethodHandleException - Exception is corrupting.\n")); + + // Check if the method can handle the severity specified in the exception object. + fLookForExceptionHandlersInMethod = CEHelper::CanMethodHandleCE(pMethodDesc, severity, fCalculateSecurityInfo); + } + else + { + // Method is in a Pre-V4 assembly - allow it to be examined for processing the CE + fLookForExceptionHandlersInMethod = TRUE; + } + } + else + { + // Non-corrupting exceptions can continue to be delivered + fLookForExceptionHandlersInMethod = TRUE; + } + + return fLookForExceptionHandlersInMethod; +} + +// Given a managed exception object, this method will return a BOOL +// indicating if it corresponds to a ProcessCorruptedState exception +// or not. +/* static */ +BOOL CEHelper::IsProcessCorruptedStateException(OBJECTREF oThrowable) +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + MODE_COOPERATIVE; + SO_TOLERANT; + PRECONDITION(oThrowable != NULL); + } + CONTRACTL_END; + + if (g_pConfig->LegacyCorruptedStateExceptionsPolicy()) + { + return FALSE; + } + +#ifndef DACCESS_COMPILE + // If the throwable represents preallocated SO, then indicate it as a CSE + if (CLRException::GetPreallocatedStackOverflowException() == oThrowable) + { + return TRUE; + } +#endif // !DACCESS_COMPILE + + // Check if we have an exception tracker for this exception + // and if so, if it represents corrupting exception or not. + // Get the exception tracker for the current exception +#ifdef WIN64EXCEPTIONS + PTR_ExceptionTracker pEHTracker = GetEHTrackerForException(oThrowable, NULL); +#elif _TARGET_X86_ + PTR_ExInfo pEHTracker = GetEHTrackerForException(oThrowable, NULL); +#else +#error Unsupported platform +#endif + + if (pEHTracker != NULL) + { + // Found the tracker for exception object - check if its CSE or not. + return (pEHTracker->GetCorruptionSeverity() == ProcessCorrupting); + } + + return FALSE; +} + +#ifdef WIN64EXCEPTIONS +void CEHelper::SetupCorruptionSeverityForActiveExceptionInUnwindPass(Thread *pCurThread, PTR_ExceptionTracker pEHTracker, BOOL fIsFirstPass, + DWORD dwExceptionCode) +{ +#ifndef DACCESS_COMPILE + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + MODE_ANY; + SO_TOLERANT; + PRECONDITION(!fIsFirstPass); // This method should only be called during an unwind + PRECONDITION(pCurThread != NULL); + } + CONTRACTL_END; + + // <WIN64> + // + // Typically, exception tracker is created for an exception when the OS is in the first pass. + // However, it may be created during the 2nd pass under specific cases. Managed C++ provides + // such a scenario. In the following, stack grows left to right: + // + // CallDescrWorker -> ILStub1 -> <Native Main> -> UMThunkStub -> IL_Stub2 -> <Managed Main> + // + // If a CSE exception goes unhandled from managed main, it will reach the OS. The [CRT in?] OS triggers + // unwind that results in invoking the personality routine of UMThunkStub, called UMThunkStubUnwindFrameChainHandler, + // that releases all exception trackers below it. Thus, the tracker for the CSE, which went unhandled, is also + // released. This detail is 64bit specific and the crux of this issue. + // + // Now, it is expected that by the time we are in the unwind pass, the corruption severity would have already been setup in the + // exception tracker and thread exception state (TES) as part of the first pass, and thus, are identical. + // + // However, for the scenario above, when the unwind continues and reaches ILStub1, its personality routine (which is ProcessCLRException) + // is invoked. It attempts to get the exception tracker corresponding to the exception. Since none exists, it creates a brand new one, + // which has the exception corruption severity as NotSet. + // + // During the stack walk, we know (from TES) that the active exception was a CSE, and thus, ILStub1 cannot handle the exception. Prior + // to bailing out, we assert that our data structures are intact by comparing the exception severity in TES with the one in the current + // exception tracker. Since the tracker was recreated, it had the severity as NotSet and this does not match the severity in TES. + // Thus, the assert fires. [This check is performed in ProcessManagedCallFrame.] + // + // To address such a case, if we have created a new exception tracker in the unwind (2nd) pass, then set its + // exception corruption severity to what the TES holds currently. This will maintain the same semantic as the case + // where new tracker is not created (for e.g. the exception was caught in Managed main). + // + // The exception is the scenario of code that uses longjmp to jump to a different context. Longjmp results in a raise + // of a new exception with the longjmp exception code (0x80000026) but with ExceptionFlags set indicating unwind. When this is + // seen by ProcessCLRException (64bit personality routine), it will create a new tracker in the 2nd pass. + // + // Longjmp outside an exceptional path does not interest us, but the one in the exceptional + // path would only happen when a method attributed to handle CSE invokes it. Thus, if the longjmp happened during the 2nd pass of a CSE, + // we want it to proceed (and thus, jump) as expected and not apply the CSE severity to the tracker - this is equivalent to + // a catch block that handles a CSE and then does a "throw new Exception();". The new exception raised is + // non-CSE in nature as well. + // + // http://www.nynaeve.net/?p=105 has a brief description of how exception-safe setjmp/longjmp works. + // + // </WIN64> + if (pEHTracker->GetCorruptionSeverity() == NotSet) + { + // Get the thread exception state + ThreadExceptionState *pCurTES = pCurThread->GetExceptionState(); + + // Set the tracker to have the same corruption severity as the last active severity unless we are dealing + // with LONGJMP + if (dwExceptionCode == STATUS_LONGJUMP) + { + pCurTES->SetLastActiveExceptionCorruptionSeverity(NotCorrupting); + } + + pEHTracker->SetCorruptionSeverity(pCurTES->GetLastActiveExceptionCorruptionSeverity()); + LOG((LF_EH, LL_INFO100, "CEHelper::SetupCorruptionSeverityForActiveExceptionInUnwindPass - Setup the corruption severity in the second pass.\n")); + } +#endif // !DACCESS_COMPILE +} +#endif // WIN64EXCEPTIONS + +// This method is invoked from the personality routine for managed code and is used to setup the +// corruption severity for the active exception on the thread exception state and the +// exception tracker corresponding to the exception. +/* static */ +void CEHelper::SetupCorruptionSeverityForActiveException(BOOL fIsRethrownException, BOOL fIsNestedException, BOOL fShouldTreatExceptionAsNonCorrupting /* = FALSE */) +{ +#ifndef DACCESS_COMPILE + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + MODE_COOPERATIVE; + } + CONTRACTL_END; + + // Get the thread and the managed exception object - they must exist at this point + Thread *pCurThread = GetThread(); + _ASSERTE(pCurThread != NULL); + + OBJECTREF oThrowable = pCurThread->GetThrowable(); + _ASSERTE(oThrowable != NULL); + + // Get the thread exception state + ThreadExceptionState * pCurTES = pCurThread->GetExceptionState(); + _ASSERTE(pCurTES != NULL); + + // Get the exception tracker for the current exception +#ifdef WIN64EXCEPTIONS + PTR_ExceptionTracker pEHTracker = pCurTES->GetCurrentExceptionTracker(); +#elif _TARGET_X86_ + PTR_ExInfo pEHTracker = pCurTES->GetCurrentExceptionTracker(); +#else // !(_WIN64 || _TARGET_X86_) +#error Unsupported platform +#endif // _WIN64 + + _ASSERTE(pEHTracker != NULL); + + // Get the current exception code from the tracker. + PEXCEPTION_RECORD pEHRecord = pCurTES->GetExceptionRecord(); + _ASSERTE(pEHRecord != NULL); + DWORD dwActiveExceptionCode = pEHRecord->ExceptionCode; + + if (pEHTracker->GetCorruptionSeverity() != NotSet) + { + // Since the exception tracker already has the corruption severity set, + // we dont have much to do. Just confirm that our assumptions are correct. + _ASSERTE(pEHTracker->GetCorruptionSeverity() == pCurTES->GetLastActiveExceptionCorruptionSeverity()); + + LOG((LF_EH, LL_INFO100, "CEHelper::SetupCorruptionSeverityForActiveException - Current tracker already has the corruption severity set.\n")); + return; + } + + // If the exception in question is to be treated as non-corrupting, + // then flag it and exit. + if (fShouldTreatExceptionAsNonCorrupting || g_pConfig->LegacyCorruptedStateExceptionsPolicy()) + { + pEHTracker->SetCorruptionSeverity(NotCorrupting); + LOG((LF_EH, LL_INFO100, "CEHelper::SetupCorruptionSeverityForActiveException - Exception treated as non-corrupting.\n")); + goto done; + } + + if (!fIsRethrownException && !fIsNestedException) + { + // There should be no previously active exception for this case + _ASSERTE(pEHTracker->GetPreviousExceptionTracker() == NULL); + + CorruptionSeverity severityTES = NotSet; + + if (pCurTES->ShouldLastActiveExceptionCorruptionSeverityBeReused()) + { + // Get the corruption severity from the ThreadExceptionState (TES) for the last active exception + severityTES = pCurTES->GetLastActiveExceptionCorruptionSeverity(); + + // Incase of scenarios like AD transition or Reflection invocation, + // TES would hold corruption severity of the last active exception. To propagate it + // to the current exception, we will apply it to current tracker and only if the applied + // severity is "NotSet", will we proceed to check the current exception for corruption + // severity. + pEHTracker->SetCorruptionSeverity(severityTES); + } + + // Reset TES Corruption Severity + pCurTES->SetLastActiveExceptionCorruptionSeverity(NotSet); + + if (severityTES == NotSet) + { + // Since the last active exception's severity was "NotSet", we will look up the + // exception code and the exception object to see if the exception should be marked + // corrupting. + // + // Since this exception was neither rethrown nor is nested, it implies that we are + // outside an active exception. Thus, even if it contains inner exceptions, we wont have + // corruption severity for them since that information is tracked in EH tracker and + // we wont have an EH tracker for the inner most exception. + + if (CEHelper::IsProcessCorruptedStateException(dwActiveExceptionCode) || + CEHelper::IsProcessCorruptedStateException(oThrowable)) + { + pEHTracker->SetCorruptionSeverity(ProcessCorrupting); + LOG((LF_EH, LL_INFO100, "CEHelper::SetupCorruptionSeverityForActiveException - Marked non-rethrow/non-nested exception as ProcessCorrupting.\n")); + } + else + { + pEHTracker->SetCorruptionSeverity(NotCorrupting); + LOG((LF_EH, LL_INFO100, "CEHelper::SetupCorruptionSeverityForActiveException - Marked non-rethrow/non-nested exception as NotCorrupting.\n")); + } + } + else + { + LOG((LF_EH, LL_INFO100, "CEHelper::SetupCorruptionSeverityForActiveException - Copied the corruption severity to tracker from ThreadExceptionState for non-rethrow/non-nested exception.\n")); + } + } + else + { + // Its either a rethrow or nested exception + +#ifdef WIN64EXCEPTIONS + PTR_ExceptionTracker pOrigEHTracker = NULL; +#elif _TARGET_X86_ + PTR_ExInfo pOrigEHTracker = NULL; +#else +#error Unsupported platform +#endif + + BOOL fDoWeHaveCorruptionSeverity = FALSE; + + if (fIsRethrownException) + { + // Rethrown exceptions are nested by nature (of our implementation). The + // original EHTracker will exist for the exception - infact, it will be + // the tracker previous to the current one. We will simply copy + // its severity to the current EH tracker representing the rethrow. + pOrigEHTracker = pEHTracker->GetPreviousExceptionTracker(); + _ASSERTE(pOrigEHTracker != NULL); + + // Ideally, we would like have the assert below enabled. But, as may happen under OOM + // stress, this can be false. Here's how it will happen: + // + // An exception is thrown, which is later caught and rethrown in the catch block. Rethrow + // results in calling IL_Rethrow that will call RaiseTheExceptionInternalOnly to actually + // raise the exception. Prior to the raise, we update the last thrown object on the thread + // by calling Thread::SafeSetLastThrownObject which, internally, could have an OOM, resulting + // in "changing" the throwable used to raise the exception to be preallocated OOM object. + // + // When the rethrow happens and CLR's exception handler for managed code sees the exception, + // the exception tracker created for the rethrown exception will contain the reference to + // the last thrown object, which will be the preallocated OOM object. + // + // Thus, though, we came here because of a rethrow, and logically, the throwable should remain + // the same, it neednt be. Simply put, rethrow can result in working with a completely different + // exception object than what was originally thrown. + // + // Hence, the assert cannot be enabled. + // + // Thus, we will use the EH tracker corresponding to the original exception, to get the + // rethrown exception's corruption severity, only when the rethrown throwable is the same + // as the original throwable. Otherwise, we will pretend that we didnt get the original tracker + // and will automatically enter the path below to set the corruption severity based upon the + // rethrown throwable. + + // _ASSERTE(pOrigEHTracker->GetThrowable() == oThrowable); + if (pOrigEHTracker->GetThrowable() != oThrowable) + { + pOrigEHTracker = NULL; + LOG((LF_EH, LL_INFO100, "CEHelper::SetupCorruptionSeverityForActiveException - Rethrown throwable does not match the original throwable. Corruption severity will be set based upon rethrown throwable.\n")); + } + } + else + { + // Get the corruption severity from the ThreadExceptionState (TES) for the last active exception + CorruptionSeverity severityTES = NotSet; + + if (pCurTES->ShouldLastActiveExceptionCorruptionSeverityBeReused()) + { + severityTES = pCurTES->GetLastActiveExceptionCorruptionSeverity(); + + // Incase of scenarios like AD transition or Reflection invocation, + // TES would hold corruption severity of the last active exception. To propagate it + // to the current exception, we will apply it to current tracker and only if the applied + // severity is "NotSet", will we proceed to check the current exception for corruption + // severity. + pEHTracker->SetCorruptionSeverity(severityTES); + } + + // Reset TES Corruption Severity + pCurTES->SetLastActiveExceptionCorruptionSeverity(NotSet); + + // If the last exception didnt have any corruption severity, proceed to look for it. + if (severityTES == NotSet) + { + // This is a nested exception - check if it has an inner exception(s). If it does, + // find the EH tracker corresponding to the innermost exception and we will copy the + // corruption severity from the original tracker to the current one. + OBJECTREF oInnermostThrowable = ((EXCEPTIONREF)oThrowable)->GetBaseException(); + if (oInnermostThrowable != NULL) + { + // Find the tracker corresponding to the inner most exception, starting from + // the tracker previous to the current one. An EH tracker may not be found if + // the code did the following inside a catch clause: + // + // Exception ex = new Exception("inner exception"); + // throw new Exception("message", ex); + // + // Or, an exception like AV happened in the catch clause. + pOrigEHTracker = GetEHTrackerForException(oInnermostThrowable, pEHTracker->GetPreviousExceptionTracker()); + } + } + else + { + // We have the corruption severity from the TES. Set the flag indicating so. + fDoWeHaveCorruptionSeverity = TRUE; + LOG((LF_EH, LL_INFO100, "CEHelper::SetupCorruptionSeverityForActiveException - Copied the corruption severity to tracker from ThreadExceptionState for nested exception.\n")); + } + } + + if (!fDoWeHaveCorruptionSeverity) + { + if (pOrigEHTracker != NULL) + { + // Copy the severity from the original EH tracker to the current one + CorruptionSeverity origCorruptionSeverity = pOrigEHTracker->GetCorruptionSeverity(); + _ASSERTE(origCorruptionSeverity != NotSet); + pEHTracker->SetCorruptionSeverity(origCorruptionSeverity); + + LOG((LF_EH, LL_INFO100, "CEHelper::SetupCorruptionSeverityForActiveException - Copied the corruption severity (%d) from the original EH tracker for rethrown exception.\n", origCorruptionSeverity)); + } + else + { + if (CEHelper::IsProcessCorruptedStateException(dwActiveExceptionCode) || + CEHelper::IsProcessCorruptedStateException(oThrowable)) + { + pEHTracker->SetCorruptionSeverity(ProcessCorrupting); + LOG((LF_EH, LL_INFO100, "CEHelper::SetupCorruptionSeverityForActiveException - Marked nested exception as ProcessCorrupting.\n")); + } + else + { + pEHTracker->SetCorruptionSeverity(NotCorrupting); + LOG((LF_EH, LL_INFO100, "CEHelper::SetupCorruptionSeverityForActiveException - Marked nested exception as NotCorrupting.\n")); + } + } + } + } + +done: + // Save the current exception's corruption severity in the ThreadExceptionState (TES) + // for cases when we catch the managed exception in the runtime using EX_CATCH. + // At such a time, all exception trackers get released (due to unwind triggered + // by EX_END_CATCH) and yet we need the corruption severity information for + // scenarios like AD Transition, Reflection invocation, etc. + CorruptionSeverity currentSeverity = pEHTracker->GetCorruptionSeverity(); + + // We should be having a valid corruption severity at this point + _ASSERTE(currentSeverity != NotSet); + + // Save it in the TES + pCurTES->SetLastActiveExceptionCorruptionSeverity(currentSeverity); + LOG((LF_EH, LL_INFO100, "CEHelper::SetupCorruptionSeverityForActiveException - Copied the corruption severity (%d) to ThreadExceptionState.\n", currentSeverity)); + +#endif // !DACCESS_COMPILE +} + +// CE can be caught in the VM and later reraised again. Examples of such scenarios +// include AD transition, COM interop, Reflection invocation, to name a few. +// In such cases, we want to mark the corruption severity for reuse upon reraise, +// implying that when the VM does a reraise of such a exception, we should use +// the original corruption severity for the new raised exception, instead of creating +// a new one for it. +/* static */ +void CEHelper::MarkLastActiveExceptionCorruptionSeverityForReraiseReuse() +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + MODE_ANY; + SO_TOLERANT; + PRECONDITION(GetThread() != NULL); + } + CONTRACTL_END; + + // If the last active exception's corruption severity is anything but + // "NotSet", mark it for ReraiseReuse + ThreadExceptionState *pCurTES = GetThread()->GetExceptionState(); + _ASSERTE(pCurTES != NULL); + + CorruptionSeverity severityTES = pCurTES->GetLastActiveExceptionCorruptionSeverity(); + if (severityTES != NotSet) + { + pCurTES->SetLastActiveExceptionCorruptionSeverity((CorruptionSeverity)(severityTES | ReuseForReraise)); + } +} + +// This method will return a BOOL to indicate if the current exception is to be treated as +// non-corrupting. Currently, this returns true for NullReferenceException only. +/* static */ +BOOL CEHelper::ShouldTreatActiveExceptionAsNonCorrupting() +{ + BOOL fShouldTreatAsNonCorrupting = FALSE; + +#ifndef DACCESS_COMPILE + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_COOPERATIVE; + PRECONDITION(GetThread() != NULL); + } + CONTRACTL_END; + + if (g_pConfig->LegacyCorruptedStateExceptionsPolicy()) + { + return TRUE; + } + + DWORD dwActiveExceptionCode = GetThread()->GetExceptionState()->GetExceptionRecord()->ExceptionCode; + if (dwActiveExceptionCode == STATUS_ACCESS_VIOLATION) + { + // NullReference has the same exception code as AV + OBJECTREF oThrowable = NULL; + GCPROTECT_BEGIN(oThrowable); + + // Get the throwable and check if it represents null reference exception + oThrowable = GetThread()->GetThrowable(); + _ASSERTE(oThrowable != NULL); + if (MscorlibBinder::GetException(kNullReferenceException) == oThrowable->GetMethodTable()) + { + fShouldTreatAsNonCorrupting = TRUE; + } + GCPROTECT_END(); + } +#endif // !DACCESS_COMPILE + + return fShouldTreatAsNonCorrupting; +} + +// If we were working in a nested exception scenario, reset the corruption severity to the last +// exception we were processing, based upon its EH tracker. +// +// If none was present, reset it to NotSet. +// +// Note: This method must be called once the exception trackers have been adjusted post catch-block execution. +/* static */ +void CEHelper::ResetLastActiveCorruptionSeverityPostCatchHandler(Thread *pThread) +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + MODE_ANY; + PRECONDITION(pThread != NULL); + } + CONTRACTL_END; + + ThreadExceptionState *pCurTES = pThread->GetExceptionState(); + + // By this time, we would have set the correct exception tracker for the active exception domain, + // if applicable. An example is throwing and catching an exception within a catch block. We will update + // the LastActiveCorruptionSeverity based upon the active exception domain. If we are not in one, we will + // set it to "NotSet". +#ifdef WIN64EXCEPTIONS + PTR_ExceptionTracker pEHTracker = pCurTES->GetCurrentExceptionTracker(); +#elif _TARGET_X86_ + PTR_ExInfo pEHTracker = pCurTES->GetCurrentExceptionTracker(); +#else +#error Unsupported platform +#endif + + if (pEHTracker) + { + pCurTES->SetLastActiveExceptionCorruptionSeverity(pEHTracker->GetCorruptionSeverity()); + } + else + { + pCurTES->SetLastActiveExceptionCorruptionSeverity(NotSet); + } + + LOG((LF_EH, LL_INFO100, "CEHelper::ResetLastActiveCorruptionSeverityPostCatchHandler - Reset LastActiveException corruption severity to %d.\n", + pCurTES->GetLastActiveExceptionCorruptionSeverity())); +} + +// This method will return a BOOL indicating if the target of IDispatch can handle the specified exception or not. +/* static */ +BOOL CEHelper::CanIDispatchTargetHandleException() +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + MODE_ANY; + PRECONDITION(GetThread() != NULL); + } + CONTRACTL_END; + + // By default, assume that the target of IDispatch cannot handle the exception. + BOOL fCanMethodHandleException = FALSE; + + if (g_pConfig->LegacyCorruptedStateExceptionsPolicy()) + { + return TRUE; + } + + // IDispatch implementation in COM interop works by invoking the actual target via reflection. + // Thus, a COM client could use the V4 runtime to invoke a V2 method. In such a case, a CSE + // could come unhandled at the actual target invoked via reflection. + // + // Reflection invocation would have set a flag for us, indicating if the actual target was + // enabled to handle the CE or not. If it is, then we should allow the COM client to get the + // hresult from the call and not let the exception continue up the stack. + ThreadExceptionState *pCurTES = GetThread()->GetExceptionState(); + fCanMethodHandleException = pCurTES->CanReflectionTargetHandleException(); + + // Reset the flag so that subsequent invocations work as expected. + pCurTES->SetCanReflectionTargetHandleException(FALSE); + + return fCanMethodHandleException; +} + +#endif // FEATURE_CORRUPTING_EXCEPTIONS + +#ifndef DACCESS_COMPILE +// When a managed thread starts in non-default domain, its callstack looks like below: +// +// <ManagedThreadBase_DispatchOuter> +// <ManagedThreadBase_DispatchMiddle> +// <ManagedThreadBase_DispatchInner> +// +// -- AD transition is here -- ==> Pushes ContextTransitionFrame and has EX_CATCH +// +// <ManagedThreadBase_DispatchOuter> +// <ManagedThreadBase_DispatchMiddle> +// <ManagedThreadBase_DispatchInner> +// +// In CoreCLR, all managed threads spawned will have a stack like this since they all +// run in non-DefaultDomain. The upper three frames are in default domain and the lower +// three are in the non-default domain in which the thread was created. Any exception +// that is unhandled in non-default domain will be caught at AD transition boundary. +// The transition boundary does the following tasks: +// +// 1) Catch any incoming unhandled exception from the non-default domain using EX_CATCH. +// 2) Marshal the exception object to the return context (i.e. DefaultDomain) +// 3) Return to the context of DefaultDomain and throw the marshalled exception object there. +// +// All this depends upon the EX_CATCH (which is based upon C++ exception handling) being +// able to catch the exception. +// +// However, if a breakpoint exception ia raised and a debugger is not available to handle it, +// C++'s catch(...) will not be able to catch it, even when compiled with /EHa. For the curious, +// refer to "FindHandlerForForeignException" function's implementation in the CRT. One of the first +// things it does is check for breakpoint exception and if it is, it will simply bail out of the +// process of finding a handler. Thus, EX_CATCH will not be able to catch this exception and we +// will not be able to transition to the previous AD context. +// +// Imagine a thread in non-default domain suffers breakpoint exception. Assuming it will go unhandled, +// it will reach the OS, which will trigger an unwind. The execution of termination handlers in lower +// three frames (above) is fine since they are in the same AD as the thread. But when termination +// handlers in the upper three frames execute, its a case of bad mixup since the thread is in a different +// AD context than what the frames are expected to be in. +// +// Hence, we need a mechanism to transition to the expected AppDomain in case of breakpoint exception. +// This function supports this mechanism in a generic fashion, i.e., one can use it to transition to +// any AppDomain, though only up the stack. + +BOOL ReturnToPreviousAppDomain() +{ + STATIC_CONTRACT_GC_NOTRIGGER; + STATIC_CONTRACT_NOTHROW; + STATIC_CONTRACT_MODE_COOPERATIVE; + STATIC_CONTRACT_SO_TOLERANT; + + Thread *pCurThread = GetThread(); + _ASSERTE(pCurThread != NULL); + + BOOL fTransitioned = FALSE; + + BEGIN_SO_INTOLERANT_CODE_NOTHROW(pCurThread, return FALSE); + + // Get the thread's current domain + AppDomain *pCurDomain = pCurThread->GetDomain(); + _ASSERTE(pCurDomain != NULL); + + // Lookup the ContextTransitionFrame for the transition into the current AppDomain. + Frame *pCtxTransitionFrame = pCurThread->GetFirstTransitionInto(pCurDomain, NULL); + if (pCtxTransitionFrame == NULL) + { + // Since we couldnt find the context transition frame, check if its the default domain. + // If so, we will set fTransitioned to TRUE since there is no context transition frame + // setup for the initial entry into the default domain. For all other transitions to it + // from non-default domains, we will have a context transition frame. We will do a + // debug-only check to assert this invariant. + BOOL fIsDefDomain = pCurDomain->IsDefaultDomain(); +#ifdef _DEBUG + if (fIsDefDomain) + { + // Start with the topmost frame and look for a CTX frame until we reach the top of the frame chain. + // We better not find one since we couldnt find a transition frame to the DefaultDomain. + Frame *pStartFrame = pCurThread->GetFrame(); + BOOL fFoundCTXFrame = FALSE; + while ((pStartFrame != NULL) && (pStartFrame != (Frame *)FRAME_TOP)) + { + if (pStartFrame->GetVTablePtr() == ContextTransitionFrame::GetMethodFrameVPtr()) + { + fFoundCTXFrame = TRUE; + break; + } + + // Get the next frame in the chain + pStartFrame = pStartFrame->PtrNextFrame(); + } + + _ASSERTE_MSG(!fFoundCTXFrame, "How come we didnt find the transition frame to DefDomain but found another CTX frame on the frame chain?"); + } +#endif // _DEBUG + fTransitioned = fIsDefDomain; + LOG((LF_EH, LL_INFO100, "ReturnToPreviousAppDomain: Unable to find the transition into the current domain (IsDefaultDomain: %d).\n", fIsDefDomain)); + + goto done; + } + + // Confirm its the correct type of frame + _ASSERTE_MSG(pCtxTransitionFrame->GetVTablePtr() == ContextTransitionFrame::GetMethodFrameVPtr(), + "How come we didn't find context transition frame for this AD transition?"); + + // Get the topmost Frame + Frame *pCurFrame; + pCurFrame = pCurThread->GetFrame(); + + // <ASSUMPTION> + // + // The loop below assumes we are called during an exception unwind since it + // unwinds the Frames and pops them off the thread. + // + // </ASSUMPTION> + // + // Clear all the frames until we are at the frame of our interest. If there was a + // CTX frame between the topmost frame and the AD transition, then we should be able to + // catch it here as well. + while((pCurFrame != NULL) && (pCurFrame < pCtxTransitionFrame) && + (pCurFrame->GetVTablePtr() != ContextTransitionFrame::GetMethodFrameVPtr())) + { + // Invoke exception unwind and pop the frame off + pCurFrame->ExceptionUnwind(); + pCurFrame->Pop(); + pCurFrame = pCurThread->GetFrame(); + } + + // Confirm that we are at the expected Frame. + _ASSERTE_MSG(((pCurFrame != NULL) && + (pCurFrame->GetVTablePtr() == ContextTransitionFrame::GetMethodFrameVPtr()) && + (pCurFrame == pCtxTransitionFrame)), + "How come we are not at the exact context transition frame?"); + + // Log our context return + LOG((LF_EH, LL_INFO100, "ReturnToPreviousAppDomain: Returning from AD %d to AD %d\n", + GetAppDomain()->GetId().m_dwId, pCtxTransitionFrame->GetReturnDomain()->GetId().m_dwId)); + + // Return to the previous AD context + pCurThread->ReturnToContext((ContextTransitionFrame *)pCtxTransitionFrame); + +#ifdef _DEBUG + // At this point, the context transition frame would have been popped off by + // ReturnToContext above. + pCurFrame = pCurThread->GetFrame(); + _ASSERTE_MSG(pCurFrame != pCtxTransitionFrame, "How come the CTX frame of AD transition is still on the frame chain?"); +#endif // _DEBUG + + // Set the flag that we transitioned correctly. + fTransitioned = TRUE; + +done:; + END_SO_INTOLERANT_CODE; + + return fTransitioned; +} + +// This class defines a holder that can be used to return to previous AppDomain incase an exception +// goes across an AD transition boundary without reverting the active context. +// +// Use this holder *after* you have transitioned to the target AD. +void ReturnToPreviousAppDomainHolder::Init() +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + MODE_ANY; + SO_TOLERANT; + } + CONTRACTL_END; + + m_fShouldReturnToPreviousAppDomain = TRUE; + m_pThread = GetThread(); + _ASSERTE(m_pThread != NULL); + +#ifdef _DEBUG + m_pTransitionedToAD = m_pThread->GetDomain(); +#endif // _DEBUG +} + +ReturnToPreviousAppDomainHolder::ReturnToPreviousAppDomainHolder() +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + MODE_ANY; + SO_TOLERANT; + } + CONTRACTL_END; + + Init(); +} + +void ReturnToPreviousAppDomainHolder::ReturnToPreviousAppDomain() +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + MODE_ANY; + SO_TOLERANT; + // Test your sanity - we should still be in the transitioned-to AD. + PRECONDITION(m_pThread->GetDomain() == m_pTransitionedToAD); + } + CONTRACTL_END; + + { + GCX_COOP(); + ::ReturnToPreviousAppDomain(); + } + + // Set the LastThrownObject as NULL since we have returned to a different + // AD. Maintaining the reference to an object in the "returned-from" AD + // will prevent the AD from getting unloaded. + // + // Setting to NULL does not require us to be in COOP mode. + m_pThread->SafeSetLastThrownObject(NULL); +} + +ReturnToPreviousAppDomainHolder::~ReturnToPreviousAppDomainHolder() +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + MODE_ANY; + SO_TOLERANT; + } + CONTRACTL_END; + + if (m_fShouldReturnToPreviousAppDomain) + { + ReturnToPreviousAppDomain(); + } +} + +// Reset the flag to indicate that reverting to previous AD is not required anymore. +// This should be invoked when the call has successfully returned from the target execution context. +// +// By default, this flag is TRUE (see the contructor above) to enable automatic context +// revert incase an exception goes past the transition. +// +// END_DOMAIN_TRANSITION_NO_EH_AT_TRANSITION macro uses it. See its implementation in threads.h +// for usage. +void ReturnToPreviousAppDomainHolder::SuppressRelease() +{ + LIMITED_METHOD_CONTRACT; + + m_fShouldReturnToPreviousAppDomain = FALSE; +} + +#endif // !DACCESS_COMPILE + +#ifndef DACCESS_COMPILE +// This method will deliver the actual exception notification. Its assumed that the caller has done the necessary checks, including +// checking whether the delegate can be invoked for the exception's corruption severity. +// +// This has been factored out of the #IFDEF FEATURE_EXCEPTION_NOTIFICATIONS so that existing ADUEN mechanism can be integrated with +// the enhanced exception notifications. +void ExceptionNotifications::DeliverExceptionNotification(ExceptionNotificationHandlerType notificationType, OBJECTREF *pDelegate, + OBJECTREF *pAppDomain, OBJECTREF *pEventArgs) +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_COOPERATIVE; + PRECONDITION(pDelegate != NULL && IsProtectedByGCFrame(pDelegate) && (*pDelegate != NULL)); + PRECONDITION(pEventArgs != NULL && IsProtectedByGCFrame(pEventArgs)); + PRECONDITION(pAppDomain != NULL && IsProtectedByGCFrame(pAppDomain)); + } + CONTRACTL_END; + + PREPARE_NONVIRTUAL_CALLSITE_USING_CODE(DELEGATEREF(*pDelegate)->GetMethodPtr()); + + DECLARE_ARGHOLDER_ARRAY(args, 3); + + args[ARGNUM_0] = OBJECTREF_TO_ARGHOLDER(DELEGATEREF(*pDelegate)->GetTarget()); + args[ARGNUM_1] = OBJECTREF_TO_ARGHOLDER(*pAppDomain); + args[ARGNUM_2] = OBJECTREF_TO_ARGHOLDER(*pEventArgs); + + CALL_MANAGED_METHOD_NORET(args); +} + +#ifdef FEATURE_EXCEPTION_NOTIFICATIONS +// To include definition of COMDelegate::GetMethodDesc +#include "comdelegate.h" + +// This method constructs the arguments to be passed to the exception notification event callback +void ExceptionNotifications::GetEventArgsForNotification(ExceptionNotificationHandlerType notificationType, + OBJECTREF *pOutEventArgs, OBJECTREF *pThrowable) +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_COOPERATIVE; + PRECONDITION(notificationType != UnhandledExceptionHandler); + PRECONDITION((pOutEventArgs != NULL) && IsProtectedByGCFrame(pOutEventArgs)); + PRECONDITION(*pOutEventArgs == NULL); + PRECONDITION((pThrowable != NULL) && (*pThrowable != NULL) && IsProtectedByGCFrame(pThrowable)); + PRECONDITION(IsException((*pThrowable)->GetMethodTable())); // We expect a valid exception object + } + CONTRACTL_END; + + MethodTable *pMTEventArgs = NULL; + BinderMethodID idEventArgsCtor = METHOD__FIRSTCHANCE_EVENTARGS__CTOR; + + EX_TRY + { + switch(notificationType) + { + case FirstChanceExceptionHandler: + pMTEventArgs = MscorlibBinder::GetClass(CLASS__FIRSTCHANCE_EVENTARGS); + idEventArgsCtor = METHOD__FIRSTCHANCE_EVENTARGS__CTOR; + break; + default: + _ASSERTE(!"Invalid Exception Notification Handler!"); + break; + } + + // Allocate the instance of the eventargs corresponding to the notification + *pOutEventArgs = AllocateObject(pMTEventArgs); + + // Prepare to invoke the .ctor + MethodDescCallSite ctor(idEventArgsCtor, pOutEventArgs); + + // Setup the arguments to be passed to the notification specific EventArgs .ctor + if (notificationType == FirstChanceExceptionHandler) + { + // FirstChance notification takes only a single argument: the exception object. + ARG_SLOT args[] = + { + ObjToArgSlot(*pOutEventArgs), + ObjToArgSlot(*pThrowable), + }; + + ctor.Call(args); + } + else + { + // Since we have already asserted above, just set the args to NULL. + *pOutEventArgs = NULL; + } + } + EX_CATCH + { + // Set event args to be NULL incase of any error (e.g. OOM) + *pOutEventArgs = NULL; + LOG((LF_EH, LL_INFO100, "ExceptionNotifications::GetEventArgsForNotification: Setting event args to NULL due to an exception.\n")); + } + EX_END_CATCH(RethrowCorruptingExceptions); // Dont swallow any CSE that may come in from the .ctor. +} + +// This SEH filter will be invoked when an exception escapes out of the exception notification +// callback and enters the runtime. In such a case, we ill simply failfast. +static LONG ExceptionNotificationFilter(PEXCEPTION_POINTERS pExceptionInfo, LPVOID pParam) +{ + EEPOLICY_HANDLE_FATAL_ERROR(COR_E_EXECUTIONENGINE); +} + +#ifdef FEATURE_CORRUPTING_EXCEPTIONS +// This method will return a BOOL indicating if the delegate should be invoked for the exception +// of the specified corruption severity. +BOOL ExceptionNotifications::CanDelegateBeInvokedForException(OBJECTREF *pDelegate, CorruptionSeverity severity) +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_COOPERATIVE; + PRECONDITION(pDelegate != NULL && IsProtectedByGCFrame(pDelegate) && (*pDelegate != NULL)); + PRECONDITION(severity > NotSet); + } + CONTRACTL_END; + + // Notifications for CSE are only delivered if the delegate target follows CSE rules. + BOOL fCanMethodHandleException = g_pConfig->LegacyCorruptedStateExceptionsPolicy() ? TRUE:(severity == NotCorrupting); + if (!fCanMethodHandleException) + { + EX_TRY + { + // Get the MethodDesc of the delegate to be invoked + MethodDesc *pMDDelegate = COMDelegate::GetMethodDesc(*pDelegate); + _ASSERTE(pMDDelegate != NULL); + + // Check the callback target and see if it is following CSE rules or not. + fCanMethodHandleException = CEHelper::CanMethodHandleException(severity, pMDDelegate); + } + EX_CATCH + { + // Incase of any exceptions, pretend we cannot handle the exception + fCanMethodHandleException = FALSE; + LOG((LF_EH, LL_INFO100, "ExceptionNotifications::CanDelegateBeInvokedForException: Exception while trying to determine if exception notification can be invoked or not.\n")); + } + EX_END_CATCH(RethrowCorruptingExceptions); // Dont swallow any CSEs. + } + + return fCanMethodHandleException; +} +#endif // FEATURE_CORRUPTING_EXCEPTIONS + +// This method will make the actual delegate invocation for the exception notification to be delivered. If an +// exception escapes out of the notification, our filter in ExceptionNotifications::DeliverNotification will +// address it. +void ExceptionNotifications::InvokeNotificationDelegate(ExceptionNotificationHandlerType notificationType, OBJECTREF *pDelegate, OBJECTREF *pEventArgs, + OBJECTREF *pAppDomain +#ifdef FEATURE_CORRUPTING_EXCEPTIONS + , CorruptionSeverity severity +#endif // FEATURE_CORRUPTING_EXCEPTIONS + ) +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_COOPERATIVE; + PRECONDITION(pDelegate != NULL && IsProtectedByGCFrame(pDelegate) && (*pDelegate != NULL)); + PRECONDITION(pEventArgs != NULL && IsProtectedByGCFrame(pEventArgs)); + PRECONDITION(pAppDomain != NULL && IsProtectedByGCFrame(pAppDomain)); +#ifdef FEATURE_CORRUPTING_EXCEPTIONS + PRECONDITION(severity > NotSet); +#endif // FEATURE_CORRUPTING_EXCEPTIONS + // Unhandled Exception Notification is delivered via Unhandled Exception Processing + // mechanism. + PRECONDITION(notificationType != UnhandledExceptionHandler); + } + CONTRACTL_END; + +#ifdef FEATURE_CORRUPTING_EXCEPTIONS + // Notifications are delivered based upon corruption severity of the exception + if (!ExceptionNotifications::CanDelegateBeInvokedForException(pDelegate, severity)) + { + LOG((LF_EH, LL_INFO100, "ExceptionNotifications::InvokeNotificationDelegate: Delegate cannot be invoked for corruption severity %d\n", + severity)); + return; + } +#endif // FEATURE_CORRUPTING_EXCEPTIONS + + // We've already exercised the prestub on this delegate's COMDelegate::GetMethodDesc, + // as part of wiring up a reliable event sink in the BCL. Deliver the notification. + ExceptionNotifications::DeliverExceptionNotification(notificationType, pDelegate, pAppDomain, pEventArgs); +} + +// This method returns a BOOL to indicate if the AppDomain is ready to receive exception notifications or not. +BOOL ExceptionNotifications::CanDeliverNotificationToCurrentAppDomain(ExceptionNotificationHandlerType notificationType) +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + MODE_COOPERATIVE; + SO_TOLERANT; + PRECONDITION(GetThread() != NULL); + PRECONDITION(notificationType != UnhandledExceptionHandler); + } + CONTRACTL_END; + + Thread *pCurThread = GetThread(); + + // Get the current AppDomain + OBJECTREF oCurAppDomain = pCurThread->GetDomain()->GetRawExposedObject(); + if (oCurAppDomain == NULL) + { + // Managed object for the current domain does not exist. Hence, no one + // can wireup to exception notifications, let alone receive them. + return FALSE; + } + + // Do we have handler(s) of the specific type wired up? + if (notificationType == FirstChanceExceptionHandler) + { + return (((APPDOMAINREF)oCurAppDomain)->GetFirstChanceExceptionNotificationHandler() != NULL); + } + else + { + _ASSERTE(!"Invalid exception notification handler specified!"); + return FALSE; + } +} + +// This method wraps the call to the actual 'DeliverNotificationInternal' method in an SEH filter +// so that if an exception escapes out of the notification callback, we will trigger failfast from +// our filter. +void ExceptionNotifications::DeliverNotification(ExceptionNotificationHandlerType notificationType, + OBJECTREF *pThrowable +#ifdef FEATURE_CORRUPTING_EXCEPTIONS + , CorruptionSeverity severity +#endif // FEATURE_CORRUPTING_EXCEPTIONS + ) +{ + STATIC_CONTRACT_GC_TRIGGERS; + STATIC_CONTRACT_NOTHROW; // NOTHROW because incase of an exception, we will FailFast. + STATIC_CONTRACT_MODE_COOPERATIVE; + + struct TryArgs + { + ExceptionNotificationHandlerType notificationType; + OBJECTREF *pThrowable; +#ifdef FEATURE_CORRUPTING_EXCEPTIONS + CorruptionSeverity severity; +#endif // FEATURE_CORRUPTING_EXCEPTIONS + } args; + + args.notificationType = notificationType; + args.pThrowable = pThrowable; +#ifdef FEATURE_CORRUPTING_EXCEPTIONS + args.severity = severity; +#endif // FEATURE_CORRUPTING_EXCEPTIONS + + PAL_TRY(TryArgs *, pArgs, &args) + { + // Make the call to the actual method that will invoke the callbacks + ExceptionNotifications::DeliverNotificationInternal(pArgs->notificationType, + pArgs->pThrowable +#ifdef FEATURE_CORRUPTING_EXCEPTIONS + , pArgs->severity +#endif // FEATURE_CORRUPTING_EXCEPTIONS + ); + } + PAL_EXCEPT_FILTER(ExceptionNotificationFilter) + { + // We should never be entering this handler since there should be + // no exception escaping out of a callback. If we are here, + // failfast. + EEPOLICY_HANDLE_FATAL_ERROR(COR_E_EXECUTIONENGINE); + } + PAL_ENDTRY; +} + +// This method will deliver the exception notification to the current AppDomain. +void ExceptionNotifications::DeliverNotificationInternal(ExceptionNotificationHandlerType notificationType, + OBJECTREF *pThrowable +#ifdef FEATURE_CORRUPTING_EXCEPTIONS + , CorruptionSeverity severity +#endif // FEATURE_CORRUPTING_EXCEPTIONS + ) +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_COOPERATIVE; + + // Unhandled Exception Notification is delivered via Unhandled Exception Processing + // mechanism. + PRECONDITION(notificationType != UnhandledExceptionHandler); + PRECONDITION((pThrowable != NULL) && (*pThrowable != NULL)); + PRECONDITION(ExceptionNotifications::CanDeliverNotificationToCurrentAppDomain(notificationType)); +#ifdef FEATURE_CORRUPTING_EXCEPTIONS + PRECONDITION(severity > NotSet); // Exception corruption severity must be valid at this point. +#endif // FEATURE_CORRUPTING_EXCEPTIONS + } + CONTRACTL_END; + + Thread *pCurThread = GetThread(); + _ASSERTE(pCurThread != NULL); + + // Get the current AppDomain + AppDomain *pCurDomain = GetAppDomain(); + _ASSERTE(pCurDomain != NULL); + +#ifdef FEATURE_CORECLR + if (true) + { + // On CoreCLR, we dont support enhanced exception notifications + _ASSERTE(!"CoreCLR does not support enhanced exception notifications!"); + return; + } +#endif // FEATURE_CORECLR + + struct + { + OBJECTREF oNotificationDelegate; + PTRARRAYREF arrDelegates; + OBJECTREF oInnerDelegate; + OBJECTREF oEventArgs; + OBJECTREF oCurrentThrowable; + OBJECTREF oCurAppDomain; + } gc; + ZeroMemory(&gc, sizeof(gc)); + + // This will hold the MethodDesc of the callback that will be invoked. + MethodDesc *pMDDelegate = NULL; + + GCPROTECT_BEGIN(gc); + + // Protect the throwable to be passed to the delegate callback + gc.oCurrentThrowable = *pThrowable; + + // We expect a valid exception object + _ASSERTE(IsException(gc.oCurrentThrowable->GetMethodTable())); + + // Save the reference to the current AppDomain. If the user code has + // wired upto this event, then the managed AppDomain object will exist. + gc.oCurAppDomain = pCurDomain->GetRawExposedObject(); + _ASSERTE(gc.oCurAppDomain); + + // Get the reference to the delegate based upon the type of notification + if (notificationType == FirstChanceExceptionHandler) + { + gc.oNotificationDelegate = ((APPDOMAINREF)gc.oCurAppDomain)->GetFirstChanceExceptionNotificationHandler(); + } + else + { + gc.oNotificationDelegate = NULL; + _ASSERTE(!"Invalid Exception Notification Handler specified!"); + } + + if (gc.oNotificationDelegate != NULL) + { + // Prevent any async exceptions from this moment on this thread + ThreadPreventAsyncHolder prevAsync; + + gc.oEventArgs = NULL; + + // Get the arguments to be passed to the delegate callback. Incase of any + // problem while allocating the event args, we will return a NULL. + ExceptionNotifications::GetEventArgsForNotification(notificationType, &gc.oEventArgs, + &gc.oCurrentThrowable); + + // Check if there are multiple callbacks registered? If there are, we will + // loop through them, invoking each one at a time. Before invoking the target, + // we will check if the target can be invoked based upon the corruption severity + // for the active exception that was passed to us. + gc.arrDelegates = (PTRARRAYREF) ((DELEGATEREF)(gc.oNotificationDelegate))->GetInvocationList(); + if (gc.arrDelegates == NULL || !gc.arrDelegates->GetMethodTable()->IsArray()) + { + ExceptionNotifications::InvokeNotificationDelegate(notificationType, &gc.oNotificationDelegate, &gc.oEventArgs, + &gc.oCurAppDomain +#ifdef FEATURE_CORRUPTING_EXCEPTIONS + , severity +#endif // FEATURE_CORRUPTING_EXCEPTIONS + ); + } + else + { + // The _invocationCount could be less than the array size, if we are sharing + // immutable arrays cleverly. + UINT_PTR cnt = ((DELEGATEREF)(gc.oNotificationDelegate))->GetInvocationCount(); + _ASSERTE(cnt <= gc.arrDelegates->GetNumComponents()); + + for (UINT_PTR i=0; i<cnt; i++) + { + gc.oInnerDelegate = gc.arrDelegates->m_Array[i]; + ExceptionNotifications::InvokeNotificationDelegate(notificationType, &gc.oInnerDelegate, &gc.oEventArgs, + &gc.oCurAppDomain +#ifdef FEATURE_CORRUPTING_EXCEPTIONS + , severity +#endif // FEATURE_CORRUPTING_EXCEPTIONS + ); + } + } + } + + GCPROTECT_END(); +} + +void ExceptionNotifications::DeliverFirstChanceNotification() +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_ANY; + } + CONTRACTL_END; + + // We check for FirstChance notification delivery after setting up the corruption severity + // so that we can determine if the callback delegate can handle CSE (or not). + // + // Deliver it only if not already done and someone has wiredup to receive it. + // + // We do this provided this is the first frame of a new exception + // that was thrown or a rethrown exception. We dont want to do this + // processing for subsequent frames on the stack since FirstChance notification + // will be delivered only when the exception is first thrown/rethrown. + ThreadExceptionState *pCurTES = GetThread()->GetExceptionState(); + _ASSERTE(pCurTES->GetCurrentExceptionTracker()); + _ASSERTE(!(pCurTES->GetCurrentExceptionTracker()->DeliveredFirstChanceNotification())); + { + GCX_COOP(); + if (ExceptionNotifications::CanDeliverNotificationToCurrentAppDomain(FirstChanceExceptionHandler)) + { + OBJECTREF oThrowable = NULL; + GCPROTECT_BEGIN(oThrowable); + + oThrowable = pCurTES->GetThrowable(); + _ASSERTE(oThrowable != NULL); + + ExceptionNotifications::DeliverNotification(FirstChanceExceptionHandler, &oThrowable +#ifdef FEATURE_CORRUPTING_EXCEPTIONS + , pCurTES->GetCurrentExceptionTracker()->GetCorruptionSeverity() +#endif // FEATURE_CORRUPTING_EXCEPTIONS + ); + GCPROTECT_END(); + + } + + // Mark the exception tracker as having delivered the first chance notification + pCurTES->GetCurrentExceptionTracker()->SetFirstChanceNotificationStatus(TRUE); + } +} + +#endif // FEATURE_EXCEPTION_NOTIFICATIONS + +#ifdef WIN64EXCEPTIONS +struct TAResetStateCallbackData +{ + // Do we have more managed code up the stack? + BOOL fDoWeHaveMoreManagedCodeOnStack; + + // StackFrame representing the crawlFrame above which + // we are searching for presence of managed code. + StackFrame sfSeedCrawlFrame; +}; + +// This callback helps the 64bit EH attempt to determine if there is more managed code +// up the stack (or not). Currently, it is used to conditionally reset the thread abort state +// as the unwind passes by. +StackWalkAction TAResetStateCallback(CrawlFrame* pCf, void* data) +{ + CONTRACTL { + NOTHROW; + GC_NOTRIGGER; + } + CONTRACTL_END; + + TAResetStateCallbackData *pTAResetStateCallbackData = static_cast<TAResetStateCallbackData *>(data); + StackWalkAction retStatus = SWA_CONTINUE; + + if(pCf->IsFrameless()) + { + IJitManager* pJitManager = pCf->GetJitManager(); + _ASSERTE(pJitManager); + if (pJitManager && (!pTAResetStateCallbackData->fDoWeHaveMoreManagedCodeOnStack)) + { + // The stackwalker can give us a callback for the seeding CrawlFrame (or other crawlframes) + // depending upon which is closer to the leaf: the seeding crawlframe or the explicit frame + // specified when starting the stackwalk. + // + // Since we are interested in checking if there is more managed code up the stack from + // the seeding crawlframe, we check if the current crawlframe is above it or not. If it is, + // then we have found managed code up the stack and should stop the stack walk. Otherwise, + // continue searching. + StackFrame sfCurrentFrame = StackFrame::FromRegDisplay(pCf->GetRegisterSet()); + if (pTAResetStateCallbackData->sfSeedCrawlFrame < sfCurrentFrame) + { + // We have found managed code on the stack. Flag it and stop the stackwalk. + pTAResetStateCallbackData->fDoWeHaveMoreManagedCodeOnStack = TRUE; + retStatus = SWA_ABORT; + } + } + } + + return retStatus; +} +#endif // WIN64EXCEPTIONS + +// This function will reset the thread abort state agains the specified thread if it is determined that +// there is no more managed code on the stack. +// +// Note: This function should be invoked ONLY during unwind. +#if defined(_TARGET_X86_) +void ResetThreadAbortState(PTR_Thread pThread, void *pEstablisherFrame) +#elif defined(WIN64EXCEPTIONS) +void ResetThreadAbortState(PTR_Thread pThread, CrawlFrame *pCf, StackFrame sfCurrentStackFrame) +#endif +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + MODE_ANY; + PRECONDITION(pThread != NULL); +#if defined(_TARGET_X86_) + PRECONDITION(pEstablisherFrame != NULL); +#elif defined(WIN64EXCEPTIONS) + PRECONDITION(pCf != NULL); + PRECONDITION(!sfCurrentStackFrame.IsNull()); +#endif + } + CONTRACTL_END; + + BOOL fResetThreadAbortState = FALSE; + + if (pThread->IsAbortRequested()) + { +#if defined(_TARGET_X86_) + if (GetNextCOMPlusSEHRecord(static_cast<EXCEPTION_REGISTRATION_RECORD *>(pEstablisherFrame)) == EXCEPTION_CHAIN_END) + { + // Topmost handler and abort requested. + fResetThreadAbortState = TRUE; + LOG((LF_EH, LL_INFO100, "ResetThreadAbortState: Topmost handler resets abort as no more managed code beyond %p.\n", pEstablisherFrame)); + } +#elif defined(WIN64EXCEPTIONS) + // Get the active exception tracker + PTR_ExceptionTracker pCurEHTracker = pThread->GetExceptionState()->GetCurrentExceptionTracker(); + _ASSERTE(pCurEHTracker != NULL); + + // We will check if thread abort state needs to be reset only for the case of exception caught in + // native code. This will happen when: + // + // 1) an unwind is triggered and + // 2) current frame is the topmost frame we saw in the first pass and + // 3) a thread abort is requested and + // 4) we dont have address of the exception handler to be invoked. + // + // (1), (2) and (4) above are checked for in ExceptionTracker::ProcessOSExceptionNotification from where we call this + // function. + + // Current frame should be the topmost frame we saw in the first pass + _ASSERTE(pCurEHTracker->GetTopmostStackFrameFromFirstPass() == sfCurrentStackFrame); + + // If the exception has been caught in native code, then alongwith not having address of the handler to be + // invoked, we also wont have the IL clause for the catch block and resume stack frame will be NULL as well. + _ASSERTE((pCurEHTracker->GetCatchToCallPC() == NULL) && + (pCurEHTracker->GetCatchHandlerExceptionClauseToken() == NULL) && + (pCurEHTracker->GetResumeStackFrame().IsNull())); + + // Walk the frame chain to see if there is any more managed code on the stack. If not, then this is the last managed frame + // on the stack and we can reset the thread abort state. + // + // Get the frame from which to start the stack walk from + Frame* pFrame = pCurEHTracker->GetLimitFrame(); + + // At this point, we are at the topmost frame we saw during the first pass + // before the unwind began. Walk the stack using the specified crawlframe and the topmost + // explicit frame to determine if we have more managed code up the stack. If none is found, + // we can reset the thread abort state. + + // Setup the data structure to be passed to the callback + TAResetStateCallbackData dataCallback; + dataCallback.fDoWeHaveMoreManagedCodeOnStack = FALSE; + + // At this point, the StackFrame in CrawlFrame should represent the current frame we have been called for. + // _ASSERTE(sfCurrentStackFrame == StackFrame::FromRegDisplay(pCf->GetRegisterSet())); + + // Reference to the StackFrame beyond which we are looking for managed code. + dataCallback.sfSeedCrawlFrame = sfCurrentStackFrame; + + pThread->StackWalkFramesEx(pCf->GetRegisterSet(), TAResetStateCallback, &dataCallback, QUICKUNWIND, pFrame); + + if (!dataCallback.fDoWeHaveMoreManagedCodeOnStack) + { + // There is no more managed code on the stack, so reset the thread abort state. + fResetThreadAbortState = TRUE; + LOG((LF_EH, LL_INFO100, "ResetThreadAbortState: Resetting thread abort state since there is no more managed code beyond stack frames:\n")); + LOG((LF_EH, LL_INFO100, "sf.SP = %p ", dataCallback.sfSeedCrawlFrame.SP)); + } +#else // WIN64EXCEPTIONS +#error Unsupported platform +#endif // WIN64EXCEPTIONS + } + + if (fResetThreadAbortState) + { + pThread->EEResetAbort(Thread::TAR_Thread); + } +} +#endif // !DACCESS_COMPILE + +#endif // !CROSSGEN_COMPILE + +//--------------------------------------------------------------------------------- +// +// +// EXCEPTION THROWING HELPERS +// +// +//--------------------------------------------------------------------------------- + +//--------------------------------------------------------------------------------- +// Funnel-worker for THROW_BAD_FORMAT and friends. +// +// Note: The "cond" argument is there to tide us over during the transition from +// BAD_FORMAT_ASSERT to THROW_BAD_FORMAT. It will go away soon. +//--------------------------------------------------------------------------------- +VOID ThrowBadFormatWorker(UINT resID, LPCWSTR imageName DEBUGARG(__in_z const char *cond)) +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + INJECT_FAULT(COMPlusThrowOM();); + SUPPORTS_DAC; + } + CONTRACTL_END + +#ifndef DACCESS_COMPILE + SString msgStr; + + if ((imageName != NULL) && (imageName[0] != 0)) + { + msgStr += W("["); + msgStr += imageName; + msgStr += W("] "); + } + + SString resStr; + if (resID == 0 || !resStr.LoadResource(CCompRC::Optional, resID)) + { + resStr.LoadResource(CCompRC::Error, MSG_FOR_URT_HR(COR_E_BADIMAGEFORMAT)); + } + msgStr += resStr; + +#ifdef _DEBUG + if (0 != strcmp(cond, "FALSE")) + { + msgStr += W(" (Failed condition: "); // this is in DEBUG only - not going to localize it. + SString condStr(SString::Ascii, cond); + msgStr += condStr; + msgStr += W(")"); + } +#endif + + ThrowHR(COR_E_BADIMAGEFORMAT, msgStr); +#endif // #ifndef DACCESS_COMPILE +} + +UINT GetResourceIDForFileLoadExceptionHR(HRESULT hr) +{ + switch (hr) { + + case CTL_E_FILENOTFOUND: + hr = IDS_EE_FILE_NOT_FOUND; + break; + + case (HRESULT)IDS_EE_PROC_NOT_FOUND: + case (HRESULT)IDS_EE_PATH_TOO_LONG: + case INET_E_OBJECT_NOT_FOUND: + case INET_E_DATA_NOT_AVAILABLE: + case INET_E_DOWNLOAD_FAILURE: + case INET_E_UNKNOWN_PROTOCOL: + case (HRESULT)IDS_INET_E_SECURITY_PROBLEM: + case (HRESULT)IDS_EE_BAD_USER_PROFILE: + case (HRESULT)IDS_EE_ALREADY_EXISTS: + case IDS_EE_REFLECTIONONLY_LOADFAILURE: + case IDS_CLASSLOAD_32BITCLRLOADING64BITASSEMBLY: + break; + + case MK_E_SYNTAX: + hr = FUSION_E_INVALID_NAME; + break; + + case INET_E_CONNECTION_TIMEOUT: + hr = IDS_INET_E_CONNECTION_TIMEOUT; + break; + + case INET_E_CANNOT_CONNECT: + hr = IDS_INET_E_CANNOT_CONNECT; + break; + + case INET_E_RESOURCE_NOT_FOUND: + hr = IDS_INET_E_RESOURCE_NOT_FOUND; + break; + + case NTE_BAD_HASH: + case NTE_BAD_LEN: + case NTE_BAD_KEY: + case NTE_BAD_DATA: + case NTE_BAD_ALGID: + case NTE_BAD_FLAGS: + case NTE_BAD_HASH_STATE: + case NTE_BAD_UID: + case NTE_FAIL: + case NTE_BAD_TYPE: + case NTE_BAD_VER: + case NTE_BAD_SIGNATURE: + case NTE_SIGNATURE_FILE_BAD: + case CRYPT_E_HASH_VALUE: + hr = IDS_EE_HASH_VAL_FAILED; + break; + + default: + hr = IDS_EE_FILELOAD_ERROR_GENERIC; + break; + + } + + return (UINT) hr; +} + +#ifndef DACCESS_COMPILE + +//========================================================================== +// Throw a runtime exception based on the last Win32 error (GetLastError()) +//========================================================================== +VOID DECLSPEC_NORETURN RealCOMPlusThrowWin32() +{ + +// before we do anything else... + DWORD err = ::GetLastError(); + + CONTRACTL + { + THROWS; + DISABLED(GC_NOTRIGGER); // Must sanitize first pass handling to enable this + MODE_ANY; + } + CONTRACTL_END; + + RealCOMPlusThrowWin32(HRESULT_FROM_WIN32(err)); +} // VOID DECLSPEC_NORETURN RealCOMPlusThrowWin32() + +//========================================================================== +// Throw a runtime exception based on the last Win32 error (GetLastError()) +//========================================================================== +VOID DECLSPEC_NORETURN RealCOMPlusThrowWin32(HRESULT hr) +{ + CONTRACTL + { + THROWS; + DISABLED(GC_NOTRIGGER); // Must sanitize first pass handling to enable this + MODE_ANY; +} + CONTRACTL_END; + + // Force to ApplicationException for compatibility with previous versions. We would + // prefer a "Win32Exception" here. + EX_THROW(EEMessageException, (kApplicationException, hr, 0 /* resid*/, + NULL /* szArg1 */, NULL /* szArg2 */, NULL /* szArg3 */, NULL /* szArg4 */, + NULL /* szArg5 */, NULL /* szArg6 */)); +} // VOID DECLSPEC_NORETURN RealCOMPlusThrowWin32() + + +//========================================================================== +// Throw an OutOfMemoryError +//========================================================================== +VOID DECLSPEC_NORETURN RealCOMPlusThrowOM() +{ + CONTRACTL + { + THROWS; + DISABLED(GC_NOTRIGGER); // Must sanitize first pass handling to enable this + CANNOT_TAKE_LOCK; + MODE_ANY; + SO_TOLERANT; + SUPPORTS_DAC; + } + CONTRACTL_END; + + ThrowOutOfMemory(); +} + +//========================================================================== +// Throw an undecorated runtime exception. +//========================================================================== +VOID DECLSPEC_NORETURN RealCOMPlusThrow(RuntimeExceptionKind reKind) +{ + CONTRACTL + { + THROWS; + DISABLED(GC_NOTRIGGER); // Must sanitize first pass handling to enable this + MODE_ANY; + } + CONTRACTL_END; + + _ASSERTE((reKind != kExecutionEngineException) || + !"ExecutionEngineException shouldn't be thrown. Use EEPolicy to failfast or a better exception. The caller of this function should modify their code."); + + EX_THROW(EEException, (reKind)); +} + +//========================================================================== +// Throw a decorated runtime exception. +// Try using RealCOMPlusThrow(reKind, wszResourceName) instead. +//========================================================================== +VOID DECLSPEC_NORETURN RealCOMPlusThrowNonLocalized(RuntimeExceptionKind reKind, LPCWSTR wszTag) +{ + CONTRACTL + { + THROWS; + DISABLED(GC_NOTRIGGER); // Must sanitize first pass handling to enable this + MODE_ANY; + } + CONTRACTL_END; + + _ASSERTE((reKind != kExecutionEngineException) || + !"ExecutionEngineException shouldn't be thrown. Use EEPolicy to failfast or a better exception. The caller of this function should modify their code."); + + EX_THROW(EEMessageException, (reKind, IDS_EE_GENERIC, wszTag)); +} + +//========================================================================== +// Throw a runtime exception based on an HResult +//========================================================================== +VOID DECLSPEC_NORETURN RealCOMPlusThrowHR(HRESULT hr, IErrorInfo* pErrInfo, Exception * pInnerException) +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; // because of IErrorInfo + MODE_ANY; + } + CONTRACTL_END; + + _ASSERTE (FAILED(hr)); + + // Though we would like to assert this, it can happen in the following scenario: + // + // MgdCode --RCW-> COM --CCW-> MgdCode2 + // + // If MgdCode2 throws EEE, when it reaches the RCW, it will invoking MarshalNative::ThrowExceptionForHr and thus, + // reach here. Hence, we will need to keep the assert off, until user code is stopped for creating an EEE. + + //_ASSERTE((hr != COR_E_EXECUTIONENGINE) || + // !"ExecutionEngineException shouldn't be thrown. Use EEPolicy to failfast or a better exception. The caller of this function should modify their code."); + +#ifndef CROSSGEN_COMPILE +#ifdef FEATURE_COMINTEROP + // check for complus created IErrorInfo pointers + if (pErrInfo != NULL) + { + GCX_COOP(); + { + OBJECTREF oRetVal = NULL; + GCPROTECT_BEGIN(oRetVal); + GetExceptionForHR(hr, pErrInfo, &oRetVal); + _ASSERTE(oRetVal != NULL); + RealCOMPlusThrow(oRetVal); + GCPROTECT_END (); + } + } +#endif // FEATURE_COMINTEROP + + if (pErrInfo != NULL) + { + if (pInnerException == NULL) + { + EX_THROW(EECOMException, (hr, pErrInfo, true, NULL, FALSE)); + } + else + { + EX_THROW_WITH_INNER(EECOMException, (hr, pErrInfo, true, NULL, FALSE), pInnerException); + } + } + else +#endif // CROSSGEN_COMPILE + { + if (pInnerException == NULL) + { + EX_THROW(EEMessageException, (hr)); + } + else + { + EX_THROW_WITH_INNER(EEMessageException, (hr), pInnerException); + } + } +} + +VOID DECLSPEC_NORETURN RealCOMPlusThrowHR(HRESULT hr) +{ + CONTRACTL + { + THROWS; + DISABLED(GC_NOTRIGGER); // Must sanitize first pass handling to enable this + MODE_ANY; + } + CONTRACTL_END; + + + // ! COMPlusThrowHR(hr) no longer snags the IErrorInfo off the TLS (Too many places + // ! call this routine where no IErrorInfo was set by the prior call.) + // ! + // ! If you actually want to pull IErrorInfo off the TLS, call + // ! + // ! COMPlusThrowHR(hr, kGetErrorInfo) + + RealCOMPlusThrowHR(hr, (IErrorInfo*)NULL); +} + + +VOID DECLSPEC_NORETURN RealCOMPlusThrowHR(HRESULT hr, tagGetErrorInfo) +{ + CONTRACTL + { + THROWS; + DISABLED(GC_NOTRIGGER); // Must sanitize first pass handling to enable this + MODE_ANY; + } + CONTRACTL_END; + + // Get an IErrorInfo if one is available. + IErrorInfo *pErrInfo = NULL; + +#ifndef CROSSGEN_COMPILE + if (SafeGetErrorInfo(&pErrInfo) != S_OK) + pErrInfo = NULL; +#endif + + // Throw the exception. + RealCOMPlusThrowHR(hr, pErrInfo); +} + + + +VOID DECLSPEC_NORETURN RealCOMPlusThrowHR(HRESULT hr, UINT resID, LPCWSTR wszArg1, + LPCWSTR wszArg2, LPCWSTR wszArg3, LPCWSTR wszArg4, + LPCWSTR wszArg5, LPCWSTR wszArg6) +{ + CONTRACTL + { + THROWS; + DISABLED(GC_NOTRIGGER); // Must sanitize first pass handling to enable this + MODE_ANY; + } + CONTRACTL_END; + + _ASSERTE (FAILED(hr)); + + // Though we would like to assert this, it can happen in the following scenario: + // + // MgdCode --RCW-> COM --CCW-> MgdCode2 + // + // If MgdCode2 throws EEE, when it reaches the RCW, it will invoking MarshalNative::ThrowExceptionForHr and thus, + // reach here. Hence, we will need to keep the assert off, until user code is stopped for creating an EEE. + + //_ASSERTE((hr != COR_E_EXECUTIONENGINE) || + // !"ExecutionEngineException shouldn't be thrown. Use EEPolicy to failfast or a better exception. The caller of this function should modify their code."); + + EX_THROW(EEMessageException, + (hr, resID, wszArg1, wszArg2, wszArg3, wszArg4, wszArg5, wszArg6)); +} + +//========================================================================== +// Throw a decorated runtime exception with a localized message. +// Queries the ResourceManager for a corresponding resource value. +//========================================================================== +VOID DECLSPEC_NORETURN RealCOMPlusThrow(RuntimeExceptionKind reKind, LPCWSTR wszResourceName, Exception * pInnerException) +{ + CONTRACTL + { + THROWS; + DISABLED(GC_NOTRIGGER); // Must sanitize first pass handling to enable this + MODE_ANY; + PRECONDITION(CheckPointer(wszResourceName)); + } + CONTRACTL_END; + + _ASSERTE((reKind != kExecutionEngineException) || + !"ExecutionEngineException shouldn't be thrown. Use EEPolicy to failfast or a better exception. The caller of this function should modify their code."); + // + // For some reason, the compiler complains about unreachable code if + // we don't split the new from the throw. So we're left with this + // unnecessarily verbose syntax. + // + + if (pInnerException == NULL) + { + EX_THROW(EEResourceException, (reKind, wszResourceName)); + } + else + { + EX_THROW_WITH_INNER(EEResourceException, (reKind, wszResourceName), pInnerException); + } +} + +//========================================================================== +// Used by the classloader to record a managed exception object to explain +// why a classload got botched. +// +// - Can be called with gc enabled or disabled. +// This allows a catch-all error path to post a generic catchall error +// message w/out bonking more specific error messages posted by inner functions. +//========================================================================== +VOID DECLSPEC_NORETURN ThrowTypeLoadException(LPCWSTR pFullTypeName, + LPCWSTR pAssemblyName, + LPCUTF8 pMessageArg, + UINT resIDWhy) +{ + CONTRACTL + { + THROWS; + DISABLED(GC_NOTRIGGER); // Must sanitize first pass handling to enable this + MODE_ANY; + } + CONTRACTL_END; + + EX_THROW(EETypeLoadException, (pFullTypeName, pAssemblyName, pMessageArg, resIDWhy)); +} + + +//========================================================================== +// Used by the classloader to post illegal layout +//========================================================================== +VOID DECLSPEC_NORETURN ThrowFieldLayoutError(mdTypeDef cl, // cl of the NStruct being loaded + Module* pModule, // Module that defines the scope, loader and heap (for allocate FieldMarshalers) + DWORD dwOffset, // Offset of field + DWORD dwID) // Message id +{ + CONTRACTL + { + THROWS; + DISABLED(GC_NOTRIGGER); // Must sanitize first pass handling to enable this + MODE_ANY; + } + CONTRACTL_END; + + IMDInternalImport *pInternalImport = pModule->GetMDImport(); // Internal interface for the NStruct being loaded. + + LPCUTF8 pszName, pszNamespace; + if (FAILED(pInternalImport->GetNameOfTypeDef(cl, &pszName, &pszNamespace))) + { + pszName = pszNamespace = "Invalid TypeDef record"; + } + + CHAR offsetBuf[16]; + sprintf_s(offsetBuf, COUNTOF(offsetBuf), "%d", dwOffset); + offsetBuf[COUNTOF(offsetBuf) - 1] = '\0'; + + pModule->GetAssembly()->ThrowTypeLoadException(pszNamespace, + pszName, + offsetBuf, + dwID); +} + +//========================================================================== +// Throw an ArithmeticException +//========================================================================== +VOID DECLSPEC_NORETURN RealCOMPlusThrowArithmetic() +{ + CONTRACTL + { + THROWS; + DISABLED(GC_NOTRIGGER); // Must sanitize first pass handling to enable this + MODE_ANY; + } + CONTRACTL_END; + + RealCOMPlusThrow(kArithmeticException); +} + +//========================================================================== +// Throw an ArgumentNullException +//========================================================================== +VOID DECLSPEC_NORETURN RealCOMPlusThrowArgumentNull(LPCWSTR argName, LPCWSTR wszResourceName) +{ + CONTRACTL + { + THROWS; + DISABLED(GC_NOTRIGGER); // Must sanitize first pass handling to enable this + MODE_ANY; + PRECONDITION(CheckPointer(wszResourceName)); + } + CONTRACTL_END; + + EX_THROW(EEArgumentException, (kArgumentNullException, argName, wszResourceName)); +} + + +VOID DECLSPEC_NORETURN RealCOMPlusThrowArgumentNull(LPCWSTR argName) +{ + CONTRACTL + { + THROWS; + DISABLED(GC_NOTRIGGER); // Must sanitize first pass handling to enable this + MODE_ANY; + } + CONTRACTL_END; + + EX_THROW(EEArgumentException, (kArgumentNullException, argName, W("ArgumentNull_Generic"))); +} + + +//========================================================================== +// Throw an ArgumentOutOfRangeException +//========================================================================== +VOID DECLSPEC_NORETURN RealCOMPlusThrowArgumentOutOfRange(LPCWSTR argName, LPCWSTR wszResourceName) +{ + CONTRACTL + { + THROWS; + DISABLED(GC_NOTRIGGER); // Must sanitize first pass handling to enable this + MODE_ANY; + } + CONTRACTL_END; + + EX_THROW(EEArgumentException, (kArgumentOutOfRangeException, argName, wszResourceName)); +} + +//========================================================================== +// Throw an ArgumentException +//========================================================================== +VOID DECLSPEC_NORETURN RealCOMPlusThrowArgumentException(LPCWSTR argName, LPCWSTR wszResourceName) +{ + CONTRACTL + { + THROWS; + DISABLED(GC_NOTRIGGER); // Must sanitize first pass handling to enable this + MODE_ANY; + } + CONTRACTL_END; + + EX_THROW(EEArgumentException, (kArgumentException, argName, wszResourceName)); +} + +//========================================================================= +// Used by the classloader to record a managed exception object to explain +// why a classload got botched. +// +// - Can be called with gc enabled or disabled. +// This allows a catch-all error path to post a generic catchall error +// message w/out bonking more specific error messages posted by inner functions. +//========================================================================== +VOID DECLSPEC_NORETURN ThrowTypeLoadException(LPCUTF8 pszNameSpace, + LPCUTF8 pTypeName, + LPCWSTR pAssemblyName, + LPCUTF8 pMessageArg, + UINT resIDWhy) +{ + CONTRACTL + { + THROWS; + DISABLED(GC_NOTRIGGER); // Must sanitize first pass handling to enable this + MODE_ANY; + } + CONTRACTL_END; + + EX_THROW(EETypeLoadException, (pszNameSpace, pTypeName, pAssemblyName, pMessageArg, resIDWhy)); +} + +//========================================================================== +// Throw a decorated runtime exception. +//========================================================================== +VOID DECLSPEC_NORETURN RealCOMPlusThrow(RuntimeExceptionKind reKind, UINT resID, + LPCWSTR wszArg1, LPCWSTR wszArg2, LPCWSTR wszArg3, + LPCWSTR wszArg4, LPCWSTR wszArg5, LPCWSTR wszArg6) +{ + CONTRACTL + { + THROWS; + DISABLED(GC_NOTRIGGER); // Must sanitize first pass handling to enable this + MODE_ANY; + } + CONTRACTL_END; + + EX_THROW(EEMessageException, + (reKind, resID, wszArg1, wszArg2, wszArg3, wszArg4, wszArg5, wszArg6)); +} + +#ifdef FEATURE_COMINTEROP +#ifndef CROSSGEN_COMPILE +//========================================================================== +// Throw a runtime exception based on an HResult, check for error info +//========================================================================== +VOID DECLSPEC_NORETURN RealCOMPlusThrowHR(HRESULT hr, IUnknown *iface, REFIID riid) +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; // because of IErrorInfo + MODE_ANY; + } + CONTRACTL_END; + + IErrorInfo *info = NULL; + { + GCX_PREEMP(); + info = GetSupportedErrorInfo(iface, riid); + } + RealCOMPlusThrowHR(hr, info); +} + +//========================================================================== +// Throw a runtime exception based on an EXCEPINFO. This function will free +// the strings in the EXCEPINFO that is passed in. +//========================================================================== +VOID DECLSPEC_NORETURN RealCOMPlusThrowHR(EXCEPINFO *pExcepInfo) +{ + CONTRACTL + { + THROWS; + DISABLED(GC_NOTRIGGER); // Must sanitize first pass handling to enable this + MODE_ANY; + } + CONTRACTL_END; + + EX_THROW(EECOMException, (pExcepInfo)); +} +#endif //CROSSGEN_COMPILE + +#endif // FEATURE_COMINTEROP + + +#ifdef FEATURE_STACK_PROBE +//========================================================================== +// Throw a StackOverflowError +//========================================================================== +VOID DECLSPEC_NORETURN RealCOMPlusThrowSO() +{ + CONTRACTL + { + // This should be throws... But it isn't because a SO doesn't technically + // fall into the same THROW/NOTHROW conventions as the rest of the contract + // infrastructure. + NOTHROW; + + DISABLED(GC_NOTRIGGER); // Must sanitize first pass handling to enable this + SO_TOLERANT; + MODE_ANY; + } + CONTRACTL_END; + + // We only use BreakOnSO if we are in debug mode, so we'll only checking if the + // _DEBUG flag is set. +#ifdef _DEBUG + static int breakOnSO = -1; + + if (breakOnSO == -1) + breakOnSO = CLRConfig::GetConfigValue(CLRConfig::UNSUPPORTED_BreakOnSO); + + if (breakOnSO != 0) + { + _ASSERTE(!"SO occurred"); + } +#endif + + ThrowStackOverflow(); +} +#endif + +//========================================================================== +// Throw an InvalidCastException +//========================================================================== + +#ifdef FEATURE_FUSION +static const WCHAR *GetContextName(LOADCTX_TYPE kLoadContext, + BOOL fIntrospectionOnly) +{ + LIMITED_METHOD_CONTRACT; + + // Context names are treated as symbols and therefore not localized + switch (kLoadContext) + { + case LOADCTX_TYPE_DEFAULT: + return W("Default"); + case LOADCTX_TYPE_LOADFROM: + return W("LoadFrom"); + default: + return (fIntrospectionOnly ? W("InspectionContext") : W("LoadNeither")); + } +} +#endif + +VOID GetAssemblyDetailInfo(SString &sType, + SString &sAssemblyDisplayName, + PEAssembly *pPEAssembly, + SString &sAssemblyDetailInfo) +{ + WRAPPER_NO_CONTRACT; + + InlineSString<MAX_LONGPATH> sFormat; +#ifdef FEATURE_FUSION + const WCHAR *pwzLoadContext = GetContextName(pPEAssembly->GetLoadContext(), + pPEAssembly->IsIntrospectionOnly()); +#else + const WCHAR *pwzLoadContext = W("Default"); +#endif + + if (pPEAssembly->GetPath().IsEmpty()) + { + sFormat.LoadResource(CCompRC::Debugging, IDS_EE_CANNOTCAST_HELPER_BYTE); + + sAssemblyDetailInfo.Printf(sFormat.GetUnicode(), + sType.GetUnicode(), + sAssemblyDisplayName.GetUnicode(), + pwzLoadContext); + } + else + { + sFormat.LoadResource(CCompRC::Debugging, IDS_EE_CANNOTCAST_HELPER_PATH); + + sAssemblyDetailInfo.Printf(sFormat.GetUnicode(), + sType.GetUnicode(), + sAssemblyDisplayName.GetUnicode(), + pwzLoadContext, + pPEAssembly->GetPath().GetUnicode()); + } +} + +VOID CheckAndThrowSameTypeAndAssemblyInvalidCastException(TypeHandle thCastFrom, + TypeHandle thCastTo) +{ + CONTRACTL { + THROWS; + GC_TRIGGERS; + MODE_COOPERATIVE; + SO_INTOLERANT; + } CONTRACTL_END; + + Module *pModuleTypeFrom = thCastFrom.GetModule(); + Module *pModuleTypeTo = thCastTo.GetModule(); + + if ((pModuleTypeFrom != NULL) && (pModuleTypeTo != NULL)) + { + Assembly *pAssemblyTypeFrom = pModuleTypeFrom->GetAssembly(); + Assembly *pAssemblyTypeTo = pModuleTypeTo->GetAssembly(); + + _ASSERTE(pAssemblyTypeFrom != NULL); + _ASSERTE(pAssemblyTypeTo != NULL); + + PEAssembly *pPEAssemblyTypeFrom = pAssemblyTypeFrom->GetManifestFile(); + PEAssembly *pPEAssemblyTypeTo = pAssemblyTypeTo->GetManifestFile(); + + _ASSERTE(pPEAssemblyTypeFrom != NULL); + _ASSERTE(pPEAssemblyTypeTo != NULL); + + InlineSString<MAX_LONGPATH> sAssemblyFromDisplayName; + InlineSString<MAX_LONGPATH> sAssemblyToDisplayName; + + pPEAssemblyTypeFrom->GetDisplayName(sAssemblyFromDisplayName); + pPEAssemblyTypeTo->GetDisplayName(sAssemblyToDisplayName); + + // Found the culprit case. Now format the new exception text. + InlineSString<MAX_CLASSNAME_LENGTH + 1> strCastFromName; + InlineSString<MAX_CLASSNAME_LENGTH + 1> strCastToName; + InlineSString<MAX_LONGPATH> sAssemblyDetailInfoFrom; + InlineSString<MAX_LONGPATH> sAssemblyDetailInfoTo; + + thCastFrom.GetName(strCastFromName); + thCastTo.GetName(strCastToName); + + SString typeA = SL(W("A")); + GetAssemblyDetailInfo(typeA, + sAssemblyFromDisplayName, + pPEAssemblyTypeFrom, + sAssemblyDetailInfoFrom); + SString typeB = SL(W("B")); + GetAssemblyDetailInfo(typeB, + sAssemblyToDisplayName, + pPEAssemblyTypeTo, + sAssemblyDetailInfoTo); + + COMPlusThrow(kInvalidCastException, + IDS_EE_CANNOTCASTSAME, + strCastFromName.GetUnicode(), + strCastToName.GetUnicode(), + sAssemblyDetailInfoFrom.GetUnicode(), + sAssemblyDetailInfoTo.GetUnicode()); + } +} + +VOID RealCOMPlusThrowInvalidCastException(TypeHandle thCastFrom, TypeHandle thCastTo) +{ + CONTRACTL { + THROWS; + GC_TRIGGERS; + MODE_COOPERATIVE; + } CONTRACTL_END; + + // Use an InlineSString with a size of MAX_CLASSNAME_LENGTH + 1 to prevent + // TypeHandle::GetName from having to allocate a new block of memory. This + // significantly improves the performance of throwing an InvalidCastException. + InlineSString<MAX_CLASSNAME_LENGTH + 1> strCastFromName; + InlineSString<MAX_CLASSNAME_LENGTH + 1> strCastToName; + + thCastTo.GetName(strCastToName); +#ifdef FEATURE_REMOTING + if (thCastFrom.IsTransparentProxy()) + { + COMPlusThrow(kInvalidCastException, IDS_EE_CANNOTCASTPROXY, strCastToName.GetUnicode()); + } + else +#endif + { + thCastFrom.GetName(strCastFromName); + // Attempt to catch the A.T != A.T case that causes so much user confusion. + if (strCastFromName.Equals(strCastToName)) + { + CheckAndThrowSameTypeAndAssemblyInvalidCastException(thCastFrom, thCastTo); + } + COMPlusThrow(kInvalidCastException, IDS_EE_CANNOTCAST, strCastFromName.GetUnicode(), strCastToName.GetUnicode()); + } +} + +#ifndef CROSSGEN_COMPILE +VOID RealCOMPlusThrowInvalidCastException(OBJECTREF *pObj, TypeHandle thCastTo) +{ + CONTRACTL { + THROWS; + GC_TRIGGERS; + MODE_COOPERATIVE; + PRECONDITION(IsProtectedByGCFrame (pObj)); + } CONTRACTL_END; + + TypeHandle thCastFrom = (*pObj)->GetTypeHandle(); +#ifdef FEATURE_COMINTEROP + if (thCastFrom.GetMethodTable()->IsComObjectType()) + { + // Special case casting RCWs so we can give better error information when the + // cast fails. + ComObject::ThrowInvalidCastException(pObj, thCastTo.GetMethodTable()); + } +#endif + COMPlusThrowInvalidCastException(thCastFrom, thCastTo); +} +#endif // CROSSGEN_COMPILE + +#endif // DACCESS_COMPILE + +#ifndef CROSSGEN_COMPILE // ??? +#ifdef FEATURE_COMINTEROP +#include "comtoclrcall.h" +#endif // FEATURE_COMINTEROP + +// Reverse COM interop IL stubs need to catch all exceptions and translate them into HRESULTs. +// But we allow for CSEs to be rethrown. Our corrupting state policy gets applied to the +// original user-visible method that triggered the IL stub to be generated. So we must be able +// to map back from a given IL stub to the user-visible method. Here, we do that only when we +// see a 'matching' ComMethodFrame further up the stack. +MethodDesc * GetUserMethodForILStub(Thread * pThread, UINT_PTR uStubSP, MethodDesc * pILStubMD, Frame ** ppFrameOut) +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + MODE_ANY; + PRECONDITION(pILStubMD->IsILStub()); + } + CONTRACTL_END; + + MethodDesc * pUserMD = pILStubMD; +#ifdef FEATURE_COMINTEROP + DynamicMethodDesc * pDMD = pILStubMD->AsDynamicMethodDesc(); + if (pDMD->IsCOMToCLRStub()) + { + // There are some differences across architectures for "which" SP is passed in. + // On ARM, the SP is the SP on entry to the IL stub, on the other arches, it's + // a post-prolog SP. But this doesn't matter here because the COM->CLR path + // always pushes the Frame in a caller's stack frame. + + Frame * pCurFrame = pThread->GetFrame(); + while ((UINT_PTR)pCurFrame < uStubSP) + { + pCurFrame = pCurFrame->PtrNextFrame(); + } + + // The construction of the COM->CLR path ensures that our corresponding ComMethodFrame + // should be present further up the stack. Normally, the ComMethodFrame in question is + // simply the next stack frame; however, there are situations where there may be other + // stack frames present (such as an optional ContextTransitionFrame if we switched + // AppDomains, or an inlined stack frame from a QCall in the IL stub). + while (pCurFrame->GetVTablePtr() != ComMethodFrame::GetMethodFrameVPtr()) + { + pCurFrame = pCurFrame->PtrNextFrame(); + } + + ComMethodFrame * pComFrame = (ComMethodFrame *)pCurFrame; + _ASSERTE((UINT_PTR)pComFrame > uStubSP); + + CONSISTENCY_CHECK_MSG(pComFrame->GetVTablePtr() == ComMethodFrame::GetMethodFrameVPtr(), + "Expected to find a ComMethodFrame."); + + ComCallMethodDesc * pCMD = pComFrame->GetComCallMethodDesc(); + + CONSISTENCY_CHECK_MSG(pILStubMD == ExecutionManager::GetCodeMethodDesc(pCMD->GetILStub()), + "The ComMethodFrame that we found doesn't match the IL stub passed in."); + + pUserMD = pCMD->GetMethodDesc(); + *ppFrameOut = pComFrame; + } +#endif // FEATURE_COMINTEROP + return pUserMD; +} +#endif //CROSSGEN_COMPILE |