summaryrefslogtreecommitdiff
path: root/src/vm/i386/excepx86.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/vm/i386/excepx86.cpp')
-rw-r--r--src/vm/i386/excepx86.cpp3734
1 files changed, 3734 insertions, 0 deletions
diff --git a/src/vm/i386/excepx86.cpp b/src/vm/i386/excepx86.cpp
new file mode 100644
index 0000000000..27c923b749
--- /dev/null
+++ b/src/vm/i386/excepx86.cpp
@@ -0,0 +1,3734 @@
+// 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 "excep.h"
+#include "object.h"
+#include "field.h"
+#include "dbginterface.h"
+#include "cgensys.h"
+#include "comutilnative.h"
+#include "sigformat.h"
+#include "siginfo.hpp"
+#include "gc.h"
+#include "eedbginterfaceimpl.h" //so we can clearexception in COMPlusThrow
+#include "perfcounters.h"
+#include "eventtrace.h"
+#include "eetoprofinterfacewrapper.inl"
+#include "eedbginterfaceimpl.inl"
+#include "dllimportcallback.h"
+#include "threads.h"
+#ifdef FEATURE_REMOTING
+#include "appdomainhelper.h"
+#endif
+#include "eeconfig.h"
+#include "vars.hpp"
+#include "generics.h"
+#include "securityprincipal.h"
+
+#include "asmconstants.h"
+#include "virtualcallstub.h"
+
+MethodDesc * GetUserMethodForILStub(Thread * pThread, UINT_PTR uStubSP, MethodDesc * pILStubMD, Frame ** ppFrameOut);
+
+#if !defined(DACCESS_COMPILE)
+
+#define FORMAT_MESSAGE_BUFFER_LENGTH 1024
+
+BOOL ComPlusFrameSEH(EXCEPTION_REGISTRATION_RECORD*);
+PEXCEPTION_REGISTRATION_RECORD GetPrevSEHRecord(EXCEPTION_REGISTRATION_RECORD*);
+
+extern "C" {
+// in asmhelpers.asm:
+VOID STDCALL ResumeAtJitEHHelper(EHContext *pContext);
+int STDCALL CallJitEHFilterHelper(size_t *pShadowSP, EHContext *pContext);
+VOID STDCALL CallJitEHFinallyHelper(size_t *pShadowSP, EHContext *pContext);
+
+BOOL CallRtlUnwind(EXCEPTION_REGISTRATION_RECORD *pEstablisherFrame,
+ void *callback,
+ EXCEPTION_RECORD *pExceptionRecord,
+ void *retval);
+
+BOOL CallRtlUnwindSafe(EXCEPTION_REGISTRATION_RECORD *pEstablisherFrame,
+ void *callback,
+ EXCEPTION_RECORD *pExceptionRecord,
+ void *retval);
+}
+
+static inline BOOL
+CPFH_ShouldUnwindStack(const EXCEPTION_RECORD * pCER) {
+
+ LIMITED_METHOD_CONTRACT;
+
+ _ASSERTE(pCER != NULL);
+
+ // We can only unwind those exceptions whose context/record we don't need for a
+ // rethrow. This is complus, and stack overflow. For all the others, we
+ // need to keep the context around for a rethrow, which means they can't
+ // be unwound.
+ if (IsComPlusException(pCER) || pCER->ExceptionCode == STATUS_STACK_OVERFLOW)
+ return TRUE;
+ else
+ return FALSE;
+}
+
+static inline BOOL IsComPlusNestedExceptionRecord(EXCEPTION_REGISTRATION_RECORD* pEHR)
+{
+ LIMITED_METHOD_CONTRACT;
+ if (pEHR->Handler == (PEXCEPTION_ROUTINE)COMPlusNestedExceptionHandler)
+ return TRUE;
+ return FALSE;
+}
+
+EXCEPTION_REGISTRATION_RECORD *TryFindNestedEstablisherFrame(EXCEPTION_REGISTRATION_RECORD *pEstablisherFrame)
+{
+ LIMITED_METHOD_CONTRACT;
+ while (pEstablisherFrame->Handler != (PEXCEPTION_ROUTINE)COMPlusNestedExceptionHandler) {
+ pEstablisherFrame = pEstablisherFrame->Next;
+ if (pEstablisherFrame == EXCEPTION_CHAIN_END) return 0;
+ }
+ return pEstablisherFrame;
+}
+
+#ifdef _DEBUG
+// stores last handler we went to in case we didn't get an endcatch and stack is
+// corrupted we can figure out who did it.
+static MethodDesc *gLastResumedExceptionFunc = NULL;
+static DWORD gLastResumedExceptionHandler = 0;
+#endif
+
+//---------------------------------------------------------------------
+// void RtlUnwindCallback()
+// call back function after global unwind, rtlunwind calls this function
+//---------------------------------------------------------------------
+static void RtlUnwindCallback()
+{
+ LIMITED_METHOD_CONTRACT;
+ _ASSERTE(!"Should never get here");
+}
+
+BOOL NExportSEH(EXCEPTION_REGISTRATION_RECORD* pEHR)
+{
+ LIMITED_METHOD_CONTRACT;
+
+ if ((LPVOID)pEHR->Handler == (LPVOID)UMThunkPrestubHandler)
+ {
+ return TRUE;
+ }
+ return FALSE;
+}
+
+BOOL FastNExportSEH(EXCEPTION_REGISTRATION_RECORD* pEHR)
+{
+ LIMITED_METHOD_CONTRACT;
+
+ if ((LPVOID)pEHR->Handler == (LPVOID)FastNExportExceptHandler)
+ return TRUE;
+ return FALSE;
+}
+
+BOOL ReverseCOMSEH(EXCEPTION_REGISTRATION_RECORD* pEHR)
+{
+ LIMITED_METHOD_CONTRACT;
+
+#ifdef FEATURE_COMINTEROP
+ if ((LPVOID)pEHR->Handler == (LPVOID)COMPlusFrameHandlerRevCom)
+ return TRUE;
+#endif // FEATURE_COMINTEROP
+ return FALSE;
+}
+
+
+//
+// Returns true if the given SEH handler is one of our SEH handlers that is responsible for managing exceptions in
+// regions of managed code.
+//
+BOOL IsUnmanagedToManagedSEHHandler(EXCEPTION_REGISTRATION_RECORD *pEstablisherFrame)
+{
+ WRAPPER_NO_CONTRACT;
+
+ //
+ // ComPlusFrameSEH() is for COMPlusFrameHandler & COMPlusNestedExceptionHandler.
+ // FastNExportSEH() is for FastNExportExceptHandler.
+ // NExportSEH() is for UMThunkPrestubHandler.
+ //
+ return (ComPlusFrameSEH(pEstablisherFrame) || FastNExportSEH(pEstablisherFrame) || NExportSEH(pEstablisherFrame) || ReverseCOMSEH(pEstablisherFrame));
+}
+
+Frame *GetCurrFrame(EXCEPTION_REGISTRATION_RECORD *pEstablisherFrame)
+{
+ Frame *pFrame;
+ WRAPPER_NO_CONTRACT;
+ _ASSERTE(IsUnmanagedToManagedSEHHandler(pEstablisherFrame));
+ if (NExportSEH(pEstablisherFrame))
+ pFrame = ((ComToManagedExRecord *)pEstablisherFrame)->GetCurrFrame();
+ else
+ pFrame = ((FrameHandlerExRecord *)pEstablisherFrame)->GetCurrFrame();
+
+ _ASSERTE(GetThread() == NULL || GetThread()->GetFrame() <= pFrame);
+
+ return pFrame;
+}
+
+EXCEPTION_REGISTRATION_RECORD* GetNextCOMPlusSEHRecord(EXCEPTION_REGISTRATION_RECORD* pRec) {
+ WRAPPER_NO_CONTRACT;
+ if (pRec == EXCEPTION_CHAIN_END)
+ return EXCEPTION_CHAIN_END;
+
+ do {
+ _ASSERTE(pRec != 0);
+ pRec = pRec->Next;
+ } while (pRec != EXCEPTION_CHAIN_END && !IsUnmanagedToManagedSEHHandler(pRec));
+
+ _ASSERTE(pRec == EXCEPTION_CHAIN_END || IsUnmanagedToManagedSEHHandler(pRec));
+ return pRec;
+}
+
+
+/*
+ * GetClrSEHRecordServicingStackPointer
+ *
+ * This function searchs all the Frame SEH records, and finds the one that is
+ * currently signed up to do all exception handling for the given stack pointer
+ * on the given thread.
+ *
+ * Parameters:
+ * pThread - The thread to search on.
+ * pStackPointer - The stack location that we are finding the Frame SEH Record for.
+ *
+ * Returns
+ * A pointer to the SEH record, or EXCEPTION_CHAIN_END if none was found.
+ *
+ */
+
+PEXCEPTION_REGISTRATION_RECORD
+GetClrSEHRecordServicingStackPointer(Thread *pThread,
+ void *pStackPointer)
+{
+ ThreadExceptionState* pExState = pThread->GetExceptionState();
+
+ //
+ // We can only do this if there is a context in the pExInfo. There are cases (most notably the
+ // EEPolicy::HandleFatalError case) where we don't have that. In these cases we will return
+ // no enclosing handler since we cannot accurately determine the FS:0 entry which services
+ // this stack address.
+ //
+ // The side effect of this is that for these cases, the debugger cannot intercept
+ // the exception
+ //
+ CONTEXT* pContextRecord = pExState->GetContextRecord();
+ if (pContextRecord == NULL)
+ {
+ return EXCEPTION_CHAIN_END;
+ }
+
+ void *exceptionSP = dac_cast<PTR_VOID>(GetSP(pContextRecord));
+
+
+ //
+ // Now set the establishing frame. What this means in English is that we need to find
+ // the fs:0 entry that handles exceptions for the place on the stack given in stackPointer.
+ //
+ PEXCEPTION_REGISTRATION_RECORD pSEHRecord = GetFirstCOMPlusSEHRecord(pThread);
+
+ while (pSEHRecord != EXCEPTION_CHAIN_END)
+ {
+
+ //
+ // Skip any SEHRecord which is not a CLR record or was pushed after the exception
+ // on this thread occurred.
+ //
+ if (IsUnmanagedToManagedSEHHandler(pSEHRecord) && (exceptionSP <= (void *)pSEHRecord))
+ {
+ Frame *pFrame = GetCurrFrame(pSEHRecord);
+ //
+ // Arcane knowledge here. All Frame records are stored on the stack by the runtime
+ // in ever decreasing address space. So, we merely have to search back until
+ // we find the first frame record with a higher stack value to find the
+ // establishing frame for the given stack address.
+ //
+ if (((void *)pFrame) >= pStackPointer)
+ {
+ break;
+ }
+
+ }
+
+ pSEHRecord = GetNextCOMPlusSEHRecord(pSEHRecord);
+ }
+
+ return pSEHRecord;
+}
+
+#ifdef _DEBUG
+// We've deteremined during a stack walk that managed code is transitioning to unamanaged (EE) code. Check that the
+// state of the EH chain is correct.
+//
+// For x86, check that we do INSTALL_COMPLUS_EXCEPTION_HANDLER before calling managed code. This check should be
+// done for all managed code sites, not just transistions. But this will catch most problem cases.
+void VerifyValidTransitionFromManagedCode(Thread *pThread, CrawlFrame *pCF)
+{
+ WRAPPER_NO_CONTRACT;
+
+ _ASSERTE(ExecutionManager::IsManagedCode(GetControlPC(pCF->GetRegisterSet())));
+
+ // Cannot get to the TEB of other threads. So ignore them.
+ if (pThread != GetThread())
+ {
+ return;
+ }
+
+ // Find the EH record guarding the current region of managed code, based on the CrawlFrame passed in.
+ PEXCEPTION_REGISTRATION_RECORD pEHR = GetCurrentSEHRecord();
+
+ while ((pEHR != EXCEPTION_CHAIN_END) && ((ULONG_PTR)pEHR < GetRegdisplaySP(pCF->GetRegisterSet())))
+ {
+ pEHR = pEHR->Next;
+ }
+
+ // VerifyValidTransitionFromManagedCode can be called before the CrawlFrame's MethodDesc is initialized.
+ // Fix that if necessary for the consistency check.
+ MethodDesc * pFunction = pCF->GetFunction();
+ if ((!IsUnmanagedToManagedSEHHandler(pEHR)) && // Will the assert fire? If not, don't waste our time.
+ (pFunction == NULL))
+ {
+ _ASSERTE(pCF->GetRegisterSet());
+ PCODE ip = GetControlPC(pCF->GetRegisterSet());
+ pFunction = ExecutionManager::GetCodeMethodDesc(ip);
+ _ASSERTE(pFunction);
+ }
+
+ // Great, we've got the EH record that's next up the stack from the current SP (which is in managed code). That
+ // had better be a record for one of our handlers responsible for handling exceptions in managed code. If its
+ // not, then someone made it into managed code without setting up one of our EH handlers, and that's really
+ // bad.
+ CONSISTENCY_CHECK_MSGF(IsUnmanagedToManagedSEHHandler(pEHR),
+ ("Invalid transition into managed code!\n\n"
+ "We're walking this thread's stack and we've reached a managed frame at Esp=0x%p. "
+ "(The method is %s::%s) "
+ "The very next FS:0 record (0x%p) up from this point on the stack should be one of "
+ "our 'unmanaged to managed SEH handlers', but its not... its something else, and "
+ "that's very bad. It indicates that someone managed to call into managed code without "
+ "setting up the proper exception handling.\n\n"
+ "Get a good unmanaged stack trace for this thread. All FS:0 records are on the stack, "
+ "so you can see who installed the last handler. Somewhere between that function and "
+ "where the thread is now is where the bad transition occurred.\n\n"
+ "A little extra info: FS:0 = 0x%p, pEHR->Handler = 0x%p\n",
+ GetRegdisplaySP(pCF->GetRegisterSet()),
+ pFunction ->m_pszDebugClassName,
+ pFunction ->m_pszDebugMethodName,
+ pEHR,
+ GetCurrentSEHRecord(),
+ pEHR->Handler));
+}
+
+#endif
+
+//================================================================================
+
+// There are some things that should never be true when handling an
+// exception. This function checks for them. Will assert or trap
+// if it finds an error.
+static inline void
+CPFH_VerifyThreadIsInValidState(Thread* pThread, DWORD exceptionCode, EXCEPTION_REGISTRATION_RECORD *pEstablisherFrame) {
+ WRAPPER_NO_CONTRACT;
+
+ if ( exceptionCode == STATUS_BREAKPOINT
+ || exceptionCode == STATUS_SINGLE_STEP) {
+ return;
+ }
+
+#ifdef _DEBUG
+ // check for overwriting of stack
+ CheckStackBarrier(pEstablisherFrame);
+ // trigger check for bad fs:0 chain
+ GetCurrentSEHRecord();
+#endif
+
+ if (!g_fEEShutDown) {
+ // An exception on the GC thread, or while holding the thread store lock, will likely lock out the entire process.
+ if (::IsGCThread() || ThreadStore::HoldingThreadStore())
+ {
+ _ASSERTE(!"Exception during garbage collection or while holding thread store");
+ EEPOLICY_HANDLE_FATAL_ERROR(COR_E_EXECUTIONENGINE);
+ }
+ }
+}
+
+
+#ifdef FEATURE_HIJACK
+void
+CPFH_AdjustContextForThreadSuspensionRace(CONTEXT *pContext, Thread *pThread)
+{
+ WRAPPER_NO_CONTRACT;
+
+ PCODE f_IP = GetIP(pContext);
+ if (Thread::IsAddrOfRedirectFunc((PVOID)f_IP)) {
+
+ // This is a very rare case where we tried to redirect a thread that was
+ // just about to dispatch an exception, and our update of EIP took, but
+ // the thread continued dispatching the exception.
+ //
+ // If this should happen (very rare) then we fix it up here.
+ //
+ _ASSERTE(pThread->GetSavedRedirectContext());
+ SetIP(pContext, GetIP(pThread->GetSavedRedirectContext()));
+ STRESS_LOG1(LF_EH, LL_INFO100, "CPFH_AdjustContextForThreadSuspensionRace: Case 1 setting IP = %x\n", pContext->Eip);
+ }
+
+ if (f_IP == GetEEFuncEntryPoint(THROW_CONTROL_FOR_THREAD_FUNCTION)) {
+
+ // This is a very rare case where we tried to redirect a thread that was
+ // just about to dispatch an exception, and our update of EIP took, but
+ // the thread continued dispatching the exception.
+ //
+ // If this should happen (very rare) then we fix it up here.
+ //
+ SetIP(pContext, GetIP(pThread->m_OSContext));
+ STRESS_LOG1(LF_EH, LL_INFO100, "CPFH_AdjustContextForThreadSuspensionRace: Case 2 setting IP = %x\n", pContext->Eip);
+ }
+
+// We have another even rarer race condition:
+// - A) On thread A, Debugger puts an int 3 in the code stream at address X
+// - A) We hit it and the begin an exception. The eip will be X + 1 (int3 is special)
+// - B) Meanwhile, thread B redirects A's eip to Y. (Although A is really somewhere
+// in the kernel, it looks like it's still in user code, so it can fall under the
+// HandledJitCase and can be redirected)
+// - A) The OS, trying to be nice, expects we have a breakpoint exception at X+1,
+// but does -1 on the address since it knows int3 will leave the eip +1.
+// So the context structure it will pass to the Handler is ideally (X+1)-1 = X
+//
+// ** Here's the race: Since thread B redirected A, the eip is actually Y (not X+1),
+// but the kernel still touches it up to Y-1. So there's a window between when we hit a
+// bp and when the handler gets called that this can happen.
+// This causes an unhandled BP (since the debugger doesn't recognize the bp at Y-1)
+//
+// So what to do: If we land at Y-1 (ie, if f_IP+1 is the addr of a Redirected Func),
+// then restore the EIP back to X. This will skip the redirection.
+// Fortunately, this only occurs in cases where it's ok
+// to skip. The debugger will recognize the patch and handle it.
+
+ if (Thread::IsAddrOfRedirectFunc((PVOID)(f_IP + 1))) {
+ _ASSERTE(pThread->GetSavedRedirectContext());
+ SetIP(pContext, GetIP(pThread->GetSavedRedirectContext()) - 1);
+ STRESS_LOG1(LF_EH, LL_INFO100, "CPFH_AdjustContextForThreadSuspensionRace: Case 3 setting IP = %x\n", pContext->Eip);
+ }
+
+ if (f_IP + 1 == GetEEFuncEntryPoint(THROW_CONTROL_FOR_THREAD_FUNCTION)) {
+ SetIP(pContext, GetIP(pThread->m_OSContext) - 1);
+ STRESS_LOG1(LF_EH, LL_INFO100, "CPFH_AdjustContextForThreadSuspensionRace: Case 4 setting IP = %x\n", pContext->Eip);
+ }
+}
+#endif // FEATURE_HIJACK
+
+
+// We want to leave true null reference exceptions alone. But if we are
+// trashing memory, we don't want the application to swallow it. The 0x100
+// below will give us false positives for debugging, if the app is accessing
+// a field more than 256 bytes down an object, where the reference is null.
+//
+// Removed use of the IgnoreUnmanagedExceptions reg key...simply return false now.
+//
+static inline BOOL
+CPFH_ShouldIgnoreException(EXCEPTION_RECORD *pExceptionRecord) {
+ LIMITED_METHOD_CONTRACT;
+ return FALSE;
+}
+
+static inline void
+CPFH_UpdatePerformanceCounters() {
+ WRAPPER_NO_CONTRACT;
+ COUNTER_ONLY(GetPerfCounters().m_Excep.cThrown++);
+}
+
+
+//******************************************************************************
+EXCEPTION_DISPOSITION COMPlusAfterUnwind(
+ EXCEPTION_RECORD *pExceptionRecord,
+ EXCEPTION_REGISTRATION_RECORD *pEstablisherFrame,
+ ThrowCallbackType& tct)
+{
+ WRAPPER_NO_CONTRACT;
+
+ // Note: we've completed the unwind pass up to the establisher frame, and we're headed off to finish our
+ // cleanup and end up back in jitted code. Any more FS0 handlers pushed from this point on out will _not_ be
+ // unwound. We go ahead and assert right here that indeed there are no handlers below the establisher frame
+ // before we go any further.
+ _ASSERTE(pEstablisherFrame == GetCurrentSEHRecord());
+
+ Thread* pThread = GetThread();
+
+ _ASSERTE(tct.pCurrentExceptionRecord == pEstablisherFrame);
+
+ NestedHandlerExRecord nestedHandlerExRecord;
+ nestedHandlerExRecord.Init((PEXCEPTION_ROUTINE)COMPlusNestedExceptionHandler, GetCurrFrame(pEstablisherFrame));
+
+ // ... and now, put the nested record back on.
+ INSTALL_EXCEPTION_HANDLING_RECORD(&(nestedHandlerExRecord.m_ExReg));
+
+ // We entered COMPlusAfterUnwind in PREEMP, but we need to be in COOP from here on out
+ GCX_COOP_NO_DTOR();
+
+ tct.bIsUnwind = TRUE;
+ tct.pProfilerNotify = NULL;
+
+ LOG((LF_EH, LL_INFO100, "COMPlusFrameHandler: unwinding\n"));
+
+ tct.bUnwindStack = CPFH_ShouldUnwindStack(pExceptionRecord);
+
+ LOG((LF_EH, LL_INFO1000, "COMPlusAfterUnwind: going to: pFunc:%#X, pStack:%#X\n",
+ tct.pFunc, tct.pStack));
+
+ // TODO: UnwindFrames ends up calling into StackWalkFrames which is SO_INTOLERANT
+ // as is UnwindFrames, etc... Should we make COMPlusAfterUnwind SO_INTOLERANT???
+ ANNOTATION_VIOLATION(SOToleranceViolation);
+
+ UnwindFrames(pThread, &tct);
+
+#ifdef DEBUGGING_SUPPORTED
+ ExInfo* pExInfo = pThread->GetExceptionState()->GetCurrentExceptionTracker();
+ if (pExInfo->m_ValidInterceptionContext)
+ {
+ // By now we should have all unknown FS:[0] handlers unwinded along with the managed Frames until
+ // the interception point. We can now pop nested exception handlers and resume at interception context.
+ EHContext context = pExInfo->m_InterceptionContext;
+ pExInfo->m_InterceptionContext.Init();
+ pExInfo->m_ValidInterceptionContext = FALSE;
+
+ UnwindExceptionTrackerAndResumeInInterceptionFrame(pExInfo, &context);
+ }
+#endif // DEBUGGING_SUPPORTED
+
+ _ASSERTE(!"Should not get here");
+ return ExceptionContinueSearch;
+} // EXCEPTION_DISPOSITION COMPlusAfterUnwind()
+
+#ifdef DEBUGGING_SUPPORTED
+
+//---------------------------------------------------------------------------------------
+//
+// This function is called to intercept an exception and start an unwind.
+//
+// Arguments:
+// pCurrentEstablisherFrame - the exception registration record covering the stack range
+// containing the interception point
+// pExceptionRecord - EXCEPTION_RECORD of the exception being intercepted
+//
+// Return Value:
+// ExceptionContinueSearch if the exception cannot be intercepted
+//
+// Notes:
+// If the exception is intercepted, this function never returns.
+//
+
+EXCEPTION_DISPOSITION ClrDebuggerDoUnwindAndIntercept(EXCEPTION_REGISTRATION_RECORD *pCurrentEstablisherFrame,
+ EXCEPTION_RECORD *pExceptionRecord)
+{
+ WRAPPER_NO_CONTRACT;
+
+ if (!CheckThreadExceptionStateForInterception())
+ {
+ return ExceptionContinueSearch;
+ }
+
+ Thread* pThread = GetThread();
+ ThreadExceptionState* pExState = pThread->GetExceptionState();
+
+ EXCEPTION_REGISTRATION_RECORD *pEstablisherFrame;
+ ThrowCallbackType tct;
+ tct.Init();
+
+ pExState->GetDebuggerState()->GetDebuggerInterceptInfo(&pEstablisherFrame,
+ &(tct.pFunc),
+ &(tct.dHandler),
+ &(tct.pStack),
+ NULL,
+ &(tct.pBottomFrame)
+ );
+
+ //
+ // If the handler that we've selected as the handler for the target frame of the unwind is in fact above the
+ // handler that we're currently executing in, then use the current handler instead. Why? Our handlers for
+ // nested exceptions actually process managed frames that live above them, up to the COMPlusFrameHanlder that
+ // pushed the nested handler. If the user selectes a frame above the nested handler, then we will have selected
+ // the COMPlusFrameHandler above the current nested handler. But we don't want to ask RtlUnwind to unwind past
+ // the nested handler that we're currently executing in.
+ //
+ if (pEstablisherFrame > pCurrentEstablisherFrame)
+ {
+ // This should only happen if we're in a COMPlusNestedExceptionHandler.
+ _ASSERTE(IsComPlusNestedExceptionRecord(pCurrentEstablisherFrame));
+
+ pEstablisherFrame = pCurrentEstablisherFrame;
+ }
+
+#ifdef _DEBUG
+ tct.pCurrentExceptionRecord = pEstablisherFrame;
+#endif
+
+ LOG((LF_EH|LF_CORDB, LL_INFO100, "ClrDebuggerDoUnwindAndIntercept: Intercepting at %s\n", tct.pFunc->m_pszDebugMethodName));
+ LOG((LF_EH|LF_CORDB, LL_INFO100, "\t\t: pFunc is 0x%X\n", tct.pFunc));
+ LOG((LF_EH|LF_CORDB, LL_INFO100, "\t\t: pStack is 0x%X\n", tct.pStack));
+
+ CallRtlUnwindSafe(pEstablisherFrame, RtlUnwindCallback, pExceptionRecord, 0);
+
+ ExInfo* pExInfo = pThread->GetExceptionState()->GetCurrentExceptionTracker();
+ if (pExInfo->m_ValidInterceptionContext)
+ {
+ // By now we should have all unknown FS:[0] handlers unwinded along with the managed Frames until
+ // the interception point. We can now pop nested exception handlers and resume at interception context.
+ GCX_COOP();
+ EHContext context = pExInfo->m_InterceptionContext;
+ pExInfo->m_InterceptionContext.Init();
+ pExInfo->m_ValidInterceptionContext = FALSE;
+
+ UnwindExceptionTrackerAndResumeInInterceptionFrame(pExInfo, &context);
+ }
+
+ // on x86 at least, RtlUnwind always returns
+
+ // Note: we've completed the unwind pass up to the establisher frame, and we're headed off to finish our
+ // cleanup and end up back in jitted code. Any more FS0 handlers pushed from this point on out will _not_ be
+ // unwound.
+ return COMPlusAfterUnwind(pExState->GetExceptionRecord(), pEstablisherFrame, tct);
+} // EXCEPTION_DISPOSITION ClrDebuggerDoUnwindAndIntercept()
+
+#endif // DEBUGGING_SUPPORTED
+
+// This is a wrapper around the assembly routine that invokes RtlUnwind in the OS.
+// When we invoke RtlUnwind, the OS will modify the ExceptionFlags field in the
+// exception record to reflect unwind. Since we call RtlUnwind in the first pass
+// with a valid exception record when we find an exception handler AND because RtlUnwind
+// returns on x86, the OS would have flagged the exception record for unwind.
+//
+// Incase the exception is rethrown from the catch/filter-handler AND it's a non-COMPLUS
+// exception, the runtime will use the reference to the saved exception record to reraise
+// the exception, as part of rethrow fixup. Since the OS would have modified the exception record
+// to reflect unwind, this wrapper will "reset" the ExceptionFlags field when RtlUnwind returns.
+// Otherwise, the rethrow will result in second pass, as opposed to first, since the ExceptionFlags
+// would indicate an unwind.
+//
+// This rethrow issue does not affect COMPLUS exceptions since we always create a brand new exception
+// record for them in RaiseTheExceptionInternalOnly.
+BOOL CallRtlUnwindSafe(EXCEPTION_REGISTRATION_RECORD *pEstablisherFrame,
+ void *callback,
+ EXCEPTION_RECORD *pExceptionRecord,
+ void *retval)
+{
+ LIMITED_METHOD_CONTRACT;
+
+ // Save the ExceptionFlags value before invoking RtlUnwind.
+ DWORD dwExceptionFlags = pExceptionRecord->ExceptionFlags;
+
+ BOOL fRetVal = CallRtlUnwind(pEstablisherFrame, callback, pExceptionRecord, retval);
+
+ // Reset ExceptionFlags field, if applicable
+ if (pExceptionRecord->ExceptionFlags != dwExceptionFlags)
+ {
+ // We would expect the 32bit OS to have set the unwind flag at this point.
+ _ASSERTE(pExceptionRecord->ExceptionFlags & EXCEPTION_UNWINDING);
+ LOG((LF_EH, LL_INFO100, "CallRtlUnwindSafe: Resetting ExceptionFlags from %lu to %lu\n", pExceptionRecord->ExceptionFlags, dwExceptionFlags));
+ pExceptionRecord->ExceptionFlags = dwExceptionFlags;
+ }
+
+ return fRetVal;
+}
+
+//******************************************************************************
+// The essence of the first pass handler (after we've decided to actually do
+// the first pass handling).
+//******************************************************************************
+inline EXCEPTION_DISPOSITION __cdecl
+CPFH_RealFirstPassHandler( // ExceptionContinueSearch, etc.
+ EXCEPTION_RECORD *pExceptionRecord, // The exception record, with exception type.
+ EXCEPTION_REGISTRATION_RECORD *pEstablisherFrame, // Exception frame on whose behalf this is called.
+ CONTEXT *pContext, // Context from the exception.
+ void *pDispatcherContext, // @todo
+ BOOL bAsynchronousThreadStop, // @todo
+ BOOL fPGCDisabledOnEntry) // @todo
+{
+ // 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;
+ STATIC_CONTRACT_SO_TOLERANT;
+
+#ifdef _DEBUG
+ static int breakOnFirstPass = -1;
+
+ if (breakOnFirstPass == -1)
+ breakOnFirstPass = CLRConfig::GetConfigValue(CLRConfig::INTERNAL_BreakOnFirstPass);
+
+ if (breakOnFirstPass != 0)
+ {
+ _ASSERTE(!"First pass exception handler");
+ }
+#endif
+
+ EXCEPTION_DISPOSITION retval;
+ DWORD exceptionCode = pExceptionRecord->ExceptionCode;
+ Thread *pThread = GetThread();
+
+#ifdef _DEBUG
+ static int breakOnSO = -1;
+
+ if (breakOnSO == -1)
+ breakOnSO = CLRConfig::GetConfigValue(CLRConfig::UNSUPPORTED_BreakOnSO);
+
+ if (breakOnSO != 0 && exceptionCode == STATUS_STACK_OVERFLOW)
+ {
+ DebugBreak(); // ASSERTing will overwrite the guard region
+ }
+#endif
+
+ // We always want to be in co-operative mode when we run this function and whenever we return
+ // from it, want to go to pre-emptive mode because are returning to OS.
+ _ASSERTE(pThread->PreemptiveGCDisabled());
+
+ BOOL bPopNestedHandlerExRecord = FALSE;
+ LFH found = LFH_NOT_FOUND; // Result of calling LookForHandler.
+ BOOL bRethrownException = FALSE;
+ BOOL bNestedException = FALSE;
+
+#if defined(USE_FEF)
+ BOOL bPopFaultingExceptionFrame = FALSE;
+ FrameWithCookie<FaultingExceptionFrame> faultingExceptionFrame;
+#endif // USE_FEF
+ ExInfo* pExInfo = &(pThread->GetExceptionState()->m_currentExInfo);
+
+ ThrowCallbackType tct;
+ tct.Init();
+
+ tct.pTopFrame = GetCurrFrame(pEstablisherFrame); // highest frame to search to
+
+#ifdef _DEBUG
+ tct.pCurrentExceptionRecord = pEstablisherFrame;
+ tct.pPrevExceptionRecord = GetPrevSEHRecord(pEstablisherFrame);
+#endif // _DEBUG
+
+ BOOL fIsManagedCode = pContext ? ExecutionManager::IsManagedCode(GetIP(pContext)) : FALSE;
+
+
+ // this establishes a marker so can determine if are processing a nested exception
+ // don't want to use the current frame to limit search as it could have been unwound by
+ // the time get to nested handler (ie if find an exception, unwind to the call point and
+ // then resume in the catch and then get another exception) so make the nested handler
+ // have the same boundary as this one. If nested handler can't find a handler, we won't
+ // end up searching this frame list twice because the nested handler will set the search
+ // boundary in the thread and so if get back to this handler it will have a range that starts
+ // and ends at the same place.
+
+ NestedHandlerExRecord nestedHandlerExRecord;
+ nestedHandlerExRecord.Init((PEXCEPTION_ROUTINE)COMPlusNestedExceptionHandler, GetCurrFrame(pEstablisherFrame));
+
+ INSTALL_EXCEPTION_HANDLING_RECORD(&(nestedHandlerExRecord.m_ExReg));
+ bPopNestedHandlerExRecord = TRUE;
+
+#if defined(USE_FEF)
+ // Note: don't attempt to push a FEF for an exception in managed code if we weren't in cooperative mode when
+ // the exception was received. If preemptive GC was enabled when we received the exception, then it means the
+ // exception was rethrown from unmangaed code (including EE impl), and we shouldn't push a FEF.
+ if (fIsManagedCode &&
+ fPGCDisabledOnEntry &&
+ (pThread->m_pFrame == FRAME_TOP ||
+ pThread->m_pFrame->GetVTablePtr() != FaultingExceptionFrame::GetMethodFrameVPtr() ||
+ (size_t)pThread->m_pFrame > (size_t)pEstablisherFrame))
+ {
+ // setup interrupted frame so that GC during calls to init won't collect the frames
+ // only need it for non COM+ exceptions in managed code when haven't already
+ // got one on the stack (will have one already if we have called rtlunwind because
+ // the instantiation that called unwind would have installed one)
+ faultingExceptionFrame.InitAndLink(pContext);
+ bPopFaultingExceptionFrame = TRUE;
+ }
+#endif // USE_FEF
+
+ OBJECTREF e;
+ e = pThread->LastThrownObject();
+
+ STRESS_LOG7(LF_EH, LL_INFO10, "CPFH_RealFirstPassHandler: code:%X, LastThrownObject:%p, MT:%pT"
+ ", IP:%p, SP:%p, pContext:%p, pEstablisherFrame:%p\n",
+ exceptionCode, OBJECTREFToObject(e), (e!=0)?e->GetMethodTable():0,
+ pContext ? GetIP(pContext) : 0, pContext ? GetSP(pContext) : 0,
+ pContext, pEstablisherFrame);
+
+#ifdef LOGGING
+ // If it is a complus exception, and there is a thrown object, get its name, for better logging.
+ if (IsComPlusException(pExceptionRecord))
+ {
+ const char * eClsName = "!EXCEPTION_COMPLUS";
+ if (e != 0)
+ {
+ eClsName = e->GetTrueMethodTable()->GetDebugClassName();
+ }
+ LOG((LF_EH, LL_INFO100, "CPFH_RealFirstPassHandler: exception: 0x%08X, class: '%s', IP: 0x%p\n",
+ exceptionCode, eClsName, pContext ? GetIP(pContext) : NULL));
+ }
+#endif
+
+ EXCEPTION_POINTERS exceptionPointers = {pExceptionRecord, pContext};
+
+ STRESS_LOG4(LF_EH, LL_INFO10000, "CPFH_RealFirstPassHandler: setting boundaries: Exinfo: 0x%p, BottomMostHandler:0x%p, SearchBoundary:0x%p, TopFrame:0x%p\n",
+ pExInfo, pExInfo->m_pBottomMostHandler, pExInfo->m_pSearchBoundary, tct.pTopFrame);
+
+ // Here we are trying to decide if we are coming in as:
+ // 1) first handler in a brand new exception
+ // 2) a subsequent handler in an exception
+ // 3) a nested exception
+ // m_pBottomMostHandler is the registration structure (establisher frame) for the most recent (ie lowest in
+ // memory) non-nested handler that was installed and pEstablisher frame is what the current handler
+ // was registered with.
+ // The OS calls each registered handler in the chain, passing its establisher frame to it.
+ if (pExInfo->m_pBottomMostHandler != NULL && pEstablisherFrame > pExInfo->m_pBottomMostHandler)
+ {
+ STRESS_LOG3(LF_EH, LL_INFO10000, "CPFH_RealFirstPassHandler: detected subsequent handler. ExInfo:0x%p, BottomMost:0x%p SearchBoundary:0x%p\n",
+ pExInfo, pExInfo->m_pBottomMostHandler, pExInfo->m_pSearchBoundary);
+
+ // If the establisher frame of this handler is greater than the bottommost then it must have been
+ // installed earlier and therefore we are case 2
+ if (pThread->GetThrowable() == NULL)
+ {
+ // Bottommost didn't setup a throwable, so not exception not for us
+ retval = ExceptionContinueSearch;
+ goto exit;
+ }
+
+ // setup search start point
+ tct.pBottomFrame = pExInfo->m_pSearchBoundary;
+
+ if (tct.pTopFrame == tct.pBottomFrame)
+ {
+ // this will happen if our nested handler already searched for us so we don't want
+ // to search again
+ retval = ExceptionContinueSearch;
+ goto exit;
+ }
+ }
+ else
+ { // we are either case 1 or case 3
+#if defined(_DEBUG_IMPL)
+ //@todo: merge frames, context, handlers
+ if (pThread->GetFrame() != FRAME_TOP)
+ pThread->GetFrame()->LogFrameChain(LF_EH, LL_INFO1000);
+#endif // _DEBUG_IMPL
+
+ // If the exception was rethrown, we'll create a new ExInfo, which will represent the rethrown exception.
+ // The original exception is not the rethrown one.
+ if (pExInfo->m_ExceptionFlags.IsRethrown() && pThread->LastThrownObject() != NULL)
+ {
+ pExInfo->m_ExceptionFlags.ResetIsRethrown();
+ bRethrownException = TRUE;
+
+#if defined(USE_FEF)
+ if (bPopFaultingExceptionFrame)
+ {
+ // if we added a FEF, it will refer to the frame at the point of the original exception which is
+ // already unwound so don't want it.
+ // If we rethrew the exception we have already added a helper frame for the rethrow, so don't
+ // need this one. If we didn't rethrow it, (ie rethrow from native) then there the topmost frame will
+ // be a transition to native frame in which case we don't need it either
+ faultingExceptionFrame.Pop();
+ bPopFaultingExceptionFrame = FALSE;
+ }
+#endif
+ }
+
+ // If the establisher frame is less than the bottommost handler, then this is nested because the
+ // establisher frame was installed after the bottommost.
+ if (pEstablisherFrame < pExInfo->m_pBottomMostHandler
+ /* || IsComPlusNestedExceptionRecord(pEstablisherFrame) */ )
+ {
+ bNestedException = TRUE;
+
+ // case 3: this is a nested exception. Need to save and restore the thread info
+ STRESS_LOG3(LF_EH, LL_INFO10000, "CPFH_RealFirstPassHandler: ExInfo:0x%p detected nested exception 0x%p < 0x%p\n",
+ pExInfo, pEstablisherFrame, pExInfo->m_pBottomMostHandler);
+
+ EXCEPTION_REGISTRATION_RECORD* pNestedER = TryFindNestedEstablisherFrame(pEstablisherFrame);
+ ExInfo *pNestedExInfo;
+
+ if (!pNestedER || pNestedER >= pExInfo->m_pBottomMostHandler )
+ {
+ // RARE CASE. We've re-entered the EE from an unmanaged filter.
+ //
+ // OR
+ //
+ // We can be here if we dont find a nested exception handler. This is exemplified using
+ // call chain of scenario 2 explained further below.
+ //
+ // Assuming __try of NativeB throws an exception E1 and it gets caught in ManagedA2, then
+ // bottom-most handler (BMH) is going to be CPFH_A. The catch will trigger an unwind
+ // and invoke __finally in NativeB. Let the __finally throw a new exception E2.
+ //
+ // Assuming ManagedB2 has a catch block to catch E2, when we enter CPFH_B looking for a
+ // handler for E2, our establisher frame will be that of CPFH_B, which will be lower
+ // in stack than current BMH (which is CPFH_A). Thus, we will come here, determining
+ // E2 to be nested exception correctly but not find a nested exception handler.
+ void *limit = (void *) GetPrevSEHRecord(pExInfo->m_pBottomMostHandler);
+
+ pNestedExInfo = new (nothrow) ExInfo(); // Very rare failure here; need robust allocator.
+ if (pNestedExInfo == NULL)
+ { // if we can't allocate memory, we can't correctly continue.
+ #if defined(_DEBUG)
+ if (CLRConfig::GetConfigValue(CLRConfig::INTERNAL_NestedEhOom))
+ _ASSERTE(!"OOM in callback from unmanaged filter.");
+ #endif // _DEBUG
+
+ EEPOLICY_HANDLE_FATAL_ERROR(COR_E_OUTOFMEMORY);
+ }
+
+
+ pNestedExInfo->m_StackAddress = limit; // Note: this is also the flag that tells us this
+ // ExInfo was stack allocated.
+ }
+ else
+ {
+ pNestedExInfo = &((NestedHandlerExRecord*)pNestedER)->m_handlerInfo;
+ }
+
+ LOG((LF_EH, LL_INFO100, "CPFH_RealFirstPassHandler: PushExInfo() current: 0x%p previous: 0x%p\n",
+ pExInfo->m_StackAddress, pNestedExInfo->m_StackAddress));
+
+ _ASSERTE(pNestedExInfo);
+ pNestedExInfo->m_hThrowable = NULL; // pNestedExInfo may be stack allocated, and as such full of
+ // garbage. m_hThrowable must be sane, so set it to NULL. (We could
+ // zero the entire record, but this is cheaper.)
+
+ pNestedExInfo->CopyAndClearSource(pExInfo);
+
+ pExInfo->m_pPrevNestedInfo = pNestedExInfo; // Save at head of nested info chain
+
+#if 0
+/* the following code was introduced in Whidbey as part of the Faulting Exception Frame removal (12/03).
+ However it isn't correct. If any nested exceptions occur while processing a rethrow, we would
+ incorrectly consider the nested exception to be a rethrow. See VSWhidbey 349379 for an example.
+
+ Therefore I am disabling this code until we see a failure that explains why it was added in the first
+ place. cwb 9/04.
+*/
+ // If we're here as a result of a rethrown exception, set the rethrown flag on the new ExInfo.
+ if (bRethrownException)
+ {
+ pExInfo->m_ExceptionFlags.SetIsRethrown();
+ }
+#endif
+ }
+ else
+ {
+ // At this point, either:
+ //
+ // 1) the bottom-most handler is NULL, implying this is a new exception for which we are getting ready, OR
+ // 2) the bottom-most handler is not-NULL, implying that a there is already an existing exception in progress.
+ //
+ // Scenario 1 is that of a new throw and is easy to understand. Scenario 2 is the interesting one.
+ //
+ // ManagedA1 -> ManagedA2 -> ManagedA3 -> NativeCodeA -> ManagedB1 -> ManagedB2 -> ManagedB3 -> NativeCodeB
+ //
+ // On x86, each block of managed code is protected by one COMPlusFrameHandler [CPFH] (CLR's exception handler
+ // for managed code), unlike 64bit where each frame has a personality routine attached to it. Thus,
+ // for the example above, assume CPFH_A protects ManagedA* blocks and is setup just before the call to
+ // ManagedA1. Likewise, CPFH_B protects ManagedB* blocks and is setup just before the call to ManagedB1.
+ //
+ // When ManagedB3 throws an exception, CPFH_B is invoked to look for a handler in all of the ManagedB* blocks.
+ // At this point, it is setup as the "bottom-most-handler" (BMH). If no handler is found and exception reaches
+ // ManagedA* blocks, CPFH_A is invoked to look for a handler and thus, becomes BMH.
+ //
+ // Thus, in the first pass on x86 for a given exception, a particular CPFH will be invoked only once when looking
+ // for a handler and thus, registered as BMH only once. Either the exception goes unhandled and the process will
+ // terminate or a handler will be found and second pass will commence.
+ //
+ // However, assume NativeCodeB had a __try/__finally and raised an exception [E1] within the __try. Let's assume
+ // it gets caught in ManagedB1 and thus, unwind is triggered. At this point, the active exception tracker
+ // has context about the exception thrown out of __try and CPFH_B is registered as BMH.
+ //
+ // If the __finally throws a new exception [E2], CPFH_B will be invoked again for first pass while looking for
+ // a handler for the thrown exception. Since BMH is already non-NULL, we will come here since EstablisherFrame will be
+ // the same as BMH (because EstablisherFrame will be that of CPFH_B). We will proceed to overwrite the "required" parts
+ // of the existing exception tracker with the details of E2 (see setting of exception record and context below), erasing
+ // any artifact of E1.
+ //
+ // This is unlike Scenario 1 when exception tracker is completely initialized to default values. This is also
+ // unlike 64bit which will detect that E1 and E2 are different exceptions and hence, will setup a new tracker
+ // to track E2, effectively behaving like Scenario 1 above. X86 cannot do this since there is no nested exception
+ // tracker setup that gets to see the new exception.
+ //
+ // Thus, if E1 was a CSE and E2 isn't, we will come here and treat E2 as a CSE as well since corruption severity
+ // is initialized as part of exception tracker initialization. Thus, E2 will start to be treated as CSE, which is
+ // incorrect. Similar argument applies to delivery of First chance exception notification delivery.
+ //
+ // <QUIP> Another example why we should unify EH systems :) </QUIP>
+ //
+ // To address this issue, we will need to reset exception tracker here, just like the overwriting of "required"
+ // parts of exception tracker.
+
+ // If the current establisher frame is the same as the bottom-most-handler and we are here
+ // in the first pass, assert that current exception and the one tracked by active exception tracker
+ // are indeed different exceptions. In such a case, we must reset the exception tracker so that it can be
+ // setup correctly further down when CEHelper::SetupCorruptionSeverityForActiveException is invoked.
+
+ if ((pExInfo->m_pBottomMostHandler != NULL) &&
+ (pEstablisherFrame == pExInfo->m_pBottomMostHandler))
+ {
+ // Current exception should be different from the one exception tracker is already tracking.
+ _ASSERTE(pExceptionRecord != pExInfo->m_pExceptionRecord);
+
+ // This cannot be nested exceptions - they are handled earlier (see above).
+ _ASSERTE(!bNestedException);
+
+ LOG((LF_EH, LL_INFO100, "CPFH_RealFirstPassHandler: Bottom-most handler (0x%p) is the same as EstablisherFrame.\n",
+ pExInfo->m_pBottomMostHandler));
+ LOG((LF_EH, LL_INFO100, "CPFH_RealFirstPassHandler: Exception record in exception tracker is 0x%p, while that of new exception is 0x%p.\n",
+ pExInfo->m_pExceptionRecord, pExceptionRecord));
+ LOG((LF_EH, LL_INFO100, "CPFH_RealFirstPassHandler: Resetting exception tracker (0x%p).\n", pExInfo));
+
+ // This will reset the exception tracker state, including the corruption severity.
+ pExInfo->Init();
+ }
+ }
+
+ // If we are handling a fault from managed code, we need to set the Thread->ExInfo->pContext to
+ // the current fault context, which is used in the stack walk to get back into the managed
+ // stack with the correct registers. (Previously, this was done by linking in a FaultingExceptionFrame
+ // record.)
+ // We are about to create the managed exception object, which may trigger a GC, so set this up now.
+
+ pExInfo->m_pExceptionRecord = pExceptionRecord;
+ pExInfo->m_pContext = pContext;
+ if (pContext && ShouldHandleManagedFault(pExceptionRecord, pContext, pEstablisherFrame, pThread))
+ { // If this was a fault in managed code, rather than create a Frame for stackwalking,
+ // we can use this exinfo (after all, it has all the register info.)
+ pExInfo->m_ExceptionFlags.SetUseExInfoForStackwalk();
+ }
+
+ // It should now be safe for a GC to happen.
+
+ // case 1 & 3: this is the first time through of a new, nested, or rethrown exception, so see if we can
+ // find a handler. Only setup throwable if are bottommost handler
+ if (IsComPlusException(pExceptionRecord) && (!bAsynchronousThreadStop))
+ {
+
+ // Update the throwable from the last thrown object. Note: this may cause OOM, in which case we replace
+ // both throwables with the preallocated OOM exception.
+ pThread->SafeSetThrowables(pThread->LastThrownObject());
+
+ // now we've got a COM+ exception, fall through to so see if we handle it
+
+ STRESS_LOG3(LF_EH, LL_INFO10000, "CPFH_RealFirstPassHandler: fall through ExInfo:0x%p setting m_pBottomMostHandler to 0x%p from 0x%p\n",
+ pExInfo, pEstablisherFrame, pExInfo->m_pBottomMostHandler);
+ pExInfo->m_pBottomMostHandler = pEstablisherFrame;
+ }
+ else if (bRethrownException)
+ {
+ // If it was rethrown and not COM+, will still be the last one thrown. Either we threw it last and
+ // stashed it here or someone else caught it and rethrew it, in which case it will still have been
+ // originally stashed here.
+
+ // Update the throwable from the last thrown object. Note: this may cause OOM, in which case we replace
+ // both throwables with the preallocated OOM exception.
+ pThread->SafeSetThrowables(pThread->LastThrownObject());
+ STRESS_LOG3(LF_EH, LL_INFO10000, "CPFH_RealFirstPassHandler: rethrow non-COM+ ExInfo:0x%p setting m_pBottomMostHandler to 0x%p from 0x%p\n",
+ pExInfo, pEstablisherFrame, pExInfo->m_pBottomMostHandler);
+ pExInfo->m_pBottomMostHandler = pEstablisherFrame;
+ }
+ else
+ {
+ if (!fIsManagedCode)
+ {
+ tct.bDontCatch = false;
+ }
+
+ if (exceptionCode == STATUS_BREAKPOINT)
+ {
+ // don't catch int 3
+ retval = ExceptionContinueSearch;
+ goto exit;
+ }
+
+ // We need to set m_pBottomMostHandler here, Thread::IsExceptionInProgress returns 1.
+ // This is a necessary part of suppressing thread abort exceptions in the constructor
+ // of any exception object we might create.
+ STRESS_LOG3(LF_EH, LL_INFO10000, "CPFH_RealFirstPassHandler: setting ExInfo:0x%p m_pBottomMostHandler for IsExceptionInProgress to 0x%p from 0x%p\n",
+ pExInfo, pEstablisherFrame, pExInfo->m_pBottomMostHandler);
+ pExInfo->m_pBottomMostHandler = pEstablisherFrame;
+
+ // Create the managed exception object.
+ OBJECTREF throwable = CreateCOMPlusExceptionObject(pThread, pExceptionRecord, bAsynchronousThreadStop);
+
+ // Set the throwables on the thread to the newly created object. If this fails, it will return a
+ // preallocated exception object instead. This also updates the last thrown exception, for rethrows.
+ throwable = pThread->SafeSetThrowables(throwable);
+
+ // Set the exception code and pointers. We set these after setting the throwables on the thread,
+ // because if the proper exception is replaced by an OOM exception, we still want the exception code
+ // and pointers set in the OOM exception.
+ EXCEPTIONREF exceptionRef = (EXCEPTIONREF)throwable;
+ exceptionRef->SetXCode(pExceptionRecord->ExceptionCode);
+ exceptionRef->SetXPtrs(&exceptionPointers);
+ }
+
+ tct.pBottomFrame = NULL;
+
+ EEToProfilerExceptionInterfaceWrapper::ExceptionThrown(pThread);
+
+ CPFH_UpdatePerformanceCounters();
+ } // End of case-1-or-3
+
+ {
+ // Allocate storage for the stack trace.
+ OBJECTREF throwable = NULL;
+ GCPROTECT_BEGIN(throwable);
+ throwable = pThread->GetThrowable();
+
+#ifdef FEATURE_CORRUPTING_EXCEPTIONS
+ {
+ BEGIN_SO_INTOLERANT_CODE(GetThread());
+ // Setup the state in current exception tracker indicating the corruption severity
+ // of the active exception.
+ CEHelper::SetupCorruptionSeverityForActiveException(bRethrownException, bNestedException,
+ CEHelper::ShouldTreatActiveExceptionAsNonCorrupting());
+ END_SO_INTOLERANT_CODE;
+ }
+#endif // FEATURE_CORRUPTING_EXCEPTIONS
+
+#ifdef FEATURE_CORECLR
+ // Check if we are dealing with AV or not and if we are,
+ // ensure that this is a real AV and not managed AV exception
+ BOOL fIsThrownExceptionAV = FALSE;
+ if ((pExceptionRecord->ExceptionCode == STATUS_ACCESS_VIOLATION) &&
+ (MscorlibBinder::GetException(kAccessViolationException) == throwable->GetMethodTable()))
+ {
+ // Its an AV - set the flag
+ fIsThrownExceptionAV = TRUE;
+ }
+
+ // Did we get an AV?
+ if (fIsThrownExceptionAV == TRUE)
+ {
+ // Get the escalation policy action for handling AV
+ EPolicyAction actionAV = GetEEPolicy()->GetActionOnFailure(FAIL_AccessViolation);
+
+ // Valid actions are: eNoAction (default behviour) or eRudeExitProcess
+ _ASSERTE(((actionAV == eNoAction) || (actionAV == eRudeExitProcess)));
+ if (actionAV == eRudeExitProcess)
+ {
+ LOG((LF_EH, LL_INFO100, "CPFH_RealFirstPassHandler: AccessViolation handler found and doing RudeExitProcess due to escalation policy (eRudeExitProcess)\n"));
+
+ // EEPolicy::HandleFatalError will help us RudeExit the process.
+ // RudeExitProcess due to AV is to prevent a security risk - we are ripping
+ // at the boundary, without looking for the handlers.
+ EEPOLICY_HANDLE_FATAL_ERROR(COR_E_SECURITY);
+ }
+ }
+#endif // FEATURE_CORECLR
+
+ // If we're out of memory, then we figure there's probably not memory to maintain a stack trace, so we skip it.
+ // If we've got a stack overflow, then we figure the stack will be so huge as to make tracking the stack trace
+ // impracticle, so we skip it.
+ if ((throwable == CLRException::GetPreallocatedOutOfMemoryException()) ||
+ (throwable == CLRException::GetPreallocatedStackOverflowException()))
+ {
+ tct.bAllowAllocMem = FALSE;
+ }
+ else
+ {
+ pExInfo->m_StackTraceInfo.AllocateStackTrace();
+ }
+
+ GCPROTECT_END();
+ }
+
+ // Set up information for GetExceptionPointers()/GetExceptionCode() callback.
+ pExInfo->SetExceptionCode(pExceptionRecord);
+
+ pExInfo->m_pExceptionPointers = &exceptionPointers;
+
+ if (bRethrownException || bNestedException)
+ {
+ _ASSERTE(pExInfo->m_pPrevNestedInfo != NULL);
+
+ BEGIN_SO_INTOLERANT_CODE(GetThread());
+ SetStateForWatsonBucketing(bRethrownException, pExInfo->GetPreviousExceptionTracker()->GetThrowableAsHandle());
+ END_SO_INTOLERANT_CODE;
+ }
+
+#ifdef DEBUGGING_SUPPORTED
+ //
+ // At this point the exception is still fresh to us, so assert that
+ // there should be nothing from the debugger on it.
+ //
+ _ASSERTE(!pExInfo->m_ExceptionFlags.DebuggerInterceptInfo());
+#endif
+
+ if (pThread->IsRudeAbort())
+ {
+ OBJECTREF rudeAbortThrowable = CLRException::GetPreallocatedRudeThreadAbortException();
+
+ if (pThread->GetThrowable() != rudeAbortThrowable)
+ {
+ // Neither of these sets will throw because the throwable that we're setting is a preallocated
+ // exception. This also updates the last thrown exception, for rethrows.
+ pThread->SafeSetThrowables(rudeAbortThrowable);
+ }
+
+ if (!pThread->IsRudeAbortInitiated())
+ {
+ pThread->PreWorkForThreadAbort();
+ }
+ }
+
+ LOG((LF_EH, LL_INFO100, "CPFH_RealFirstPassHandler: looking for handler bottom %x, top %x\n",
+ tct.pBottomFrame, tct.pTopFrame));
+ tct.bReplaceStack = pExInfo->m_pBottomMostHandler == pEstablisherFrame && !bRethrownException;
+ tct.bSkipLastElement = bRethrownException && bNestedException;
+ found = LookForHandler(&exceptionPointers,
+ pThread,
+ &tct);
+
+ // We have searched this far.
+ pExInfo->m_pSearchBoundary = tct.pTopFrame;
+ LOG((LF_EH, LL_INFO1000, "CPFH_RealFirstPassHandler: set pSearchBoundary to 0x%p\n", pExInfo->m_pSearchBoundary));
+
+ if ((found == LFH_NOT_FOUND)
+#ifdef DEBUGGING_SUPPORTED
+ && !pExInfo->m_ExceptionFlags.DebuggerInterceptInfo()
+#endif
+ )
+ {
+ LOG((LF_EH, LL_INFO100, "CPFH_RealFirstPassHandler: NOT_FOUND\n"));
+
+ if (tct.pTopFrame == FRAME_TOP)
+ {
+ LOG((LF_EH, LL_INFO100, "CPFH_RealFirstPassHandler: NOT_FOUND at FRAME_TOP\n"));
+ }
+
+ retval = ExceptionContinueSearch;
+ goto exit;
+ }
+ else
+ {
+ // so we are going to handle the exception
+
+ // Remove the nested exception record -- before calling RtlUnwind.
+ // The second-pass callback for a NestedExceptionRecord assumes that if it's
+ // being unwound, it should pop one exception from the pExInfo chain. This is
+ // true for any older NestedRecords that might be unwound -- but not for the
+ // new one we're about to add. To avoid this, we remove the new record
+ // before calling Unwind.
+ //
+ // <TODO>@NICE: This can probably be a little cleaner -- the nested record currently
+ // is also used to guard the running of the filter code. When we clean up the
+ // behaviour of exceptions within filters, we should be able to get rid of this
+ // PUSH/POP/PUSH behaviour.</TODO>
+ _ASSERTE(bPopNestedHandlerExRecord);
+
+ UNINSTALL_EXCEPTION_HANDLING_RECORD(&(nestedHandlerExRecord.m_ExReg));
+
+ // Since we are going to handle the exception we switch into preemptive mode
+ GCX_PREEMP_NO_DTOR();
+
+#ifdef DEBUGGING_SUPPORTED
+ //
+ // Check if the debugger wants to intercept this frame at a different point than where we are.
+ //
+ if (pExInfo->m_ExceptionFlags.DebuggerInterceptInfo())
+ {
+ ClrDebuggerDoUnwindAndIntercept(pEstablisherFrame, pExceptionRecord);
+
+ //
+ // If this returns, then the debugger couldn't do it's stuff and we default to the found handler.
+ //
+ if (found == LFH_NOT_FOUND)
+ {
+ retval = ExceptionContinueSearch;
+ // we need to be sure to switch back into Cooperative mode since we are going to
+ // jump to the exit: label and follow the normal return path (it is expected that
+ // CPFH_RealFirstPassHandler returns in COOP.
+ GCX_PREEMP_NO_DTOR_END();
+ goto exit;
+ }
+ }
+#endif
+
+ LOG((LF_EH, LL_INFO100, "CPFH_RealFirstPassHandler: handler found: %s\n", tct.pFunc->m_pszDebugMethodName));
+
+ CallRtlUnwindSafe(pEstablisherFrame, RtlUnwindCallback, pExceptionRecord, 0);
+ // on x86 at least, RtlUnwind always returns
+
+ // Note: we've completed the unwind pass up to the establisher frame, and we're headed off to finish our
+ // cleanup and end up back in jitted code. Any more FS0 handlers pushed from this point on out will _not_ be
+ // unwound.
+ // Note: we are still in Preemptive mode here and that is correct, COMPlusAfterUnwind will switch us back
+ // into Cooperative mode.
+ return COMPlusAfterUnwind(pExceptionRecord, pEstablisherFrame, tct);
+ }
+
+exit:
+ {
+ // We need to be in COOP if we get here
+ GCX_ASSERT_COOP();
+ }
+
+ // If we got as far as saving pExInfo, save the context pointer so it's available for the unwind.
+ if (pExInfo)
+ {
+ pExInfo->m_pContext = pContext;
+ // pExInfo->m_pExceptionPointers points to a local structure, which is now going out of scope.
+ pExInfo->m_pExceptionPointers = NULL;
+ }
+
+#if defined(USE_FEF)
+ if (bPopFaultingExceptionFrame)
+ {
+ faultingExceptionFrame.Pop();
+ }
+#endif // USE_FEF
+
+ if (bPopNestedHandlerExRecord)
+ {
+ UNINSTALL_EXCEPTION_HANDLING_RECORD(&(nestedHandlerExRecord.m_ExReg));
+ }
+ return retval;
+} // CPFH_RealFirstPassHandler()
+
+
+//******************************************************************************
+//
+void InitializeExceptionHandling()
+{
+ WRAPPER_NO_CONTRACT;
+
+ InitSavedExceptionInfo();
+
+ CLRAddVectoredHandlers();
+
+ // Initialize the lock used for synchronizing access to the stacktrace in the exception object
+ g_StackTraceArrayLock.Init(LOCK_TYPE_DEFAULT, TRUE);
+}
+
+//******************************************************************************
+static inline EXCEPTION_DISPOSITION __cdecl
+CPFH_FirstPassHandler(EXCEPTION_RECORD *pExceptionRecord,
+ EXCEPTION_REGISTRATION_RECORD *pEstablisherFrame,
+ CONTEXT *pContext,
+ DISPATCHER_CONTEXT *pDispatcherContext)
+{
+ WRAPPER_NO_CONTRACT;
+ EXCEPTION_DISPOSITION retval;
+
+ _ASSERTE (!(pExceptionRecord->ExceptionFlags & (EXCEPTION_UNWINDING | EXCEPTION_EXIT_UNWIND)));
+
+ DWORD exceptionCode = pExceptionRecord->ExceptionCode;
+
+ Thread *pThread = GetThread();
+
+ STRESS_LOG4(LF_EH, LL_INFO100,
+ "CPFH_FirstPassHandler: pEstablisherFrame = %x EH code = %x EIP = %x with ESP = %x\n",
+ pEstablisherFrame, exceptionCode, pContext ? GetIP(pContext) : 0, pContext ? GetSP(pContext) : 0);
+
+ EXCEPTION_POINTERS ptrs = { pExceptionRecord, pContext };
+
+ // Call to the vectored handler to give other parts of the Runtime a chance to jump in and take over an
+ // exception before we do too much with it. The most important point in the vectored handler is not to toggle
+ // the GC mode.
+ DWORD filter = CLRVectoredExceptionHandler(&ptrs);
+
+ if (filter == (DWORD) EXCEPTION_CONTINUE_EXECUTION)
+ {
+ return ExceptionContinueExecution;
+ }
+ else if (filter == EXCEPTION_CONTINUE_SEARCH)
+ {
+ return ExceptionContinueSearch;
+ }
+
+#if defined(STRESS_HEAP)
+ //
+ // Check to see if this exception is due to GCStress. Since the GCStress mechanism only injects these faults
+ // into managed code, we only need to check for them in CPFH_FirstPassHandler.
+ //
+ if (IsGcMarker(exceptionCode, pContext))
+ {
+ return ExceptionContinueExecution;
+ }
+#endif // STRESS_HEAP
+
+ // We always want to be in co-operative mode when we run this function and whenever we return
+ // from it, want to go to pre-emptive mode because are returning to OS.
+ BOOL disabled = pThread->PreemptiveGCDisabled();
+ GCX_COOP_NO_DTOR();
+
+ BOOL bAsynchronousThreadStop = IsThreadHijackedForThreadStop(pThread, pExceptionRecord);
+
+ if (bAsynchronousThreadStop)
+ {
+ // If we ever get here in preemptive mode, we're in trouble. We've
+ // changed the thread's IP to point at a little function that throws ... if
+ // the thread were to be in preemptive mode and a GC occurred, the stack
+ // crawl would have been all messed up (becuase we have no frame that points
+ // us back to the right place in managed code).
+ _ASSERTE(disabled);
+
+ AdjustContextForThreadStop(pThread, pContext);
+ LOG((LF_EH, LL_INFO100, "CPFH_FirstPassHandler is Asynchronous Thread Stop or Abort\n"));
+ }
+
+ pThread->ResetThrowControlForThread();
+
+ CPFH_VerifyThreadIsInValidState(pThread, exceptionCode, pEstablisherFrame);
+
+ // If we were in cooperative mode when we came in here, then its okay to see if we should do HandleManagedFault
+ // and push a FaultingExceptionFrame. If we weren't in coop mode coming in here, then it means that there's no
+ // way the exception could really be from managed code. I might look like it was from managed code, but in
+ // reality its a rethrow from unmanaged code, either unmanaged user code, or unmanaged EE implementation.
+ if (disabled && ShouldHandleManagedFault(pExceptionRecord, pContext, pEstablisherFrame, pThread))
+ {
+#if defined(USE_FEF)
+ HandleManagedFault(pExceptionRecord, pContext, pEstablisherFrame, pThread);
+ retval = ExceptionContinueExecution;
+ goto exit;
+#else // USE_FEF
+ // Save the context pointer in the Thread's EXInfo, so that a stack crawl can recover the
+ // register values from the fault.
+
+ //@todo: I haven't yet found any case where we need to do anything here. If there are none, eliminate
+ // this entire if () {} block.
+#endif // USE_FEF
+ }
+
+ // OK. We're finally ready to start the real work. Nobody else grabbed the exception in front of us. Now we can
+ // get started.
+ retval = CPFH_RealFirstPassHandler(pExceptionRecord,
+ pEstablisherFrame,
+ pContext,
+ pDispatcherContext,
+ bAsynchronousThreadStop,
+ disabled);
+
+#if defined(USE_FEF) // This label is only used in the HandleManagedFault() case above.
+exit:
+#endif
+ if (retval != ExceptionContinueExecution || !disabled)
+ {
+ GCX_PREEMP_NO_DTOR();
+ }
+
+ STRESS_LOG1(LF_EH, LL_INFO100, "CPFH_FirstPassHandler: exiting with retval %d\n", retval);
+ return retval;
+} // CPFH_FirstPassHandler()
+
+//******************************************************************************
+inline void
+CPFH_UnwindFrames1(Thread* pThread, EXCEPTION_REGISTRATION_RECORD* pEstablisherFrame, DWORD exceptionCode)
+{
+ WRAPPER_NO_CONTRACT;
+
+ ExInfo* pExInfo = &(pThread->GetExceptionState()->m_currentExInfo);
+
+ // Ready to unwind the stack...
+ ThrowCallbackType tct;
+ tct.Init();
+ tct.bIsUnwind = TRUE;
+ tct.pTopFrame = GetCurrFrame(pEstablisherFrame); // highest frame to search to
+ tct.pBottomFrame = NULL;
+
+ // Set the flag indicating if the current exception represents a longjmp.
+ // See comment in COMPlusUnwindCallback for details.
+ CORRUPTING_EXCEPTIONS_ONLY(tct.m_fIsLongJump = (exceptionCode == STATUS_LONGJUMP);)
+
+ #ifdef _DEBUG
+ tct.pCurrentExceptionRecord = pEstablisherFrame;
+ tct.pPrevExceptionRecord = GetPrevSEHRecord(pEstablisherFrame);
+ #endif
+
+ #ifdef DEBUGGING_SUPPORTED
+ EXCEPTION_REGISTRATION_RECORD *pInterceptEstablisherFrame = NULL;
+
+ // If the exception is intercepted, use information stored in the DebuggerExState to unwind the stack.
+ if (pExInfo->m_ExceptionFlags.DebuggerInterceptInfo())
+ {
+ pExInfo->m_DebuggerExState.GetDebuggerInterceptInfo(&pInterceptEstablisherFrame,
+ NULL, // MethodDesc **ppFunc,
+ NULL, // int *pdHandler,
+ NULL, // BYTE **ppStack
+ NULL, // ULONG_PTR *pNativeOffset,
+ NULL // Frame **ppFrame)
+ );
+ LOG((LF_EH, LL_INFO1000, "CPFH_UnwindFrames1: frames are Est 0x%X, Intercept 0x%X\n",
+ pEstablisherFrame, pInterceptEstablisherFrame));
+
+ //
+ // When we set up for the interception we store off the CPFH or CPNEH that we
+ // *know* will handle unwinding the destination of the intercept.
+ //
+ // However, a CPNEH with the same limiting Capital-F-rame could do the work
+ // and unwind us, so...
+ //
+ // If this is the exact frame handler we are supposed to search for, or
+ // if this frame handler services the same Capital-F-rame as the frame handler
+ // we are looking for (i.e. this frame handler may do the work that we would
+ // expect our frame handler to do),
+ // then
+ // we need to pass the interception destination during this unwind.
+ //
+ _ASSERTE(IsUnmanagedToManagedSEHHandler(pEstablisherFrame));
+
+ if ((pEstablisherFrame == pInterceptEstablisherFrame) ||
+ (GetCurrFrame(pEstablisherFrame) == GetCurrFrame(pInterceptEstablisherFrame)))
+ {
+ pExInfo->m_DebuggerExState.GetDebuggerInterceptInfo(NULL,
+ &(tct.pFunc),
+ &(tct.dHandler),
+ &(tct.pStack),
+ NULL,
+ &(tct.pBottomFrame)
+ );
+
+ LOG((LF_EH, LL_INFO1000, "CPFH_UnwindFrames1: going to: pFunc:%#X, pStack:%#X\n",
+ tct.pFunc, tct.pStack));
+
+ }
+
+ }
+ #endif
+
+ UnwindFrames(pThread, &tct);
+
+ LOG((LF_EH, LL_INFO1000, "CPFH_UnwindFrames1: after unwind ec:%#x, tct.pTopFrame:0x%p, pSearchBndry:0x%p\n"
+ " pEstFrame:0x%p, IsC+NestExRec:%d, !Nest||Active:%d\n",
+ exceptionCode, tct.pTopFrame, pExInfo->m_pSearchBoundary, pEstablisherFrame,
+ IsComPlusNestedExceptionRecord(pEstablisherFrame),
+ (!IsComPlusNestedExceptionRecord(pEstablisherFrame) || reinterpret_cast<NestedHandlerExRecord*>(pEstablisherFrame)->m_ActiveForUnwind)));
+
+ if (tct.pTopFrame >= pExInfo->m_pSearchBoundary &&
+ (!IsComPlusNestedExceptionRecord(pEstablisherFrame) ||
+ reinterpret_cast<NestedHandlerExRecord*>(pEstablisherFrame)->m_ActiveForUnwind) )
+ {
+ // If this is the search boundary, and we're not a nested handler, then
+ // this is the last time we'll see this exception. Time to unwind our
+ // exinfo.
+ STRESS_LOG0(LF_EH, LL_INFO100, "CPFH_UnwindFrames1: Exception unwind -- unmanaged catcher detected\n");
+ pExInfo->UnwindExInfo((VOID*)pEstablisherFrame);
+ }
+} // CPFH_UnwindFrames1()
+
+//******************************************************************************
+inline EXCEPTION_DISPOSITION __cdecl
+CPFH_UnwindHandler(EXCEPTION_RECORD *pExceptionRecord,
+ EXCEPTION_REGISTRATION_RECORD *pEstablisherFrame,
+ CONTEXT *pContext,
+ void *pDispatcherContext)
+{
+ WRAPPER_NO_CONTRACT;
+ _ASSERTE (pExceptionRecord->ExceptionFlags & (EXCEPTION_UNWINDING | EXCEPTION_EXIT_UNWIND));
+
+ #ifdef _DEBUG
+ // Note: you might be inclined to write "static int breakOnSecondPass = CLRConfig::GetConfigValue(...);", but
+ // you can't do that here. That causes C++ EH to be generated under the covers for this function, and this
+ // function isn't allowed to have any C++ EH in it because its never going to return.
+ static int breakOnSecondPass; // = 0
+ static BOOL breakOnSecondPassSetup; // = FALSE
+ if (!breakOnSecondPassSetup)
+ {
+ breakOnSecondPass = CLRConfig::GetConfigValue(CLRConfig::INTERNAL_BreakOnSecondPass);
+ breakOnSecondPassSetup = TRUE;
+ }
+ if (breakOnSecondPass != 0)
+ {
+ _ASSERTE(!"Unwind handler");
+ }
+ #endif
+
+ DWORD exceptionCode = pExceptionRecord->ExceptionCode;
+ Thread *pThread = GetThread();
+
+ ExInfo* pExInfo = &(pThread->GetExceptionState()->m_currentExInfo);
+
+ STRESS_LOG4(LF_EH, LL_INFO100, "In CPFH_UnwindHandler EHCode = %x EIP = %x with ESP = %x, pEstablisherFrame = 0x%p\n", exceptionCode,
+ pContext ? GetIP(pContext) : 0, pContext ? GetSP(pContext) : 0, pEstablisherFrame);
+
+ // We always want to be in co-operative mode when we run this function. Whenever we return
+ // from it, want to go to pre-emptive mode because are returning to OS.
+
+ {
+ // needs to be in its own scope to avoid polluting the namespace, since
+ // we don't do a _END then we don't revert the state
+ GCX_COOP_NO_DTOR();
+ }
+
+ CPFH_VerifyThreadIsInValidState(pThread, exceptionCode, pEstablisherFrame);
+
+ if (IsComPlusNestedExceptionRecord(pEstablisherFrame))
+ {
+ NestedHandlerExRecord *pHandler = reinterpret_cast<NestedHandlerExRecord*>(pEstablisherFrame);
+ if (pHandler->m_pCurrentExInfo != NULL)
+ {
+ // See the comment at the end of COMPlusNestedExceptionHandler about nested exception.
+ // OS is going to skip the EstablisherFrame before our NestedHandler.
+ if (pHandler->m_pCurrentExInfo->m_pBottomMostHandler <= pHandler->m_pCurrentHandler)
+ {
+ // We're unwinding -- the bottom most handler is potentially off top-of-stack now. If
+ // it is, change it to the next COM+ frame. (This one is not good, as it's about to
+ // disappear.)
+ EXCEPTION_REGISTRATION_RECORD *pNextBottomMost = GetNextCOMPlusSEHRecord(pHandler->m_pCurrentHandler);
+
+ STRESS_LOG3(LF_EH, LL_INFO10000, "COMPlusNestedExceptionHandler: setting ExInfo:0x%p m_pBottomMostHandler from 0x%p to 0x%p\n",
+ pHandler->m_pCurrentExInfo, pHandler->m_pCurrentExInfo->m_pBottomMostHandler, pNextBottomMost);
+
+ pHandler->m_pCurrentExInfo->m_pBottomMostHandler = pNextBottomMost;
+ }
+ }
+ }
+
+ // this establishes a marker so can determine if are processing a nested exception
+ // don't want to use the current frame to limit search as it could have been unwound by
+ // the time get to nested handler (ie if find an exception, unwind to the call point and
+ // then resume in the catch and then get another exception) so make the nested handler
+ // have the same boundary as this one. If nested handler can't find a handler, we won't
+ // end up searching this frame list twice because the nested handler will set the search
+ // boundary in the thread and so if get back to this handler it will have a range that starts
+ // and ends at the same place.
+ NestedHandlerExRecord nestedHandlerExRecord;
+ nestedHandlerExRecord.Init((PEXCEPTION_ROUTINE)COMPlusNestedExceptionHandler, GetCurrFrame(pEstablisherFrame));
+
+ nestedHandlerExRecord.m_ActiveForUnwind = TRUE;
+ nestedHandlerExRecord.m_pCurrentExInfo = pExInfo;
+ nestedHandlerExRecord.m_pCurrentHandler = pEstablisherFrame;
+
+ INSTALL_EXCEPTION_HANDLING_RECORD(&(nestedHandlerExRecord.m_ExReg));
+
+ // Unwind the stack. The establisher frame sets the boundary.
+ CPFH_UnwindFrames1(pThread, pEstablisherFrame, exceptionCode);
+
+ // We're unwinding -- the bottom most handler is potentially off top-of-stack now. If
+ // it is, change it to the next COM+ frame. (This one is not good, as it's about to
+ // disappear.)
+ if (pExInfo->m_pBottomMostHandler &&
+ pExInfo->m_pBottomMostHandler <= pEstablisherFrame)
+ {
+ EXCEPTION_REGISTRATION_RECORD *pNextBottomMost = GetNextCOMPlusSEHRecord(pEstablisherFrame);
+
+ // If there is no previous COM+ SEH handler, GetNextCOMPlusSEHRecord() will return -1. Much later, we will dereference that and AV.
+ _ASSERTE (pNextBottomMost != EXCEPTION_CHAIN_END);
+
+ STRESS_LOG3(LF_EH, LL_INFO10000, "CPFH_UnwindHandler: setting ExInfo:0x%p m_pBottomMostHandler from 0x%p to 0x%p\n",
+ pExInfo, pExInfo->m_pBottomMostHandler, pNextBottomMost);
+
+ pExInfo->m_pBottomMostHandler = pNextBottomMost;
+ }
+
+ {
+ // needs to be in its own scope to avoid polluting the namespace, since
+ // we don't do a _END then we don't revert the state
+ GCX_PREEMP_NO_DTOR();
+ }
+ UNINSTALL_EXCEPTION_HANDLING_RECORD(&(nestedHandlerExRecord.m_ExReg));
+
+ // If we are here, then exception was not caught in managed code protected by this
+ // ComplusFrameHandler. Hence, reset thread abort state if this is the last personality routine,
+ // for managed code, on the stack.
+ ResetThreadAbortState(pThread, pEstablisherFrame);
+
+ STRESS_LOG0(LF_EH, LL_INFO100, "CPFH_UnwindHandler: Leaving with ExceptionContinueSearch\n");
+ return ExceptionContinueSearch;
+} // CPFH_UnwindHandler()
+
+//******************************************************************************
+// This is the first handler that is called in the context of managed code
+// It is the first level of defense and tries to find a handler in the user
+// code to handle the exception
+//-------------------------------------------------------------------------
+// EXCEPTION_DISPOSITION __cdecl COMPlusFrameHandler(
+// EXCEPTION_RECORD *pExceptionRecord,
+// _EXCEPTION_REGISTRATION_RECORD *pEstablisherFrame,
+// CONTEXT *pContext,
+// DISPATCHER_CONTEXT *pDispatcherContext)
+//
+// See http://www.microsoft.com/msj/0197/exception/exception.aspx for a background piece on Windows
+// unmanaged structured exception handling.
+EXCEPTION_HANDLER_IMPL(COMPlusFrameHandler)
+{
+ WRAPPER_NO_CONTRACT;
+ _ASSERTE(!DebugIsEECxxException(pExceptionRecord) && "EE C++ Exception leaked into managed code!");
+
+ STRESS_LOG5(LF_EH, LL_INFO100, "In COMPlusFrameHander EH code = %x flag = %x EIP = %x with ESP = %x, pEstablisherFrame = 0x%p\n",
+ pExceptionRecord->ExceptionCode, pExceptionRecord->ExceptionFlags,
+ pContext ? GetIP(pContext) : 0, pContext ? GetSP(pContext) : 0, pEstablisherFrame);
+
+ _ASSERTE((pContext == NULL) || ((pContext->ContextFlags & CONTEXT_CONTROL) == CONTEXT_CONTROL));
+
+ if (g_fNoExceptions)
+ return ExceptionContinueSearch; // No EH during EE shutdown.
+
+ // Check if the exception represents a GCStress Marker. If it does,
+ // we shouldnt record its entry in the TLS as such exceptions are
+ // continuable and can confuse the VM to treat them as CSE,
+ // as they are implemented using illegal instruction exception.
+
+ bool fIsGCMarker = false;
+
+#ifdef HAVE_GCCOVER // This is a debug only macro
+ if (GCStress<cfg_instr_jit>::IsEnabled())
+ {
+ // UnsafeTlsGetValue trashes last error. When Complus_GCStress=4, GC is invoked
+ // on every allowable JITed instruction by means of our exception handling machanism
+ // it is very easy to trash the last error. For example, a p/invoke called a native method
+ // which sets last error. Before we getting the last error in the IL stub, it is trashed here
+ DWORD dwLastError = GetLastError();
+ fIsGCMarker = IsGcMarker(pExceptionRecord->ExceptionCode, pContext);
+ if (!fIsGCMarker)
+ {
+ SaveCurrentExceptionInfo(pExceptionRecord, pContext);
+ }
+ SetLastError(dwLastError);
+ }
+ else
+#endif
+ {
+ // GCStress does not exist on retail builds (see IsGcMarker implementation for details).
+ SaveCurrentExceptionInfo(pExceptionRecord, pContext);
+ }
+
+ if (fIsGCMarker)
+ {
+ // If this was a GCStress marker exception, then return
+ // ExceptionContinueExecution to the OS.
+ return ExceptionContinueExecution;
+ }
+
+ EXCEPTION_DISPOSITION retVal = ExceptionContinueSearch;
+
+ Thread *pThread = GetThread();
+ if ((pExceptionRecord->ExceptionFlags & (EXCEPTION_UNWINDING | EXCEPTION_EXIT_UNWIND)) == 0)
+ {
+ if (IsSOExceptionCode(pExceptionRecord->ExceptionCode))
+ {
+ EEPolicy::HandleStackOverflow(SOD_ManagedFrameHandler, (void*)pEstablisherFrame);
+
+ // VC's unhandled exception filter plays with stack. It VirtualAlloc's a new stack, and
+ // then launch Watson from the new stack. When Watson asks CLR to save required data, we
+ // are not able to walk the stack.
+ // Setting Context in ExInfo so that our Watson dump routine knows how to walk this stack.
+ ExInfo* pExInfo = &(pThread->GetExceptionState()->m_currentExInfo);
+ pExInfo->m_pContext = pContext;
+
+ // Save the reference to the topmost handler we see during first pass when an SO goes past us.
+ // When an unwind gets triggered for the exception, we will reset the frame chain when we reach
+ // the topmost handler we saw during the first pass.
+ //
+ // This unifies, behaviour-wise, 32bit with 64bit.
+ if ((pExInfo->m_pTopMostHandlerDuringSO == NULL) ||
+ (pEstablisherFrame > pExInfo->m_pTopMostHandlerDuringSO))
+ {
+ pExInfo->m_pTopMostHandlerDuringSO = pEstablisherFrame;
+ }
+
+ // Switch to preemp mode since we are returning back to the OS.
+ // We will do the quick switch since we are short of stack
+ FastInterlockAnd (&pThread->m_fPreemptiveGCDisabled, 0);
+
+ return ExceptionContinueSearch;
+ }
+ else
+ {
+#ifdef FEATURE_STACK_PROBE
+ if (GetEEPolicy()->GetActionOnFailure(FAIL_StackOverflow) == eRudeUnloadAppDomain)
+ {
+ RetailStackProbe(static_cast<unsigned int>(ADJUST_PROBE(BACKOUT_CODE_STACK_LIMIT)), pThread);
+ }
+#endif
+ }
+ }
+ else
+ {
+ DWORD exceptionCode = pExceptionRecord->ExceptionCode;
+
+ if (exceptionCode == STATUS_UNWIND)
+ {
+ // If exceptionCode is STATUS_UNWIND, RtlUnwind is called with a NULL ExceptionRecord,
+ // therefore OS uses a faked ExceptionRecord with STATUS_UNWIND code. Then we need to
+ // look at our saved exception code.
+ exceptionCode = GetCurrentExceptionCode();
+ }
+
+ if (IsSOExceptionCode(exceptionCode))
+ {
+ // We saved the context during the first pass in case the stack overflow exception is
+ // unhandled and Watson dump code needs it. Now we are in the second pass, therefore
+ // either the exception is handled by user code, or we have finished unhandled exception
+ // filter process, and the OS is unwinding the stack. Either way, we don't need the
+ // context any more. It is very important to reset the context so that our code does not
+ // accidentally walk the frame using the dangling context in ExInfoWalker::WalkToPosition.
+ ExInfo* pExInfo = &(pThread->GetExceptionState()->m_currentExInfo);
+ pExInfo->m_pContext = NULL;
+
+ // We should have the reference to the topmost handler seen during the first pass of SO
+ _ASSERTE(pExInfo->m_pTopMostHandlerDuringSO != NULL);
+
+ // Reset frame chain till we reach the topmost establisher frame we saw in the first pass.
+ // This will ensure that if any intermediary frame calls back into managed (e.g. native frame
+ // containing a __finally that reverse pinvokes into managed), then we have the correct
+ // explicit frame on the stack. Resetting the frame chain only when we reach the topmost
+ // personality routine seen in the first pass may not result in expected behaviour,
+ // specially during stack walks when crawl frame needs to be initialized from
+ // explicit frame.
+ if (pEstablisherFrame <= pExInfo->m_pTopMostHandlerDuringSO)
+ {
+ GCX_COOP_NO_DTOR();
+
+ if (pThread->GetFrame() < GetCurrFrame(pEstablisherFrame))
+ {
+ // We are very short of stack. We avoid calling UnwindFrame which may
+ // run unknown code here.
+ pThread->SetFrame(GetCurrFrame(pEstablisherFrame));
+ }
+ }
+
+ // Switch to preemp mode since we are returning back to the OS.
+ // We will do the quick switch since we are short of stack
+ FastInterlockAnd(&pThread->m_fPreemptiveGCDisabled, 0);
+
+ return ExceptionContinueSearch;
+ }
+ }
+
+ // <TODO> . We need to probe here, but can't introduce destructors etc. </TODO>
+ BEGIN_CONTRACT_VIOLATION(SOToleranceViolation);
+
+ if (pExceptionRecord->ExceptionFlags & (EXCEPTION_UNWINDING | EXCEPTION_EXIT_UNWIND))
+ {
+ retVal = CPFH_UnwindHandler(pExceptionRecord,
+ pEstablisherFrame,
+ pContext,
+ pDispatcherContext);
+ }
+ else
+ {
+
+ /* Make no assumptions about the current machine state.
+ <TODO>@PERF: Only needs to be called by the very first handler invoked by SEH </TODO>*/
+ ResetCurrentContext();
+
+ retVal = CPFH_FirstPassHandler(pExceptionRecord,
+ pEstablisherFrame,
+ pContext,
+ pDispatcherContext);
+
+ }
+
+ END_CONTRACT_VIOLATION;
+
+ return retVal;
+} // COMPlusFrameHandler()
+
+
+//-------------------------------------------------------------------------
+// This is called by the EE to restore the stack pointer if necessary.
+//-------------------------------------------------------------------------
+
+// This can't be inlined into the caller to avoid introducing EH frame
+NOINLINE LPVOID COMPlusEndCatchWorker(Thread * pThread)
+{
+ STATIC_CONTRACT_THROWS;
+ STATIC_CONTRACT_GC_TRIGGERS;
+ STATIC_CONTRACT_MODE_COOPERATIVE;
+ STATIC_CONTRACT_SO_INTOLERANT;
+
+ LOG((LF_EH, LL_INFO1000, "COMPlusPEndCatch:called with "
+ "pThread:0x%x\n",pThread));
+
+ // indicate that we are out of the managed clause as early as possible
+ ExInfo* pExInfo = &(pThread->GetExceptionState()->m_currentExInfo);
+ pExInfo->m_EHClauseInfo.SetManagedCodeEntered(FALSE);
+
+ void* esp = NULL;
+
+ // @todo . We need to probe in the EH code, but can't introduce destructors etc.
+ BEGIN_CONTRACT_VIOLATION(SOToleranceViolation);
+
+ // Notify the profiler that the catcher has finished running
+ // IL stubs don't contain catch blocks so inability to perform this check does not matter.
+ // if (!pFunc->IsILStub())
+ EEToProfilerExceptionInterfaceWrapper::ExceptionCatcherLeave();
+
+ // no need to set pExInfo->m_ClauseType = (DWORD)COR_PRF_CLAUSE_NONE now that the
+ // notification is done because because the ExInfo record is about to be popped off anyway
+
+ LOG((LF_EH, LL_INFO1000, "COMPlusPEndCatch:pThread:0x%x\n",pThread));
+
+#ifdef _DEBUG
+ gLastResumedExceptionFunc = NULL;
+ gLastResumedExceptionHandler = 0;
+#endif
+ // Set the thrown object to NULL as no longer needed. This also sets the last thrown object to NULL.
+ pThread->SafeSetThrowables(NULL);
+
+ // reset the stashed exception info
+ pExInfo->m_pExceptionRecord = NULL;
+ pExInfo->m_pContext = NULL;
+ pExInfo->m_pExceptionPointers = NULL;
+
+ if (pExInfo->m_pShadowSP)
+ {
+ *pExInfo->m_pShadowSP = 0; // Reset the shadow SP
+ }
+
+ // pExInfo->m_dEsp was set in ResumeAtJITEH(). It is the Esp of the
+ // handler nesting level which catches the exception.
+ esp = (void*)(size_t)pExInfo->m_dEsp;
+
+ pExInfo->UnwindExInfo(esp);
+
+ // Prepare to sync managed exception state
+ //
+ // In a case when we're nested inside another catch block, the domain in which we're executing may not be the
+ // same as the one the domain of the throwable that was just made the current throwable above. Therefore, we
+ // make a special effort to preserve the domain of the throwable as we update the the last thrown object.
+ //
+ // This function (COMPlusEndCatch) can also be called by the in-proc debugger helper thread on x86 when
+ // an attempt to SetIP takes place to set IP outside the catch clause. In such a case, managed thread object
+ // will not be available. Thus, we should reset the severity only if its not such a thread.
+ //
+ // This behaviour (of debugger doing SetIP) is not allowed on 64bit since the catch clauses are implemented
+ // as a seperate funclet and it's just not allowed to set the IP across EH scopes, such as from inside a catch
+ // clause to outside of the catch clause.
+ bool fIsDebuggerHelperThread = (g_pDebugInterface == NULL) ? false : g_pDebugInterface->ThisIsHelperThread();
+
+ // Sync managed exception state, for the managed thread, based upon any active exception tracker
+ pThread->SyncManagedExceptionState(fIsDebuggerHelperThread);
+
+ LOG((LF_EH, LL_INFO1000, "COMPlusPEndCatch: esp=%p\n", esp));
+
+ END_CONTRACT_VIOLATION;
+
+ return esp;
+}
+
+//
+// This function works in conjunction with JIT_EndCatch. On input, the parameters are set as follows:
+// ebp, ebx, edi, esi: the values of these registers at the end of the catch block
+// *pRetAddress: the next instruction after the call to JIT_EndCatch
+//
+// On output, *pRetAddress is the instruction at which to resume execution. This may be user code,
+// or it may be ThrowControlForThread (which will re-raise a pending ThreadAbortException).
+//
+// Returns the esp to set before resuming at *pRetAddress.
+//
+LPVOID STDCALL COMPlusEndCatch(LPVOID ebp, DWORD ebx, DWORD edi, DWORD esi, LPVOID* pRetAddress)
+{
+ //
+ // PopNestedExceptionRecords directly manipulates fs:[0] chain. This method can't have any EH!
+ //
+ STATIC_CONTRACT_THROWS;
+ STATIC_CONTRACT_GC_TRIGGERS;
+ STATIC_CONTRACT_MODE_COOPERATIVE;
+ STATIC_CONTRACT_SO_INTOLERANT;
+
+ ETW::ExceptionLog::ExceptionCatchEnd();
+ ETW::ExceptionLog::ExceptionThrownEnd();
+
+ void* esp = COMPlusEndCatchWorker(GetThread());
+
+ // We are going to resume at a handler nesting level whose esp is dEsp. Pop off any SEH records below it. This
+ // would be the COMPlusNestedExceptionHandler we had inserted.
+ PopNestedExceptionRecords(esp);
+
+ //
+ // Set up m_OSContext for the call to COMPlusCheckForAbort
+ //
+ Thread* pThread = GetThread();
+ _ASSERTE(pThread != NULL);
+
+ SetIP(pThread->m_OSContext, (PCODE)*pRetAddress);
+ SetSP(pThread->m_OSContext, (TADDR)esp);
+ SetFP(pThread->m_OSContext, (TADDR)ebp);
+ pThread->m_OSContext->Ebx = ebx;
+ pThread->m_OSContext->Edi = edi;
+ pThread->m_OSContext->Esi = esi;
+
+ LPVOID throwControl = COMPlusCheckForAbort((UINT_PTR)*pRetAddress);
+ if (throwControl)
+ *pRetAddress = throwControl;
+
+ return esp;
+}
+
+#endif // !DACCESS_COMPILE
+
+PTR_CONTEXT GetCONTEXTFromRedirectedStubStackFrame(CONTEXT * pContext)
+{
+ LIMITED_METHOD_DAC_CONTRACT;
+
+ UINT_PTR stackSlot = pContext->Ebp + REDIRECTSTUB_EBP_OFFSET_CONTEXT;
+ PTR_PTR_CONTEXT ppContext = dac_cast<PTR_PTR_CONTEXT>((TADDR)stackSlot);
+ return *ppContext;
+}
+
+#if !defined(DACCESS_COMPILE)
+
+PEXCEPTION_REGISTRATION_RECORD GetCurrentSEHRecord()
+{
+ WRAPPER_NO_CONTRACT;
+
+ LPVOID fs0 = (LPVOID)__readfsdword(0);
+
+#if 0 // This walk is too expensive considering we hit it every time we a CONTRACT(NOTHROW)
+#ifdef _DEBUG
+ EXCEPTION_REGISTRATION_RECORD *pEHR = (EXCEPTION_REGISTRATION_RECORD *)fs0;
+ LPVOID spVal;
+ __asm {
+ mov spVal, esp
+ }
+
+ // check that all the eh frames are all greater than the current stack value. If not, the
+ // stack has been updated somehow w/o unwinding the SEH chain.
+
+ // LOG((LF_EH, LL_INFO1000000, "ER Chain:\n"));
+ while (pEHR != NULL && pEHR != EXCEPTION_CHAIN_END) {
+ // LOG((LF_EH, LL_INFO1000000, "\tp: prev:p handler:%x\n", pEHR, pEHR->Next, pEHR->Handler));
+ if (pEHR < spVal) {
+ if (gLastResumedExceptionFunc != 0)
+ _ASSERTE(!"Stack is greater than start of SEH chain - possible missing leave in handler. See gLastResumedExceptionHandler & gLastResumedExceptionFunc for info");
+ else
+ _ASSERTE(!"Stack is greater than start of SEH chain (FS:0)");
+ }
+ if (pEHR->Handler == (void *)-1)
+ _ASSERTE(!"Handler value has been corrupted");
+
+ _ASSERTE(pEHR < pEHR->Next);
+
+ pEHR = pEHR->Next;
+ }
+#endif
+#endif
+
+ return (EXCEPTION_REGISTRATION_RECORD*) fs0;
+}
+
+PEXCEPTION_REGISTRATION_RECORD GetFirstCOMPlusSEHRecord(Thread *pThread) {
+ WRAPPER_NO_CONTRACT;
+ EXCEPTION_REGISTRATION_RECORD *pEHR = *(pThread->GetExceptionListPtr());
+ if (pEHR == EXCEPTION_CHAIN_END || IsUnmanagedToManagedSEHHandler(pEHR)) {
+ return pEHR;
+ } else {
+ return GetNextCOMPlusSEHRecord(pEHR);
+ }
+}
+
+
+PEXCEPTION_REGISTRATION_RECORD GetPrevSEHRecord(EXCEPTION_REGISTRATION_RECORD *next)
+{
+ WRAPPER_NO_CONTRACT;
+ _ASSERTE(IsUnmanagedToManagedSEHHandler(next));
+
+ EXCEPTION_REGISTRATION_RECORD *pEHR = GetCurrentSEHRecord();
+ _ASSERTE(pEHR != 0 && pEHR != EXCEPTION_CHAIN_END);
+
+ EXCEPTION_REGISTRATION_RECORD *pBest = 0;
+ while (pEHR != next) {
+ if (IsUnmanagedToManagedSEHHandler(pEHR))
+ pBest = pEHR;
+ pEHR = pEHR->Next;
+ _ASSERTE(pEHR != 0 && pEHR != EXCEPTION_CHAIN_END);
+ }
+
+ return pBest;
+}
+
+VOID SetCurrentSEHRecord(EXCEPTION_REGISTRATION_RECORD *pSEH)
+{
+ WRAPPER_NO_CONTRACT;
+ *GetThread()->GetExceptionListPtr() = pSEH;
+}
+
+
+//
+// Unwind pExinfo, pops FS:[0] handlers until the interception context SP, and
+// resumes at interception context.
+//
+VOID UnwindExceptionTrackerAndResumeInInterceptionFrame(ExInfo* pExInfo, EHContext* context)
+{
+ STATIC_CONTRACT_NOTHROW;
+ STATIC_CONTRACT_GC_NOTRIGGER;
+ STATIC_CONTRACT_MODE_COOPERATIVE;
+ STATIC_CONTRACT_SO_TOLERANT;
+
+ _ASSERTE(pExInfo && context);
+
+ pExInfo->UnwindExInfo((LPVOID)(size_t)context->Esp);
+ PopNestedExceptionRecords((LPVOID)(size_t)context->Esp);
+
+ STRESS_LOG3(LF_EH|LF_CORDB, LL_INFO100, "UnwindExceptionTrackerAndResumeInInterceptionFrame: completing intercept at EIP = %p ESP = %p EBP = %p\n", context->Eip, context->Esp, context->Ebp);
+
+ ResumeAtJitEHHelper(context);
+ UNREACHABLE_MSG("Should never return from ResumeAtJitEHHelper!");
+}
+
+//
+// Pop SEH records below the given target ESP. This is only used to pop nested exception records.
+// If bCheckForUnknownHandlers is set, it only checks for unknown FS:[0] handlers.
+//
+BOOL PopNestedExceptionRecords(LPVOID pTargetSP, BOOL bCheckForUnknownHandlers)
+{
+ // No CONTRACT here, because we can't run the risk of it pushing any SEH into the current method.
+ STATIC_CONTRACT_NOTHROW;
+ STATIC_CONTRACT_GC_NOTRIGGER;
+ STATIC_CONTRACT_SO_TOLERANT;
+
+ PEXCEPTION_REGISTRATION_RECORD pEHR = GetCurrentSEHRecord();
+
+ while ((LPVOID)pEHR < pTargetSP)
+ {
+ //
+ // The only handler type we're allowed to have below the limit on the FS:0 chain in these cases is a nested
+ // exception record, so we verify that here.
+ //
+ // There is a special case, of course: for an unhandled exception, when the default handler does the exit
+ // unwind, we may have an exception that escapes a finally clause, thus replacing the original unhandled
+ // exception. If we find a catcher for that new exception, then we'll go ahead and do our own unwind, then
+ // jump to the catch. When we are called here, just before jumpping to the catch, we'll pop off our nested
+ // handlers, then we'll pop off one more handler: the handler that ntdll!ExecuteHandler2 pushed before
+ // calling our nested handler. We go ahead and pop off that handler, too. Its okay, its only there to catch
+ // exceptions from handlers and turn them into collided unwind status codes... there's no cleanup in the
+ // handler that we're removing, and that's the important point. The handler that ExecuteHandler2 pushes
+ // isn't a public export from ntdll, but its named "UnwindHandler" and is physically shortly after
+ // ExecuteHandler2 in ntdll.
+ //
+ static HINSTANCE ExecuteHandler2Module = 0;
+ static BOOL ExecuteHandler2ModuleInited = FALSE;
+
+ // Cache the handle to the dll with the handler pushed by ExecuteHandler2.
+ if (!ExecuteHandler2ModuleInited)
+ {
+ ExecuteHandler2Module = WszGetModuleHandle(W("ntdll.dll"));
+ ExecuteHandler2ModuleInited = TRUE;
+ }
+
+ if (bCheckForUnknownHandlers)
+ {
+ if (!IsComPlusNestedExceptionRecord(pEHR) ||
+ !((ExecuteHandler2Module != NULL) && IsIPInModule(ExecuteHandler2Module, (PCODE)pEHR->Handler)))
+ {
+ return TRUE;
+ }
+ }
+#ifdef _DEBUG
+ else
+ {
+ // Note: if we can't find the module containing ExecuteHandler2, we'll just be really strict and require
+ // that we're only popping nested handlers.
+ _ASSERTE(IsComPlusNestedExceptionRecord(pEHR) ||
+ ((ExecuteHandler2Module != NULL) && IsIPInModule(ExecuteHandler2Module, (PCODE)pEHR->Handler)));
+ }
+#endif // _DEBUG
+
+ pEHR = pEHR->Next;
+ }
+
+ if (!bCheckForUnknownHandlers)
+ {
+ SetCurrentSEHRecord(pEHR);
+ }
+ return FALSE;
+}
+
+//
+// This is implemented differently from the PopNestedExceptionRecords above because it's called in the context of
+// the DebuggerRCThread to operate on the stack of another thread.
+//
+VOID PopNestedExceptionRecords(LPVOID pTargetSP, CONTEXT *pCtx, void *pSEH)
+{
+ // No CONTRACT here, because we can't run the risk of it pushing any SEH into the current method.
+ STATIC_CONTRACT_NOTHROW;
+ STATIC_CONTRACT_GC_NOTRIGGER;
+
+#ifdef _DEBUG
+ LOG((LF_CORDB,LL_INFO1000, "\nPrintSEHRecords:\n"));
+
+ EXCEPTION_REGISTRATION_RECORD *pEHR = (EXCEPTION_REGISTRATION_RECORD *)(size_t)*(DWORD *)pSEH;
+
+ // check that all the eh frames are all greater than the current stack value. If not, the
+ // stack has been updated somehow w/o unwinding the SEH chain.
+ while (pEHR != NULL && pEHR != EXCEPTION_CHAIN_END)
+ {
+ LOG((LF_EH, LL_INFO1000000, "\t%08x: next:%08x handler:%x\n", pEHR, pEHR->Next, pEHR->Handler));
+ pEHR = pEHR->Next;
+ }
+#endif
+
+ DWORD dwCur = *(DWORD*)pSEH; // 'EAX' in the original routine
+ DWORD dwPrev = (DWORD)(size_t)pSEH;
+
+ while (dwCur < (DWORD)(size_t)pTargetSP)
+ {
+ // Watch for the OS handler
+ // for nested exceptions, or any C++ handlers for destructors in our call
+ // stack, or anything else.
+ if (dwCur < (DWORD)GetSP(pCtx))
+ dwPrev = dwCur;
+
+ dwCur = *(DWORD *)(size_t)dwCur;
+
+ LOG((LF_CORDB,LL_INFO10000, "dwCur: 0x%x dwPrev:0x%x pTargetSP:0x%x\n",
+ dwCur, dwPrev, pTargetSP));
+ }
+
+ *(DWORD *)(size_t)dwPrev = dwCur;
+
+#ifdef _DEBUG
+ pEHR = (EXCEPTION_REGISTRATION_RECORD *)(size_t)*(DWORD *)pSEH;
+ // check that all the eh frames are all greater than the current stack value. If not, the
+ // stack has been updated somehow w/o unwinding the SEH chain.
+
+ LOG((LF_CORDB,LL_INFO1000, "\nPopSEHRecords:\n"));
+ while (pEHR != NULL && pEHR != (void *)-1)
+ {
+ LOG((LF_EH, LL_INFO1000000, "\t%08x: next:%08x handler:%x\n", pEHR, pEHR->Next, pEHR->Handler));
+ pEHR = pEHR->Next;
+ }
+#endif
+}
+
+//==========================================================================
+// COMPlusThrowCallback
+//
+//==========================================================================
+
+/*
+ *
+ * COMPlusThrowCallbackHelper
+ *
+ * This function is a simple helper function for COMPlusThrowCallback. It is needed
+ * because of the EX_TRY macro. This macro does an alloca(), which allocates space
+ * off the stack, not free'ing it. Thus, doing a EX_TRY in a loop can easily result
+ * in a stack overflow error. By factoring out the EX_TRY into a separate function,
+ * we recover that stack space.
+ *
+ * Parameters:
+ * pJitManager - The JIT manager that will filter the EH.
+ * pCf - The frame to crawl.
+ * EHClausePtr
+ * nestingLevel
+ * pThread - Used to determine if the thread is throwable or not.
+ *
+ * Return:
+ * Exception status.
+ *
+ */
+int COMPlusThrowCallbackHelper(IJitManager *pJitManager,
+ CrawlFrame *pCf,
+ ThrowCallbackType* pData,
+ EE_ILEXCEPTION_CLAUSE *EHClausePtr,
+ DWORD nestingLevel,
+ OBJECTREF throwable,
+ Thread *pThread
+ )
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ GC_TRIGGERS;
+ MODE_COOPERATIVE;
+ }
+ CONTRACTL_END;
+
+ int iFilt = 0;
+ BOOL impersonating = FALSE;
+
+ EX_TRY
+ {
+ GCPROTECT_BEGIN (throwable);
+ if (pData->hCallerToken != NULL)
+ {
+ STRESS_LOG1(LF_EH, LL_INFO100, "In COMPlusThrowCallbackHelper hCallerToken = %d\n",pData->hCallerToken);
+ // CLR_ImpersonateLoggedOnUser fails fast on error
+ COMPrincipal::CLR_ImpersonateLoggedOnUser(pData->hCallerToken);
+ impersonating = TRUE;
+ }
+
+ // We want to call filters even if the thread is aborting, so suppress abort
+ // checks while the filter runs.
+ ThreadPreventAsyncHolder preventAbort;
+
+ BYTE* startAddress = (BYTE*)pCf->GetCodeInfo()->GetStartAddress();
+ iFilt = ::CallJitEHFilter(pCf, startAddress, EHClausePtr, nestingLevel, throwable);
+
+ if (impersonating)
+ {
+ STRESS_LOG1(LF_EH, LL_INFO100, "In COMPlusThrowCallbackHelper hImpersonationToken = %d\n",pData->hImpersonationToken);
+ // CLR_ImpersonateLoggedOnUser fails fast on error
+ COMPrincipal::CLR_ImpersonateLoggedOnUser(pData->hImpersonationToken);
+ impersonating = FALSE;
+ }
+ GCPROTECT_END();
+ }
+ EX_CATCH
+ {
+ if (impersonating)
+ {
+ STRESS_LOG1(LF_EH, LL_INFO100, "In COMPlusThrowCallbackHelper EX_CATCH hImpersonationToken = %d\n",pData->hImpersonationToken);
+ // CLR_ImpersonateLoggedOnUser fails fast on error
+ COMPrincipal::CLR_ImpersonateLoggedOnUser(pData->hImpersonationToken);
+ impersonating = FALSE;
+ }
+
+ // We had an exception in filter invocation that remained unhandled.
+ // Sync managed exception state, for the managed thread, based upon the active exception tracker.
+ pThread->SyncManagedExceptionState(false);
+
+ //
+ // Swallow exception. Treat as exception continue search.
+ //
+ iFilt = EXCEPTION_CONTINUE_SEARCH;
+
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ return iFilt;
+}
+
+//******************************************************************************
+// The stack walk callback for exception handling on x86.
+// Returns one of:
+// SWA_CONTINUE = 0, // continue walking
+// SWA_ABORT = 1, // stop walking, early out in "failure case"
+// SWA_FAILED = 2 // couldn't walk stack
+StackWalkAction COMPlusThrowCallback( // SWA value
+ CrawlFrame *pCf, // Data from StackWalkFramesEx
+ ThrowCallbackType *pData) // Context data passed through from CPFH
+{
+ // 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;
+
+ Frame *pFrame = pCf->GetFrame();
+ MethodDesc *pFunc = pCf->GetFunction();
+
+ #if defined(_DEBUG)
+ #define METHODNAME(pFunc) (pFunc?pFunc->m_pszDebugMethodName:"<n/a>")
+ #else
+ #define METHODNAME(pFunc) "<n/a>"
+ #endif
+ STRESS_LOG4(LF_EH, LL_INFO100, "COMPlusThrowCallback: STACKCRAWL method:%pM ('%s'), Frame:%p, FrameVtable = %pV\n",
+ pFunc, METHODNAME(pFunc), pFrame, pCf->IsFrameless()?0:(*(void**)pFrame));
+ #undef METHODNAME
+
+ Thread *pThread = GetThread();
+
+ if (pFrame && pData->pTopFrame == pFrame)
+ /* Don't look past limiting frame if there is one */
+ return SWA_ABORT;
+
+ if (!pFunc)
+ return SWA_CONTINUE;
+
+ if (pThread->IsRudeAbortInitiated() && !pThread->IsWithinCer(pCf))
+ {
+ return SWA_CONTINUE;
+ }
+
+ ExInfo* pExInfo = &(pThread->GetExceptionState()->m_currentExInfo);
+
+ _ASSERTE(!pData->bIsUnwind);
+#ifdef _DEBUG
+ // It SHOULD be the case that any frames we consider live between this exception
+ // record and the previous one.
+ if (!pExInfo->m_pPrevNestedInfo) {
+ if (pData->pCurrentExceptionRecord) {
+ if (pFrame) _ASSERTE(pData->pCurrentExceptionRecord > pFrame);
+ if (pCf->IsFrameless()) _ASSERTE((ULONG_PTR)pData->pCurrentExceptionRecord >= GetRegdisplaySP(pCf->GetRegisterSet()));
+ }
+ if (pData->pPrevExceptionRecord) {
+ // FCALLS have an extra SEH record in debug because of the desctructor
+ // associated with ForbidGC checking. This is benign, so just ignore it.
+ if (pFrame) _ASSERTE(pData->pPrevExceptionRecord < pFrame || pFrame->GetVTablePtr() == HelperMethodFrame::GetMethodFrameVPtr());
+ if (pCf->IsFrameless()) _ASSERTE((ULONG_PTR)pData->pPrevExceptionRecord <= GetRegdisplaySP(pCf->GetRegisterSet()));
+ }
+ }
+#endif
+
+ UINT_PTR currentIP = 0;
+ UINT_PTR currentSP = 0;
+
+ if (pCf->IsFrameless())
+ {
+ currentIP = (UINT_PTR)GetControlPC(pCf->GetRegisterSet());
+ currentSP = (UINT_PTR)GetRegdisplaySP(pCf->GetRegisterSet());
+ }
+ else if (InlinedCallFrame::FrameHasActiveCall(pFrame))
+ {
+ // don't have the IP, SP for native code
+ currentIP = 0;
+ currentSP = 0;
+ }
+ else
+ {
+ currentIP = (UINT_PTR)(pCf->GetFrame()->GetIP());
+ currentSP = 0; //Don't have an SP to get.
+ }
+
+ if (!pFunc->IsILStub())
+ {
+ // Append the current frame to the stack trace and save the save trace to the managed Exception object.
+ pExInfo->m_StackTraceInfo.AppendElement(pData->bAllowAllocMem, currentIP, currentSP, pFunc, pCf);
+
+ pExInfo->m_StackTraceInfo.SaveStackTrace(pData->bAllowAllocMem,
+ pThread->GetThrowableAsHandle(),
+ pData->bReplaceStack,
+ pData->bSkipLastElement);
+ }
+ else
+ {
+ LOG((LF_EH, LL_INFO1000, "COMPlusThrowCallback: Skipping AppendElement/SaveStackTrace for IL stub MD %p\n", pFunc));
+ }
+
+ // Fire an exception thrown ETW event when an exception occurs
+ ETW::ExceptionLog::ExceptionThrown(pCf, pData->bSkipLastElement, pData->bReplaceStack);
+
+ // Reset the flags. These flags are set only once before each stack walk done by LookForHandler(), and
+ // they apply only to the first frame we append to the stack trace. Subsequent frames are always appended.
+ if (pData->bReplaceStack)
+ {
+ pData->bReplaceStack = FALSE;
+ }
+ if (pData->bSkipLastElement)
+ {
+ pData->bSkipLastElement = FALSE;
+ }
+
+ // Check for any impersonation on the frame and save that for use during EH filter callbacks
+ OBJECTREF* pRefSecDesc = pCf->GetAddrOfSecurityObject();
+ if (pRefSecDesc != NULL && *pRefSecDesc != NULL)
+ {
+ FRAMESECDESCREF fsdRef = (FRAMESECDESCREF)*pRefSecDesc;
+ if (fsdRef->GetCallerToken() != NULL)
+ {
+ // Impersonation info present on the Frame
+ pData->hCallerToken = fsdRef->GetCallerToken();
+ STRESS_LOG1(LF_EH, LL_INFO100, "In COMPlusThrowCallback. Found non-NULL callertoken on FSD:%d\n",pData->hCallerToken);
+ if (!pData->bImpersonationTokenSet)
+ {
+ pData->hImpersonationToken = fsdRef->GetImpersonationToken();
+ STRESS_LOG1(LF_EH, LL_INFO100, "In COMPlusThrowCallback. Found non-NULL impersonationtoken on FSD:%d\n",pData->hImpersonationToken);
+ pData->bImpersonationTokenSet = TRUE;
+ }
+ }
+ }
+
+ // now we've got the stack trace, if we aren't allowed to catch this and we're first pass, return
+ if (pData->bDontCatch)
+ return SWA_CONTINUE;
+
+ if (!pCf->IsFrameless())
+ {
+ // @todo - remove this once SIS is fully enabled.
+ extern bool g_EnableSIS;
+ if (g_EnableSIS)
+ {
+ // For debugger, we may want to notify 1st chance exceptions if they're coming out of a stub.
+ // We recognize stubs as Frames with a M2U transition type. The debugger's stackwalker also
+ // recognizes these frames and publishes ICorDebugInternalFrames in the stackwalk. It's
+ // important to use pFrame as the stack address so that the Exception callback matches up
+ // w/ the ICorDebugInternlFrame stack range.
+ if (CORDebuggerAttached())
+ {
+ Frame * pFrameStub = pCf->GetFrame();
+ Frame::ETransitionType t = pFrameStub->GetTransitionType();
+ if (t == Frame::TT_M2U)
+ {
+ // Use address of the frame as the stack address.
+ currentSP = (SIZE_T) ((void*) pFrameStub);
+ currentIP = 0; // no IP.
+ EEToDebuggerExceptionInterfaceWrapper::FirstChanceManagedException(pThread, (SIZE_T)currentIP, (SIZE_T)currentSP);
+#ifdef FEATURE_EXCEPTION_NOTIFICATIONS
+ // Deliver the FirstChanceNotification after the debugger, if not already delivered.
+ if (!pExInfo->DeliveredFirstChanceNotification())
+ {
+ ExceptionNotifications::DeliverFirstChanceNotification();
+ }
+#endif // FEATURE_EXCEPTION_NOTIFICATIONS
+ }
+ }
+ }
+ return SWA_CONTINUE;
+ }
+
+ bool fIsILStub = pFunc->IsILStub();
+ bool fGiveDebuggerAndProfilerNotification = !fIsILStub;
+ BOOL fMethodCanHandleException = TRUE;
+
+ MethodDesc * pUserMDForILStub = NULL;
+ Frame * pILStubFrame = NULL;
+ if (fIsILStub)
+ pUserMDForILStub = GetUserMethodForILStub(pThread, currentSP, pFunc, &pILStubFrame);
+
+#ifdef FEATURE_CORRUPTING_EXCEPTIONS
+ CorruptionSeverity currentSeverity = pThread->GetExceptionState()->GetCurrentExceptionTracker()->GetCorruptionSeverity();
+ {
+ // We must defer to the MethodDesc of the user method instead of the IL stub
+ // itself because the user can specify the policy on a per-method basis and
+ // that won't be reflected via the IL stub's MethodDesc.
+ MethodDesc * pMDWithCEAttribute = fIsILStub ? pUserMDForILStub : pFunc;
+
+ // Check if the exception can be delivered to the method? It will check if the exception
+ // is a CE or not. If it is, it will check if the method can process it or not.
+ fMethodCanHandleException = CEHelper::CanMethodHandleException(currentSeverity, pMDWithCEAttribute);
+ }
+#endif // FEATURE_CORRUPTING_EXCEPTIONS
+
+ // Let the profiler know that we are searching for a handler within this function instance
+ if (fGiveDebuggerAndProfilerNotification)
+ EEToProfilerExceptionInterfaceWrapper::ExceptionSearchFunctionEnter(pFunc);
+
+ // The following debugger notification and AppDomain::FirstChanceNotification should be scoped together
+ // since the AD notification *must* follow immediately after the debugger's notification.
+ {
+#ifdef DEBUGGING_SUPPORTED
+ //
+ // Go ahead and notify any debugger of this exception.
+ //
+ EEToDebuggerExceptionInterfaceWrapper::FirstChanceManagedException(pThread, (SIZE_T)currentIP, (SIZE_T)currentSP);
+
+ if (CORDebuggerAttached() && pExInfo->m_ExceptionFlags.DebuggerInterceptInfo())
+ {
+ return SWA_ABORT;
+ }
+#endif // DEBUGGING_SUPPORTED
+
+#ifdef FEATURE_EXCEPTION_NOTIFICATIONS
+ // Attempt to deliver the first chance notification to the AD only *AFTER* the debugger
+ // has done that, provided we have not already done that.
+ if (!pExInfo->DeliveredFirstChanceNotification())
+ {
+ ExceptionNotifications::DeliverFirstChanceNotification();
+ }
+#endif // FEATURE_EXCEPTION_NOTIFICATIONS
+ }
+ IJitManager* pJitManager = pCf->GetJitManager();
+ _ASSERTE(pJitManager);
+ EH_CLAUSE_ENUMERATOR pEnumState;
+ unsigned EHCount = 0;
+
+#ifdef FEATURE_CORRUPTING_EXCEPTIONS
+ // If exception cannot be handled, then just bail out. We shouldnt examine the EH clauses
+ // in such a method.
+ if (!fMethodCanHandleException)
+ {
+ LOG((LF_EH, LL_INFO100, "COMPlusThrowCallback - CEHelper decided not to look for exception handlers in the method(MD:%p).\n", pFunc));
+
+ // Set the flag to skip this frame since the CE cannot be delivered
+ _ASSERTE(currentSeverity == ProcessCorrupting);
+
+ // Ensure EHClause count is zero
+ EHCount = 0;
+ }
+ else
+#endif // FEATURE_CORRUPTING_EXCEPTIONS
+ {
+ EHCount = pJitManager->InitializeEHEnumeration(pCf->GetMethodToken(), &pEnumState);
+ }
+
+ if (EHCount == 0)
+ {
+ // Inform the profiler that we're leaving, and what pass we're on
+ if (fGiveDebuggerAndProfilerNotification)
+ EEToProfilerExceptionInterfaceWrapper::ExceptionSearchFunctionLeave(pFunc);
+ return SWA_CONTINUE;
+ }
+
+ TypeHandle thrownType = TypeHandle();
+ // if we are being called on an unwind for an exception that we did not try to catch, eg.
+ // an internal EE exception, then pThread->GetThrowable will be null
+ {
+ OBJECTREF throwable = pThread->GetThrowable();
+ if (throwable != NULL)
+ {
+ throwable = PossiblyUnwrapThrowable(throwable, pCf->GetAssembly());
+ thrownType = TypeHandle(throwable->GetTrueMethodTable());
+ }
+ }
+
+ PREGDISPLAY regs = pCf->GetRegisterSet();
+ BYTE *pStack = (BYTE *) GetRegdisplaySP(regs);
+#ifdef DEBUGGING_SUPPORTED
+ BYTE *pHandlerEBP = (BYTE *) GetRegdisplayFP(regs);
+#endif
+
+ DWORD offs = (DWORD)pCf->GetRelOffset(); //= (BYTE*) (*regs->pPC) - (BYTE*) pCf->GetStartAddress();
+ STRESS_LOG1(LF_EH, LL_INFO10000, "COMPlusThrowCallback: offset is %d\n", offs);
+
+ EE_ILEXCEPTION_CLAUSE EHClause;
+ unsigned start_adjust, end_adjust;
+
+ start_adjust = !(pCf->HasFaulted() || pCf->IsIPadjusted());
+ end_adjust = pCf->IsActiveFunc();
+
+ for(ULONG i=0; i < EHCount; i++)
+ {
+ pJitManager->GetNextEHClause(&pEnumState, &EHClause);
+ _ASSERTE(IsValidClause(&EHClause));
+
+ STRESS_LOG4(LF_EH, LL_INFO100, "COMPlusThrowCallback: considering '%s' clause [%d,%d], ofs:%d\n",
+ (IsFault(&EHClause) ? "fault" : (
+ IsFinally(&EHClause) ? "finally" : (
+ IsFilterHandler(&EHClause) ? "filter" : (
+ IsTypedHandler(&EHClause) ? "typed" : "unknown")))),
+ EHClause.TryStartPC,
+ EHClause.TryEndPC,
+ offs
+ );
+
+ // Checking the exception range is a bit tricky because
+ // on CPU faults (null pointer access, div 0, ..., the IP points
+ // to the faulting instruction, but on calls, the IP points
+ // to the next instruction.
+ // This means that we should not include the start point on calls
+ // as this would be a call just preceding the try block.
+ // Also, we should include the end point on calls, but not faults.
+
+ // If we're in the FILTER part of a filter clause, then we
+ // want to stop crawling. It's going to be caught in a
+ // EX_CATCH just above us. If not, the exception
+ if ( IsFilterHandler(&EHClause)
+ && ( offs > EHClause.FilterOffset
+ || offs == EHClause.FilterOffset && !start_adjust)
+ && ( offs < EHClause.HandlerStartPC
+ || offs == EHClause.HandlerStartPC && !end_adjust)) {
+
+ STRESS_LOG4(LF_EH, LL_INFO100, "COMPlusThrowCallback: Fault inside filter [%d,%d] startAdj %d endAdj %d\n",
+ EHClause.FilterOffset, EHClause.HandlerStartPC, start_adjust, end_adjust);
+
+ if (fGiveDebuggerAndProfilerNotification)
+ EEToProfilerExceptionInterfaceWrapper::ExceptionSearchFunctionLeave(pFunc);
+ return SWA_ABORT;
+ }
+
+ if ( (offs < EHClause.TryStartPC) ||
+ (offs > EHClause.TryEndPC) ||
+ (offs == EHClause.TryStartPC && start_adjust) ||
+ (offs == EHClause.TryEndPC && end_adjust))
+ continue;
+
+ BOOL typeMatch = FALSE;
+ BOOL isTypedHandler = IsTypedHandler(&EHClause);
+
+ if (isTypedHandler && !thrownType.IsNull())
+ {
+ if (EHClause.TypeHandle == (void*)(size_t)mdTypeRefNil)
+ {
+ // this is a catch(...)
+ typeMatch = TRUE;
+ }
+ else
+ {
+ TypeHandle exnType = pJitManager->ResolveEHClause(&EHClause,pCf);
+
+ // if doesn't have cached class then class wasn't loaded so couldn't have been thrown
+ typeMatch = !exnType.IsNull() && ExceptionIsOfRightType(exnType, thrownType);
+ }
+ }
+
+ // <TODO>@PERF: Is this too expensive? Consider storing the nesting level
+ // instead of the HandlerEndPC.</TODO>
+
+ // Determine the nesting level of EHClause. Just walk the table
+ // again, and find out how many handlers enclose it
+ DWORD nestingLevel = 0;
+
+ if (IsFaultOrFinally(&EHClause))
+ continue;
+ if (isTypedHandler)
+ {
+ LOG((LF_EH, LL_INFO100, "COMPlusThrowCallback: %s match for typed handler.\n", typeMatch?"Found":"Did not find"));
+ if (!typeMatch)
+ {
+ continue;
+ }
+ }
+ else
+ {
+ // Must be an exception filter (__except() part of __try{}__except(){}).
+ nestingLevel = ComputeEnclosingHandlerNestingLevel(pJitManager,
+ pCf->GetMethodToken(),
+ EHClause.HandlerStartPC);
+
+ // We just need *any* address within the method. This will let the debugger
+ // resolve the EnC version of the method.
+ PCODE pMethodAddr = GetControlPC(regs);
+ if (fGiveDebuggerAndProfilerNotification)
+ EEToDebuggerExceptionInterfaceWrapper::ExceptionFilter(pFunc, pMethodAddr, EHClause.FilterOffset, pHandlerEBP);
+
+ UINT_PTR uStartAddress = (UINT_PTR)pCf->GetCodeInfo()->GetStartAddress();
+
+ // save clause information in the exinfo
+ pExInfo->m_EHClauseInfo.SetInfo(COR_PRF_CLAUSE_FILTER,
+ uStartAddress + EHClause.FilterOffset,
+ StackFrame((UINT_PTR)pHandlerEBP));
+
+ // Let the profiler know we are entering a filter
+ if (fGiveDebuggerAndProfilerNotification)
+ EEToProfilerExceptionInterfaceWrapper::ExceptionSearchFilterEnter(pFunc);
+
+ COUNTER_ONLY(GetPerfCounters().m_Excep.cFiltersExecuted++);
+
+ STRESS_LOG3(LF_EH, LL_INFO10, "COMPlusThrowCallback: calling filter code, EHClausePtr:%08x, Start:%08x, End:%08x\n",
+ &EHClause, EHClause.HandlerStartPC, EHClause.HandlerEndPC);
+
+ OBJECTREF throwable = PossiblyUnwrapThrowable(pThread->GetThrowable(), pCf->GetAssembly());
+
+ pExInfo->m_EHClauseInfo.SetManagedCodeEntered(TRUE);
+
+ int iFilt = COMPlusThrowCallbackHelper(pJitManager,
+ pCf,
+ pData,
+ &EHClause,
+ nestingLevel,
+ throwable,
+ pThread);
+
+ pExInfo->m_EHClauseInfo.SetManagedCodeEntered(FALSE);
+
+ // Let the profiler know we are leaving a filter
+ if (fGiveDebuggerAndProfilerNotification)
+ EEToProfilerExceptionInterfaceWrapper::ExceptionSearchFilterLeave();
+
+ pExInfo->m_EHClauseInfo.ResetInfo();
+
+ if (pThread->IsRudeAbortInitiated() && !pThread->IsWithinCer(pCf))
+ {
+ if (fGiveDebuggerAndProfilerNotification)
+ EEToProfilerExceptionInterfaceWrapper::ExceptionSearchFunctionLeave(pFunc);
+ return SWA_CONTINUE;
+ }
+
+ // If this filter didn't want the exception, keep looking.
+ if (EXCEPTION_EXECUTE_HANDLER != iFilt)
+ continue;
+ }
+
+ // Record this location, to stop the unwind phase, later.
+ pData->pFunc = pFunc;
+ pData->dHandler = i;
+ pData->pStack = pStack;
+
+ // Notify the profiler that a catcher has been found
+ if (fGiveDebuggerAndProfilerNotification)
+ {
+ EEToProfilerExceptionInterfaceWrapper::ExceptionSearchCatcherFound(pFunc);
+ EEToProfilerExceptionInterfaceWrapper::ExceptionSearchFunctionLeave(pFunc);
+ }
+
+#ifdef DEBUGGING_SUPPORTED
+ //
+ // Notify debugger that a catcher has been found.
+ //
+ if (fIsILStub)
+ {
+ EEToDebuggerExceptionInterfaceWrapper::NotifyOfCHFFilter(pExInfo->m_pExceptionPointers, pILStubFrame);
+ }
+ else
+ if (fGiveDebuggerAndProfilerNotification &&
+ CORDebuggerAttached() && !pExInfo->m_ExceptionFlags.DebuggerInterceptInfo())
+ {
+ _ASSERTE(pData);
+ // We just need *any* address within the method. This will let the debugger
+ // resolve the EnC version of the method.
+ PCODE pMethodAddr = GetControlPC(regs);
+
+ EEToDebuggerExceptionInterfaceWrapper::FirstChanceManagedExceptionCatcherFound(pThread,
+ pData->pFunc, pMethodAddr,
+ (SIZE_T)pData->pStack,
+ &EHClause);
+ }
+#endif // DEBUGGING_SUPPORTED
+
+ return SWA_ABORT;
+ }
+ if (fGiveDebuggerAndProfilerNotification)
+ EEToProfilerExceptionInterfaceWrapper::ExceptionSearchFunctionLeave(pFunc);
+ return SWA_CONTINUE;
+} // StackWalkAction COMPlusThrowCallback()
+
+
+//==========================================================================
+// COMPlusUnwindCallback
+//==========================================================================
+
+#if defined(_MSC_VER)
+#pragma warning(push)
+#pragma warning (disable : 4740) // There is inline asm code in this function, which disables
+ // global optimizations.
+#pragma warning (disable : 4731)
+#endif
+StackWalkAction COMPlusUnwindCallback (CrawlFrame *pCf, ThrowCallbackType *pData)
+{
+ STATIC_CONTRACT_THROWS;
+ STATIC_CONTRACT_GC_NOTRIGGER;
+ STATIC_CONTRACT_MODE_COOPERATIVE;
+
+ _ASSERTE(pData->bIsUnwind);
+
+ Frame *pFrame = pCf->GetFrame();
+ MethodDesc *pFunc = pCf->GetFunction();
+
+ #if defined(_DEBUG)
+ #define METHODNAME(pFunc) (pFunc?pFunc->m_pszDebugMethodName:"<n/a>")
+ #else
+ #define METHODNAME(pFunc) "<n/a>"
+ #endif
+ STRESS_LOG4(LF_EH, LL_INFO100, "COMPlusUnwindCallback: STACKCRAWL method:%pM ('%s'), Frame:%p, FrameVtable = %pV\n",
+ pFunc, METHODNAME(pFunc), pFrame, pCf->IsFrameless()?0:(*(void**)pFrame));
+ #undef METHODNAME
+
+ if (pFrame && pData->pTopFrame == pFrame)
+ /* Don't look past limiting frame if there is one */
+ return SWA_ABORT;
+
+ if (!pFunc)
+ return SWA_CONTINUE;
+
+ if (!pCf->IsFrameless())
+ return SWA_CONTINUE;
+
+ Thread *pThread = GetThread();
+
+ // If the thread is being RudeAbort, we will not run any finally
+ if (pThread->IsRudeAbortInitiated() && !pThread->IsWithinCer(pCf))
+ {
+ return SWA_CONTINUE;
+ }
+
+ IJitManager* pJitManager = pCf->GetJitManager();
+ _ASSERTE(pJitManager);
+
+ ExInfo *pExInfo = &(pThread->GetExceptionState()->m_currentExInfo);
+
+ PREGDISPLAY regs = pCf->GetRegisterSet();
+ BYTE *pStack = (BYTE *) GetRegdisplaySP(regs);
+
+ TypeHandle thrownType = TypeHandle();
+
+ BOOL fCanMethodHandleException = TRUE;
+#ifdef FEATURE_CORRUPTING_EXCEPTIONS
+ // MethodDesc's security information (i.e. whether it is critical or transparent) is calculated lazily.
+ // If this method's security information was not precalculated, then it would have been in the first pass
+ // already using Security::IsMethodCritical which could take have taken us down a path which is GC_TRIGGERS.
+ //
+ //
+ // However, this unwind callback (for X86) is GC_NOTRIGGER and at this point the security information would have been
+ // calculated already. Hence, we wouldnt endup in the GC_TRIGGERS path. Thus, to keep SCAN.EXE (static contract analyzer) happy,
+ // we will pass a FALSE to the CanMethodHandleException call, indicating we dont need to calculate security information (and thus,
+ // not go down the GC_TRIGGERS path.
+ //
+ // Check if the exception can be delivered to the method? It will check if the exception
+ // is a CE or not. If it is, it will check if the method can process it or not.
+ CorruptionSeverity currentSeverity = pThread->GetExceptionState()->GetCurrentExceptionTracker()->GetCorruptionSeverity();
+
+ // We have to do this check for x86 since, unlike 64bit which will setup a new exception tracker for longjmp,
+ // x86 only sets up new trackers in the first pass (and longjmp is 2nd pass only exception). Hence, we pass
+ // this information in the callback structure without affecting any existing exception tracker (incase longjmp was
+ // a nested exception).
+ if (pData->m_fIsLongJump)
+ {
+ // Longjump is not a CSE. With a CSE in progress, this can be invoked by either:
+ //
+ // 1) Managed code (e.g. finally/fault/catch), OR
+ // 2) By native code
+ //
+ // In scenario (1), managed code can invoke it only if it was attributed with HPCSE attribute. Thus,
+ // longjmp is no different than managed code doing a "throw new Exception();".
+ //
+ // In scenario (2), longjmp is no different than any other non-CSE native exception raised.
+ //
+ // In both these case, longjmp should be treated as non-CSE. Since x86 does not setup a tracker for
+ // it (see comment above), we pass this information (of whether the current exception is a longjmp or not)
+ // to this callback (from UnwindFrames) to setup the correct corruption severity.
+ //
+ // http://www.nynaeve.net/?p=105 has a brief description of how exception-safe setjmp/longjmp works.
+ currentSeverity = NotCorrupting;
+ }
+ {
+ MethodDesc * pFuncWithCEAttribute = pFunc;
+ Frame * pILStubFrame = NULL;
+ if (pFunc->IsILStub())
+ {
+ // We must defer to the MethodDesc of the user method instead of the IL stub
+ // itself because the user can specify the policy on a per-method basis and
+ // that won't be reflected via the IL stub's MethodDesc.
+ pFuncWithCEAttribute = GetUserMethodForILStub(pThread, (UINT_PTR)pStack, pFunc, &pILStubFrame);
+ }
+ fCanMethodHandleException = CEHelper::CanMethodHandleException(currentSeverity, pFuncWithCEAttribute, FALSE);
+ }
+#endif // FEATURE_CORRUPTING_EXCEPTIONS
+
+#ifdef DEBUGGING_SUPPORTED
+ LOG((LF_EH, LL_INFO1000, "COMPlusUnwindCallback: Intercept %d, pData->pFunc 0x%X, pFunc 0x%X, pData->pStack 0x%X, pStack 0x%X\n",
+ pExInfo->m_ExceptionFlags.DebuggerInterceptInfo(),
+ pData->pFunc,
+ pFunc,
+ pData->pStack,
+ pStack));
+
+ //
+ // If the debugger wants to intercept this exception here, go do that.
+ //
+ if (pExInfo->m_ExceptionFlags.DebuggerInterceptInfo() && (pData->pFunc == pFunc) && (pData->pStack == pStack))
+ {
+ goto LDoDebuggerIntercept;
+ }
+#endif
+
+ bool fGiveDebuggerAndProfilerNotification;
+ fGiveDebuggerAndProfilerNotification = !pFunc->IsILStub();
+
+ // Notify the profiler of the function we're dealing with in the unwind phase
+ if (fGiveDebuggerAndProfilerNotification)
+ EEToProfilerExceptionInterfaceWrapper::ExceptionUnwindFunctionEnter(pFunc);
+
+ EH_CLAUSE_ENUMERATOR pEnumState;
+ unsigned EHCount;
+
+#ifdef FEATURE_CORRUPTING_EXCEPTIONS
+ if (!fCanMethodHandleException)
+ {
+ LOG((LF_EH, LL_INFO100, "COMPlusUnwindCallback - CEHelper decided not to look for exception handlers in the method(MD:%p).\n", pFunc));
+
+ // Set the flag to skip this frame since the CE cannot be delivered
+ _ASSERTE(currentSeverity == ProcessCorrupting);
+
+ // Force EHClause count to be zero
+ EHCount = 0;
+ }
+ else
+#endif // FEATURE_CORRUPTING_EXCEPTIONS
+ {
+ EHCount = pJitManager->InitializeEHEnumeration(pCf->GetMethodToken(), &pEnumState);
+ }
+
+ if (EHCount == 0)
+ {
+ // Inform the profiler that we're leaving, and what pass we're on
+ if (fGiveDebuggerAndProfilerNotification)
+ EEToProfilerExceptionInterfaceWrapper::ExceptionUnwindFunctionLeave(pFunc);
+
+ return SWA_CONTINUE;
+ }
+
+ // if we are being called on an unwind for an exception that we did not try to catch, eg.
+ // an internal EE exception, then pThread->GetThrowable will be null
+ {
+ OBJECTREF throwable = pThread->GetThrowable();
+ if (throwable != NULL)
+ {
+ throwable = PossiblyUnwrapThrowable(throwable, pCf->GetAssembly());
+ thrownType = TypeHandle(throwable->GetTrueMethodTable());
+ }
+ }
+#ifdef DEBUGGING_SUPPORTED
+ BYTE *pHandlerEBP;
+ pHandlerEBP = (BYTE *) GetRegdisplayFP(regs);
+#endif
+
+ DWORD offs;
+ offs = (DWORD)pCf->GetRelOffset(); //= (BYTE*) (*regs->pPC) - (BYTE*) pCf->GetStartAddress();
+
+ LOG((LF_EH, LL_INFO100, "COMPlusUnwindCallback: current EIP offset in method 0x%x, \n", offs));
+
+ EE_ILEXCEPTION_CLAUSE EHClause;
+ unsigned start_adjust, end_adjust;
+
+ start_adjust = !(pCf->HasFaulted() || pCf->IsIPadjusted());
+ end_adjust = pCf->IsActiveFunc();
+
+ for(ULONG i=0; i < EHCount; i++)
+ {
+ pJitManager->GetNextEHClause(&pEnumState, &EHClause);
+ _ASSERTE(IsValidClause(&EHClause));
+
+ STRESS_LOG4(LF_EH, LL_INFO100, "COMPlusUnwindCallback: considering '%s' clause [%d,%d], offs:%d\n",
+ (IsFault(&EHClause) ? "fault" : (
+ IsFinally(&EHClause) ? "finally" : (
+ IsFilterHandler(&EHClause) ? "filter" : (
+ IsTypedHandler(&EHClause) ? "typed" : "unknown")))),
+ EHClause.TryStartPC,
+ EHClause.TryEndPC,
+ offs
+ );
+
+ // Checking the exception range is a bit tricky because
+ // on CPU faults (null pointer access, div 0, ..., the IP points
+ // to the faulting instruction, but on calls, the IP points
+ // to the next instruction.
+ // This means that we should not include the start point on calls
+ // as this would be a call just preceding the try block.
+ // Also, we should include the end point on calls, but not faults.
+
+ if ( IsFilterHandler(&EHClause)
+ && ( offs > EHClause.FilterOffset
+ || offs == EHClause.FilterOffset && !start_adjust)
+ && ( offs < EHClause.HandlerStartPC
+ || offs == EHClause.HandlerStartPC && !end_adjust)
+ ) {
+ STRESS_LOG4(LF_EH, LL_INFO100, "COMPlusUnwindCallback: Fault inside filter [%d,%d] startAdj %d endAdj %d\n",
+ EHClause.FilterOffset, EHClause.HandlerStartPC, start_adjust, end_adjust);
+
+ // Make the filter as done. See comment in CallJitEHFilter
+ // on why we have to do it here.
+ Frame* pFilterFrame = pThread->GetFrame();
+ _ASSERTE(pFilterFrame->GetVTablePtr() == ExceptionFilterFrame::GetMethodFrameVPtr());
+ ((ExceptionFilterFrame*)pFilterFrame)->SetFilterDone();
+
+ // Inform the profiler that we're leaving, and what pass we're on
+ if (fGiveDebuggerAndProfilerNotification)
+ EEToProfilerExceptionInterfaceWrapper::ExceptionUnwindFunctionLeave(pFunc);
+
+ return SWA_ABORT;
+ }
+
+ if ( (offs < EHClause.TryStartPC) ||
+ (offs > EHClause.TryEndPC) ||
+ (offs == EHClause.TryStartPC && start_adjust) ||
+ (offs == EHClause.TryEndPC && end_adjust))
+ continue;
+
+ // <TODO>@PERF : Is this too expensive? Consider storing the nesting level
+ // instead of the HandlerEndPC.</TODO>
+
+ // Determine the nesting level of EHClause. Just walk the table
+ // again, and find out how many handlers enclose it
+
+ DWORD nestingLevel = ComputeEnclosingHandlerNestingLevel(pJitManager,
+ pCf->GetMethodToken(),
+ EHClause.HandlerStartPC);
+
+ // We just need *any* address within the method. This will let the debugger
+ // resolve the EnC version of the method.
+ PCODE pMethodAddr = GetControlPC(regs);
+
+ UINT_PTR uStartAddress = (UINT_PTR)pCf->GetCodeInfo()->GetStartAddress();
+
+ if (IsFaultOrFinally(&EHClause))
+ {
+ COUNTER_ONLY(GetPerfCounters().m_Excep.cFinallysExecuted++);
+
+ if (fGiveDebuggerAndProfilerNotification)
+ EEToDebuggerExceptionInterfaceWrapper::ExceptionHandle(pFunc, pMethodAddr, EHClause.HandlerStartPC, pHandlerEBP);
+
+ pExInfo->m_EHClauseInfo.SetInfo(COR_PRF_CLAUSE_FINALLY,
+ uStartAddress + EHClause.HandlerStartPC,
+ StackFrame((UINT_PTR)pHandlerEBP));
+
+ // Notify the profiler that we are about to execute the finally code
+ if (fGiveDebuggerAndProfilerNotification)
+ EEToProfilerExceptionInterfaceWrapper::ExceptionUnwindFinallyEnter(pFunc);
+
+ LOG((LF_EH, LL_INFO100, "COMPlusUnwindCallback: finally clause [%d,%d] - call\n", EHClause.TryStartPC, EHClause.TryEndPC));
+
+ pExInfo->m_EHClauseInfo.SetManagedCodeEntered(TRUE);
+
+ ::CallJitEHFinally(pCf, (BYTE *)uStartAddress, &EHClause, nestingLevel);
+
+ pExInfo->m_EHClauseInfo.SetManagedCodeEntered(FALSE);
+
+ LOG((LF_EH, LL_INFO100, "COMPlusUnwindCallback: finally - returned\n"));
+
+ // Notify the profiler that we are done with the finally code
+ if (fGiveDebuggerAndProfilerNotification)
+ EEToProfilerExceptionInterfaceWrapper::ExceptionUnwindFinallyLeave();
+
+ pExInfo->m_EHClauseInfo.ResetInfo();
+
+ continue;
+ }
+
+ // Current is not a finally, check if it's the catching handler (or filter).
+ if (pData->pFunc != pFunc || (ULONG)(pData->dHandler) != i || pData->pStack != pStack)
+ {
+ continue;
+ }
+
+#ifdef _DEBUG
+ gLastResumedExceptionFunc = pCf->GetFunction();
+ gLastResumedExceptionHandler = i;
+#endif
+
+ // save clause information in the exinfo
+ pExInfo->m_EHClauseInfo.SetInfo(COR_PRF_CLAUSE_CATCH,
+ uStartAddress + EHClause.HandlerStartPC,
+ StackFrame((UINT_PTR)pHandlerEBP));
+
+ // Notify the profiler that we are about to resume at the catcher.
+ if (fGiveDebuggerAndProfilerNotification)
+ {
+ DACNotify::DoExceptionCatcherEnterNotification(pFunc, EHClause.HandlerStartPC);
+
+ EEToProfilerExceptionInterfaceWrapper::ExceptionCatcherEnter(pThread, pFunc);
+
+ EEToDebuggerExceptionInterfaceWrapper::ExceptionHandle(pFunc, pMethodAddr, EHClause.HandlerStartPC, pHandlerEBP);
+ }
+
+ STRESS_LOG4(LF_EH, LL_INFO100, "COMPlusUnwindCallback: offset 0x%x matches clause [0x%x, 0x%x) matches in method %pM\n",
+ offs, EHClause.TryStartPC, EHClause.TryEndPC, pFunc);
+
+ // ResumeAtJitEH will set pExInfo->m_EHClauseInfo.m_fManagedCodeEntered = TRUE; at the appropriate time
+ ::ResumeAtJitEH(pCf, (BYTE *)uStartAddress, &EHClause, nestingLevel, pThread, pData->bUnwindStack);
+ //UNREACHABLE_MSG("ResumeAtJitEH shouldn't have returned!");
+
+ // we do not set pExInfo->m_EHClauseInfo.m_fManagedCodeEntered = FALSE here,
+ // that happens when the catch clause calls back to COMPlusEndCatch
+
+ }
+
+ STRESS_LOG1(LF_EH, LL_INFO100, "COMPlusUnwindCallback: no handler found in method %pM\n", pFunc);
+ if (fGiveDebuggerAndProfilerNotification)
+ EEToProfilerExceptionInterfaceWrapper::ExceptionUnwindFunctionLeave(pFunc);
+
+ return SWA_CONTINUE;
+
+
+#ifdef DEBUGGING_SUPPORTED
+LDoDebuggerIntercept:
+
+ STRESS_LOG1(LF_EH|LF_CORDB, LL_INFO100, "COMPlusUnwindCallback: Intercepting in method %pM\n", pFunc);
+
+ //
+ // Setup up the easy parts of the context to restart at.
+ //
+ EHContext context;
+
+ //
+ // Note: EAX ECX EDX are scratch
+ //
+ context.Esp = (DWORD)(size_t)(GetRegdisplaySP(regs));
+ context.Ebx = *regs->pEbx;
+ context.Esi = *regs->pEsi;
+ context.Edi = *regs->pEdi;
+ context.Ebp = *regs->pEbp;
+
+ //
+ // Set scratch registers to 0 to avoid reporting incorrect values to GC in case of debugger changing the IP
+ // in the middle of a scratch register lifetime (see Dev10 754922)
+ //
+ context.Eax = 0;
+ context.Ecx = 0;
+ context.Edx = 0;
+
+ //
+ // Ok, now set the target Eip to the address the debugger requested.
+ //
+ ULONG_PTR nativeOffset;
+ pExInfo->m_DebuggerExState.GetDebuggerInterceptInfo(NULL, NULL, NULL, NULL, &nativeOffset, NULL);
+ context.Eip = GetControlPC(regs) - (pCf->GetRelOffset() - nativeOffset);
+
+ //
+ // Finally we need to get the correct Esp for this nested level
+ //
+
+ context.Esp = pCf->GetCodeManager()->GetAmbientSP(regs,
+ pCf->GetCodeInfo(),
+ nativeOffset,
+ pData->dHandler,
+ pCf->GetCodeManState()
+ );
+ //
+ // In case we see unknown FS:[0] handlers we delay the interception point until we reach the handler that protects the interception point.
+ // This way we have both FS:[0] handlers being poped up by RtlUnwind and managed capital F Frames being unwinded by managed stackwalker.
+ //
+ BOOL fCheckForUnknownHandler = TRUE;
+ if (PopNestedExceptionRecords((LPVOID)(size_t)context.Esp, fCheckForUnknownHandler))
+ {
+ // Let ClrDebuggerDoUnwindAndIntercept RtlUnwind continue to unwind frames until we reach the handler protected by COMPlusNestedExceptionHandler.
+ pExInfo->m_InterceptionContext = context;
+ pExInfo->m_ValidInterceptionContext = TRUE;
+ STRESS_LOG0(LF_EH|LF_CORDB, LL_INFO100, "COMPlusUnwindCallback: Skip interception until unwinding reaches the actual handler protected by COMPlusNestedExceptionHandler\n");
+ }
+ else
+ {
+ //
+ // Pop off all the Exception information up to this point in the stack
+ //
+ UnwindExceptionTrackerAndResumeInInterceptionFrame(pExInfo, &context);
+ }
+ return SWA_ABORT;
+#endif // DEBUGGING_SUPPORTED
+} // StackWalkAction COMPlusUnwindCallback ()
+#if defined(_MSC_VER)
+#pragma warning(pop)
+#endif
+
+#if defined(_MSC_VER)
+#pragma warning(push)
+#pragma warning (disable : 4740) // There is inline asm code in this function, which disables
+ // global optimizations.
+#pragma warning (disable : 4731)
+#endif
+void ResumeAtJitEH(CrawlFrame* pCf,
+ BYTE* startPC,
+ EE_ILEXCEPTION_CLAUSE *EHClausePtr,
+ DWORD nestingLevel,
+ Thread *pThread,
+ BOOL unwindStack)
+{
+ // No dynamic contract here because this function doesn't return and destructors wouldn't be executed
+ WRAPPER_NO_CONTRACT;
+
+ EHContext context;
+
+ context.Setup(PCODE(startPC + EHClausePtr->HandlerStartPC), pCf->GetRegisterSet());
+
+ size_t * pShadowSP = NULL; // Write Esp to *pShadowSP before jumping to handler
+ size_t * pHandlerEnd = NULL;
+
+ OBJECTREF throwable = PossiblyUnwrapThrowable(pThread->GetThrowable(), pCf->GetAssembly());
+
+ pCf->GetCodeManager()->FixContext(ICodeManager::CATCH_CONTEXT,
+ &context,
+ pCf->GetCodeInfo(),
+ EHClausePtr->HandlerStartPC,
+ nestingLevel,
+ throwable,
+ pCf->GetCodeManState(),
+ &pShadowSP,
+ &pHandlerEnd);
+
+ if (pHandlerEnd)
+ {
+ *pHandlerEnd = EHClausePtr->HandlerEndPC;
+ }
+
+ // save esp so that endcatch can restore it (it always restores, so want correct value)
+ ExInfo* pExInfo = &(pThread->GetExceptionState()->m_currentExInfo);
+ pExInfo->m_dEsp = (LPVOID)context.GetSP();
+ LOG((LF_EH, LL_INFO1000, "ResumeAtJitEH: current m_dEsp set to %p\n", context.GetSP()));
+
+ PVOID dEsp = GetCurrentSP();
+
+ if (!unwindStack)
+ {
+ // If we don't want to unwind the stack, then the guard page had better not be gone!
+ _ASSERTE(pThread->DetermineIfGuardPagePresent());
+
+ // so down below won't really update esp
+ context.SetSP(dEsp);
+ pExInfo->m_pShadowSP = pShadowSP; // so that endcatch can zero it back
+
+ if (pShadowSP)
+ {
+ *pShadowSP = (size_t)dEsp;
+ }
+ }
+ else
+ {
+ // so shadow SP has the real SP as we are going to unwind the stack
+ dEsp = (LPVOID)context.GetSP();
+
+ // BEGIN: pExInfo->UnwindExInfo(dEsp);
+ ExInfo *pPrevNestedInfo = pExInfo->m_pPrevNestedInfo;
+
+ while (pPrevNestedInfo && pPrevNestedInfo->m_StackAddress < dEsp)
+ {
+ LOG((LF_EH, LL_INFO1000, "ResumeAtJitEH: popping nested ExInfo at 0x%p\n", pPrevNestedInfo->m_StackAddress));
+
+ pPrevNestedInfo->DestroyExceptionHandle();
+ pPrevNestedInfo->m_StackTraceInfo.FreeStackTrace();
+
+#ifdef DEBUGGING_SUPPORTED
+ if (g_pDebugInterface != NULL)
+ {
+ g_pDebugInterface->DeleteInterceptContext(pPrevNestedInfo->m_DebuggerExState.GetDebuggerInterceptContext());
+ }
+#endif // DEBUGGING_SUPPORTED
+
+ pPrevNestedInfo = pPrevNestedInfo->m_pPrevNestedInfo;
+ }
+
+ pExInfo->m_pPrevNestedInfo = pPrevNestedInfo;
+
+ _ASSERTE(pExInfo->m_pPrevNestedInfo == 0 || pExInfo->m_pPrevNestedInfo->m_StackAddress >= dEsp);
+
+ // Before we unwind the SEH records, get the Frame from the top-most nested exception record.
+ Frame* pNestedFrame = GetCurrFrame(FindNestedEstablisherFrame(GetCurrentSEHRecord()));
+
+ PopNestedExceptionRecords((LPVOID)(size_t)dEsp);
+
+ EXCEPTION_REGISTRATION_RECORD* pNewBottomMostHandler = GetCurrentSEHRecord();
+
+ pExInfo->m_pShadowSP = pShadowSP;
+
+ // The context and exception record are no longer any good.
+ _ASSERTE(pExInfo->m_pContext < dEsp); // It must be off the top of the stack.
+ pExInfo->m_pContext = 0; // Whack it.
+ pExInfo->m_pExceptionRecord = 0;
+ pExInfo->m_pExceptionPointers = 0;
+
+ // We're going to put one nested record back on the stack before we resume. This is
+ // where it goes.
+ NestedHandlerExRecord *pNestedHandlerExRecord = (NestedHandlerExRecord*)((BYTE*)dEsp - ALIGN_UP(sizeof(NestedHandlerExRecord), STACK_ALIGN_SIZE));
+
+ // The point of no return. The next statement starts scribbling on the stack. It's
+ // deep enough that we won't hit our own locals. (That's important, 'cuz we're still
+ // using them.)
+ //
+ _ASSERTE(dEsp > &pCf);
+ pNestedHandlerExRecord->m_handlerInfo.m_hThrowable=NULL; // This is random memory. Handle
+ // must be initialized to null before
+ // calling Init(), as Init() will try
+ // to free any old handle.
+ pNestedHandlerExRecord->Init((PEXCEPTION_ROUTINE)COMPlusNestedExceptionHandler, pNestedFrame);
+
+ INSTALL_EXCEPTION_HANDLING_RECORD(&(pNestedHandlerExRecord->m_ExReg));
+
+ context.SetSP(pNestedHandlerExRecord);
+
+ // We might have moved the bottommost handler. The nested record itself is never
+ // the bottom most handler -- it's pushed afte the fact. So we have to make the
+ // bottom-most handler the one BEFORE the nested record.
+ if (pExInfo->m_pBottomMostHandler < pNewBottomMostHandler)
+ {
+ STRESS_LOG3(LF_EH, LL_INFO10000, "ResumeAtJitEH: setting ExInfo:0x%p m_pBottomMostHandler from 0x%p to 0x%p\n",
+ pExInfo, pExInfo->m_pBottomMostHandler, pNewBottomMostHandler);
+ pExInfo->m_pBottomMostHandler = pNewBottomMostHandler;
+ }
+
+ if (pShadowSP)
+ {
+ *pShadowSP = context.GetSP();
+ }
+ }
+
+ STRESS_LOG3(LF_EH, LL_INFO100, "ResumeAtJitEH: resuming at EIP = %p ESP = %p EBP = %p\n",
+ context.Eip, context.GetSP(), context.GetFP());
+
+#ifdef STACK_GUARDS_DEBUG
+ // We are transitioning back to managed code, so ensure that we are in
+ // SO-tolerant mode before we do so.
+ RestoreSOToleranceState();
+#endif
+
+ // we want this to happen as late as possible but certainly after the notification
+ // that the handle for the current ExInfo has been freed has been delivered
+ pExInfo->m_EHClauseInfo.SetManagedCodeEntered(TRUE);
+
+ ETW::ExceptionLog::ExceptionCatchBegin(pCf->GetCodeInfo()->GetMethodDesc(), (PVOID)pCf->GetCodeInfo()->GetStartAddress());
+
+ ResumeAtJitEHHelper(&context);
+ UNREACHABLE_MSG("Should never return from ResumeAtJitEHHelper!");
+
+ // we do not set pExInfo->m_EHClauseInfo.m_fManagedCodeEntered = FALSE here,
+ // that happens when the catch clause calls back to COMPlusEndCatch
+ // we don't return to this point so it would be moot (see unreachable_msg above)
+
+}
+#if defined(_MSC_VER)
+#pragma warning(pop)
+#endif
+
+// Must be in a separate function because INSTALL_COMPLUS_EXCEPTION_HANDLER has a filter
+int CallJitEHFilterWorker(size_t *pShadowSP, EHContext *pContext)
+{
+ STATIC_CONTRACT_THROWS;
+ STATIC_CONTRACT_GC_TRIGGERS;
+ STATIC_CONTRACT_MODE_COOPERATIVE;
+ STATIC_CONTRACT_SO_INTOLERANT;
+
+ int retVal = EXCEPTION_CONTINUE_SEARCH;
+
+ BEGIN_CALL_TO_MANAGED();
+
+ retVal = CallJitEHFilterHelper(pShadowSP, pContext);
+
+ END_CALL_TO_MANAGED();
+
+ return retVal;
+}
+
+int CallJitEHFilter(CrawlFrame* pCf, BYTE* startPC, EE_ILEXCEPTION_CLAUSE *EHClausePtr, DWORD nestingLevel, OBJECTREF thrownObj)
+{
+ STATIC_CONTRACT_THROWS;
+ STATIC_CONTRACT_GC_TRIGGERS;
+ STATIC_CONTRACT_MODE_COOPERATIVE;
+
+ int retVal = EXCEPTION_CONTINUE_SEARCH;
+ size_t * pShadowSP = NULL;
+ EHContext context;
+
+ context.Setup(PCODE(startPC + EHClausePtr->FilterOffset), pCf->GetRegisterSet());
+
+ size_t * pEndFilter = NULL; // Write
+ pCf->GetCodeManager()->FixContext(ICodeManager::FILTER_CONTEXT, &context, pCf->GetCodeInfo(),
+ EHClausePtr->FilterOffset, nestingLevel, thrownObj, pCf->GetCodeManState(),
+ &pShadowSP, &pEndFilter);
+
+ // End of the filter is the same as start of handler
+ if (pEndFilter)
+ {
+ *pEndFilter = EHClausePtr->HandlerStartPC;
+ }
+
+ // ExceptionFilterFrame serves two purposes:
+ //
+ // 1. It serves as a frame that stops the managed search for handler
+ // if we fault in the filter. ThrowCallbackType.pTopFrame is going point
+ // to this frame during search for exception handler inside filter.
+ // The search for handler needs a frame to stop. If we had no frame here,
+ // the exceptions in filters would not be swallowed correctly since we would
+ // walk past the EX_TRY/EX_CATCH block in COMPlusThrowCallbackHelper.
+ //
+ // 2. It allows setting of SHADOW_SP_FILTER_DONE flag in UnwindFrames()
+ // if we fault in the filter. We have to set this flag together with unwinding
+ // of the filter frame. Using a regular C++ holder to clear this flag here would cause
+ // GC holes. The stack would be in inconsistent state when we trigger gc just before
+ // returning from UnwindFrames.
+
+ FrameWithCookie<ExceptionFilterFrame> exceptionFilterFrame(pShadowSP);
+
+ ETW::ExceptionLog::ExceptionFilterBegin(pCf->GetCodeInfo()->GetMethodDesc(), (PVOID)pCf->GetCodeInfo()->GetStartAddress());
+
+ retVal = CallJitEHFilterWorker(pShadowSP, &context);
+
+ ETW::ExceptionLog::ExceptionFilterEnd();
+
+ exceptionFilterFrame.Pop();
+
+ return retVal;
+}
+
+void CallJitEHFinally(CrawlFrame* pCf, BYTE* startPC, EE_ILEXCEPTION_CLAUSE *EHClausePtr, DWORD nestingLevel)
+{
+ WRAPPER_NO_CONTRACT;
+
+ EHContext context;
+ context.Setup(PCODE(startPC + EHClausePtr->HandlerStartPC), pCf->GetRegisterSet());
+
+ size_t * pShadowSP = NULL; // Write Esp to *pShadowSP before jumping to handler
+
+ size_t * pFinallyEnd = NULL;
+ pCf->GetCodeManager()->FixContext(
+ ICodeManager::FINALLY_CONTEXT, &context, pCf->GetCodeInfo(),
+ EHClausePtr->HandlerStartPC, nestingLevel, ObjectToOBJECTREF((Object *) NULL), pCf->GetCodeManState(),
+ &pShadowSP, &pFinallyEnd);
+
+ if (pFinallyEnd)
+ {
+ *pFinallyEnd = EHClausePtr->HandlerEndPC;
+ }
+
+ ETW::ExceptionLog::ExceptionFinallyBegin(pCf->GetCodeInfo()->GetMethodDesc(), (PVOID)pCf->GetCodeInfo()->GetStartAddress());
+
+ CallJitEHFinallyHelper(pShadowSP, &context);
+
+ ETW::ExceptionLog::ExceptionFinallyEnd();
+
+ //
+ // Update the registers using new context
+ //
+ // This is necessary to reflect GC pointer changes during the middle of a unwind inside a
+ // finally clause, because:
+ // 1. GC won't see the part of stack inside try (which has thrown an exception) that is already
+ // unwinded and thus GC won't update GC pointers for this portion of the stack, but rather the
+ // call stack in finally.
+ // 2. upon return of finally, the unwind process continues and unwinds stack based on the part
+ // of stack inside try and won't see the updated values in finally.
+ // As a result, we need to manually update the context using register values upon return of finally
+ //
+ // Note that we only update the registers for finally clause because
+ // 1. For filter handlers, stack walker is able to see the whole stack (including the try part)
+ // with the help of ExceptionFilterFrame as filter handlers are called in first pass
+ // 2. For catch handlers, the current unwinding is already finished
+ //
+ context.UpdateFrame(pCf->GetRegisterSet());
+
+ // This does not need to be guarded by a holder because the frame is dead if an exception gets thrown. Filters are different
+ // since they are run in the first pass, so we must update the shadowSP reset in CallJitEHFilter.
+ if (pShadowSP) {
+ *pShadowSP = 0; // reset the shadowSP to 0
+ }
+}
+#if defined(_MSC_VER)
+#pragma warning (default : 4731)
+#endif
+
+//=====================================================================
+// *********************************************************************
+BOOL ComPlusFrameSEH(EXCEPTION_REGISTRATION_RECORD* pEHR)
+{
+ LIMITED_METHOD_CONTRACT;
+
+ return ((LPVOID)pEHR->Handler == (LPVOID)COMPlusFrameHandler || (LPVOID)pEHR->Handler == (LPVOID)COMPlusNestedExceptionHandler);
+}
+
+
+//
+//-------------------------------------------------------------------------
+// This is installed when we call COMPlusFrameHandler to provide a bound to
+// determine when are within a nested exception
+//-------------------------------------------------------------------------
+EXCEPTION_HANDLER_IMPL(COMPlusNestedExceptionHandler)
+{
+ WRAPPER_NO_CONTRACT;
+
+ if (pExceptionRecord->ExceptionFlags & (EXCEPTION_UNWINDING | EXCEPTION_EXIT_UNWIND))
+ {
+ LOG((LF_EH, LL_INFO100, " COMPlusNestedHandler(unwind) with %x at %x\n", pExceptionRecord->ExceptionCode,
+ pContext ? GetIP(pContext) : 0));
+
+
+ // We're unwinding past a nested exception record, which means that we've thrown
+ // a new exception out of a region in which we're handling a previous one. The
+ // previous exception is overridden -- and needs to be unwound.
+
+ // The preceding is ALMOST true. There is one more case, where we use setjmp/longjmp
+ // from withing a nested handler. We won't have a nested exception in that case -- just
+ // the unwind.
+
+ Thread* pThread = GetThread();
+ _ASSERTE(pThread);
+ ExInfo* pExInfo = &(pThread->GetExceptionState()->m_currentExInfo);
+ ExInfo* pPrevNestedInfo = pExInfo->m_pPrevNestedInfo;
+
+ if (pPrevNestedInfo == &((NestedHandlerExRecord*)pEstablisherFrame)->m_handlerInfo)
+ {
+ _ASSERTE(pPrevNestedInfo);
+
+ LOG((LF_EH, LL_INFO100, "COMPlusNestedExceptionHandler: PopExInfo(): popping nested ExInfo at 0x%p\n", pPrevNestedInfo));
+
+ pPrevNestedInfo->DestroyExceptionHandle();
+ pPrevNestedInfo->m_StackTraceInfo.FreeStackTrace();
+
+#ifdef DEBUGGING_SUPPORTED
+ if (g_pDebugInterface != NULL)
+ {
+ g_pDebugInterface->DeleteInterceptContext(pPrevNestedInfo->m_DebuggerExState.GetDebuggerInterceptContext());
+ }
+#endif // DEBUGGING_SUPPORTED
+
+ pExInfo->m_pPrevNestedInfo = pPrevNestedInfo->m_pPrevNestedInfo;
+
+ } else {
+ // The whacky setjmp/longjmp case. Nothing to do.
+ }
+
+ } else {
+ LOG((LF_EH, LL_INFO100, " InCOMPlusNestedHandler with %x at %x\n", pExceptionRecord->ExceptionCode,
+ pContext ? GetIP(pContext) : 0));
+ }
+
+
+ // There is a nasty "gotcha" in the way exception unwinding, finally's, and nested exceptions
+ // interact. Here's the scenario ... it involves two exceptions, one normal one, and one
+ // raised in a finally.
+ //
+ // The first exception occurs, and is caught by some handler way up the stack. That handler
+ // calls RtlUnwind -- and handlers that didn't catch this first exception are called again, with
+ // the UNWIND flag set. If, one of the handlers throws an exception during
+ // unwind (like, a throw from a finally) -- then that same handler is not called during
+ // the unwind pass of the second exception. [ASIDE: It is called on first-pass.]
+ //
+ // What that means is -- the COMPlusExceptionHandler, can't count on unwinding itself correctly
+ // if an exception is thrown from a finally. Instead, it relies on the NestedExceptionHandler
+ // that it pushes for this.
+ //
+
+ EXCEPTION_DISPOSITION retval = EXCEPTION_HANDLER_FWD(COMPlusFrameHandler);
+ LOG((LF_EH, LL_INFO100, "Leaving COMPlusNestedExceptionHandler with %d\n", retval));
+ return retval;
+}
+
+EXCEPTION_REGISTRATION_RECORD *FindNestedEstablisherFrame(EXCEPTION_REGISTRATION_RECORD *pEstablisherFrame)
+{
+ LIMITED_METHOD_CONTRACT;
+
+ while (pEstablisherFrame->Handler != (PEXCEPTION_ROUTINE)COMPlusNestedExceptionHandler) {
+ pEstablisherFrame = pEstablisherFrame->Next;
+ _ASSERTE(pEstablisherFrame != EXCEPTION_CHAIN_END); // should always find one
+ }
+ return pEstablisherFrame;
+}
+
+EXCEPTION_HANDLER_IMPL(FastNExportExceptHandler)
+{
+ WRAPPER_NO_CONTRACT;
+
+ // Most of our logic is in commin with COMPlusFrameHandler.
+ EXCEPTION_DISPOSITION retval = EXCEPTION_HANDLER_FWD(COMPlusFrameHandler);
+
+#ifdef _DEBUG
+ // If the exception is escaping the last CLR personality routine on the stack,
+ // then state a flag on the thread to indicate so.
+ if (retval == ExceptionContinueSearch)
+ {
+ SetReversePInvokeEscapingUnhandledExceptionStatus(IS_UNWINDING(pExceptionRecord->ExceptionFlags), pEstablisherFrame);
+ }
+#endif // _DEBUG
+
+ return retval;
+}
+
+
+// Just like a regular NExport handler -- except it pops an extra frame on unwind. A handler
+// like this is needed by the COMMethodStubProlog code. It first pushes a frame -- and then
+// pushes a handler. When we unwind, we need to pop the extra frame to avoid corrupting the
+// frame chain in the event of an unmanaged catcher.
+//
+EXCEPTION_HANDLER_IMPL(UMThunkPrestubHandler)
+{
+ // @todo: we'd like to have a dynamic contract here, but there's a problem. (Bug 129180) Enter on the CRST used
+ // in HandleManagedFault leaves the no-trigger count incremented. The destructor of this contract will restore
+ // it to zero, then when we leave the CRST in LinkFrameAndThrow, we assert because we're trying to decrement the
+ // gc-trigger count down past zero. The solution is to fix what we're doing with this CRST. </TODO>
+ STATIC_CONTRACT_THROWS; // COMPlusFrameHandler throws
+ STATIC_CONTRACT_GC_TRIGGERS;
+ STATIC_CONTRACT_MODE_ANY;
+
+ EXCEPTION_DISPOSITION retval = ExceptionContinueSearch;
+
+ BEGIN_CONTRACT_VIOLATION(SOToleranceViolation);
+
+ // We must forward to the COMPlusFrameHandler. This will unwind the Frame Chain up to here, and also leave the
+ // preemptive GC mode set correctly.
+ retval = EXCEPTION_HANDLER_FWD(COMPlusFrameHandler);
+
+#ifdef _DEBUG
+ // If the exception is escaping the last CLR personality routine on the stack,
+ // then state a flag on the thread to indicate so.
+ if (retval == ExceptionContinueSearch)
+ {
+ SetReversePInvokeEscapingUnhandledExceptionStatus(IS_UNWINDING(pExceptionRecord->ExceptionFlags), pEstablisherFrame);
+ }
+#endif // _DEBUG
+
+ if (IS_UNWINDING(pExceptionRecord->ExceptionFlags))
+ {
+ // Pops an extra frame on unwind.
+
+ GCX_COOP(); // Must be cooperative to modify frame chain.
+
+ Thread *pThread = GetThread();
+ _ASSERTE(pThread);
+ Frame *pFrame = pThread->GetFrame();
+ pFrame->ExceptionUnwind();
+ pFrame->Pop(pThread);
+ }
+
+ END_CONTRACT_VIOLATION;
+
+ return retval;
+}
+
+LONG CLRNoCatchHandler(EXCEPTION_POINTERS* pExceptionInfo, PVOID pv)
+{
+ WRAPPER_NO_CONTRACT;
+ STATIC_CONTRACT_ENTRY_POINT;
+
+ LONG result = EXCEPTION_CONTINUE_SEARCH;
+
+ // This function can be called during the handling of a SO
+ //BEGIN_ENTRYPOINT_VOIDRET;
+
+ result = CLRVectoredExceptionHandler(pExceptionInfo);
+
+ if (EXCEPTION_EXECUTE_HANDLER == result)
+ {
+ result = EXCEPTION_CONTINUE_SEARCH;
+ }
+
+ //END_ENTRYPOINT_VOIDRET;
+
+ return result;
+}
+
+#ifdef FEATURE_COMINTEROP
+// The reverse COM interop path needs to be sure to pop the ComMethodFrame that is pushed, but we do not want
+// to have an additional FS:0 handler between the COM callsite and the call into managed. So we push this
+// FS:0 handler, which will defer to the usual COMPlusFrameHandler and then perform the cleanup of the
+// ComMethodFrame, if needed.
+EXCEPTION_HANDLER_IMPL(COMPlusFrameHandlerRevCom)
+{
+ STATIC_CONTRACT_THROWS;
+ STATIC_CONTRACT_GC_TRIGGERS;
+ STATIC_CONTRACT_MODE_ANY;
+
+ // Defer to COMPlusFrameHandler
+ EXCEPTION_DISPOSITION result = EXCEPTION_HANDLER_FWD(COMPlusFrameHandler);
+
+ if (pExceptionRecord->ExceptionFlags & (EXCEPTION_UNWINDING | EXCEPTION_EXIT_UNWIND))
+ {
+ // Do cleanup as needed
+ ComMethodFrame::DoSecondPassHandlerCleanup(GetCurrFrame(pEstablisherFrame));
+ }
+
+ return result;
+}
+#endif // FEATURE_COMINTEROP
+
+
+// Returns TRUE if caller should resume execution.
+BOOL
+AdjustContextForVirtualStub(
+ EXCEPTION_RECORD *pExceptionRecord,
+ CONTEXT *pContext)
+{
+ LIMITED_METHOD_CONTRACT;
+
+ Thread * pThread = GetThread();
+
+ // We may not have a managed thread object. Example is an AV on the helper thread.
+ // (perhaps during StubManager::IsStub)
+ if (pThread == NULL)
+ {
+ return FALSE;
+ }
+
+ PCODE f_IP = GetIP(pContext);
+
+ VirtualCallStubManager::StubKind sk;
+ /* VirtualCallStubManager *pMgr = */ VirtualCallStubManager::FindStubManager(f_IP, &sk);
+
+ if (sk == VirtualCallStubManager::SK_DISPATCH)
+ {
+ if (*PTR_WORD(f_IP) != X86_INSTR_CMP_IND_ECX_IMM32)
+ {
+ _ASSERTE(!"AV in DispatchStub at unknown instruction");
+ return FALSE;
+ }
+ }
+ else
+ if (sk == VirtualCallStubManager::SK_RESOLVE)
+ {
+ if (*PTR_WORD(f_IP) != X86_INSTR_MOV_EAX_ECX_IND)
+ {
+ _ASSERTE(!"AV in ResolveStub at unknown instruction");
+ return FALSE;
+ }
+
+ SetSP(pContext, dac_cast<PCODE>(dac_cast<PTR_BYTE>(GetSP(pContext)) + sizeof(void*))); // rollback push eax
+ }
+ else
+ {
+ return FALSE;
+ }
+
+ PCODE callsite = GetAdjustedCallAddress(*dac_cast<PTR_PCODE>(GetSP(pContext)));
+ pExceptionRecord->ExceptionAddress = (PVOID)callsite;
+ SetIP(pContext, callsite);
+
+ // put ESP back to what it was before the call.
+ SetSP(pContext, dac_cast<PCODE>(dac_cast<PTR_BYTE>(GetSP(pContext)) + sizeof(void*)));
+
+ return TRUE;
+}
+
+#endif // !DACCESS_COMPILE