From dfc9860677c000f7bbf493a5f67f4075b5191e8d Mon Sep 17 00:00:00 2001 From: Filip Navara Date: Tue, 1 Apr 2025 22:47:00 +0200 Subject: [PATCH 1/6] Delete dead code in ExceptionTracker --- src/coreclr/vm/exceptionhandling.cpp | 2355 +------------------------- src/coreclr/vm/exceptionhandling.h | 68 - 2 files changed, 47 insertions(+), 2376 deletions(-) diff --git a/src/coreclr/vm/exceptionhandling.cpp b/src/coreclr/vm/exceptionhandling.cpp index 074ce71d0a2b42..5568604cea03b2 100644 --- a/src/coreclr/vm/exceptionhandling.cpp +++ b/src/coreclr/vm/exceptionhandling.cpp @@ -1063,341 +1063,6 @@ bool FixNonvolatileRegisters(UINT_PTR uOriginalSP, return true; } - - - -// static -void ExceptionTracker::InitializeCrawlFrameForExplicitFrame(CrawlFrame* pcfThisFrame, Frame* pFrame, MethodDesc *pMD) -{ - CONTRACTL - { - MODE_ANY; - NOTHROW; - GC_NOTRIGGER; - - PRECONDITION(pFrame != FRAME_TOP); - } - CONTRACTL_END; - - // Clear various flags - pcfThisFrame->isFrameless = false; - pcfThisFrame->isInterrupted = false; - pcfThisFrame->hasFaulted = false; - pcfThisFrame->isIPadjusted = false; - - pcfThisFrame->pFrame = pFrame; - pcfThisFrame->pFunc = pFrame->GetFunction(); - - if (pFrame->GetFrameIdentifier() == FrameIdentifier::InlinedCallFrame && - !InlinedCallFrame::FrameHasActiveCall(pFrame)) - { - // Inactive ICFs in IL stubs contain the true interop MethodDesc which must be - // reported in the stack trace. - if (pMD->IsILStub() && pMD->AsDynamicMethodDesc()->HasMDContextArg()) - { - // Report interop MethodDesc - pcfThisFrame->pFunc = ((InlinedCallFrame *)pFrame)->GetActualInteropMethodDesc(); - _ASSERTE(pcfThisFrame->pFunc != NULL); - _ASSERTE(pcfThisFrame->pFunc->SanityCheck()); - } - } - - pcfThisFrame->pFirstGSCookie = NULL; - pcfThisFrame->pCurGSCookie = NULL; -} - -// This method will initialize the RegDisplay in the CrawlFrame with the correct state for current and caller context -// See the long description of contexts and their validity in ExceptionTracker::InitializeCrawlFrame for details. -void ExceptionTracker::InitializeCurrentContextForCrawlFrame(CrawlFrame* pcfThisFrame, PT_DISPATCHER_CONTEXT pDispatcherContext, StackFrame sfEstablisherFrame) -{ - CONTRACTL - { - MODE_ANY; - NOTHROW; - GC_NOTRIGGER; - PRECONDITION(IsInFirstPass()); - } - CONTRACTL_END; - - if (IsInFirstPass()) - { - REGDISPLAY *pRD = pcfThisFrame->pRD; - -#ifndef USE_CURRENT_CONTEXT_IN_FILTER - INDEBUG(memset(pRD->pCurrentContext, 0xCC, sizeof(*(pRD->pCurrentContext)))); - // Ensure that clients can tell the current context isn't valid. - SetIP(pRD->pCurrentContext, 0); -#else // !USE_CURRENT_CONTEXT_IN_FILTER - RestoreNonvolatileRegisters(pRD->pCurrentContext, pDispatcherContext->CurrentNonVolatileContextRecord); - RestoreNonvolatileRegisterPointers(pRD->pCurrentContextPointers, pDispatcherContext->CurrentNonVolatileContextRecord); -#endif // USE_CURRENT_CONTEXT_IN_FILTER - - *(pRD->pCallerContext) = *(pDispatcherContext->ContextRecord); - pRD->IsCallerContextValid = TRUE; - - pRD->SP = sfEstablisherFrame.SP; - pRD->ControlPC = pDispatcherContext->ControlPc; - -#ifdef ESTABLISHER_FRAME_ADDRESS_IS_CALLER_SP - pcfThisFrame->pRD->IsCallerSPValid = TRUE; - - // Assert our first pass assumptions for the Arm/Arm64 - _ASSERTE(sfEstablisherFrame.SP == GetSP(pDispatcherContext->ContextRecord)); -#endif // ESTABLISHER_FRAME_ADDRESS_IS_CALLER_SP - - } - - EH_LOG((LL_INFO100, "ExceptionTracker::InitializeCurrentContextForCrawlFrame: DispatcherContext->ControlPC = %p; IP in DispatcherContext->ContextRecord = %p.\n", - pDispatcherContext->ControlPc, GetIP(pDispatcherContext->ContextRecord))); -} - -// static -void ExceptionTracker::InitializeCrawlFrame(CrawlFrame* pcfThisFrame, Thread* pThread, StackFrame sf, REGDISPLAY* pRD, - PDISPATCHER_CONTEXT pDispatcherContext, DWORD_PTR ControlPCForEHSearch, - UINT_PTR* puMethodStartPC, - ExceptionTracker *pCurrentTracker) -{ - CONTRACTL - { - MODE_ANY; - NOTHROW; - GC_NOTRIGGER; - } - CONTRACTL_END; - - pcfThisFrame->pRD = pRD; - - // Clear various flags - pcfThisFrame->pFunc = NULL; - pcfThisFrame->isInterrupted = false; - pcfThisFrame->hasFaulted = false; - pcfThisFrame->isIPadjusted = false; - - // Initialize the RegDisplay from DC->ContextRecord. DC->ControlPC always contains the IP - // in the frame for which the personality routine was invoked. - // - // - // - // During 1st pass, DC->ContextRecord contains the context of the caller of the frame for which personality - // routine was invoked. On the other hand, in the 2nd pass, it contains the context of the frame for which - // personality routine was invoked. - // - // - // - // - // - // In the first pass on ARM & ARM64: - // - // 1) EstablisherFrame (passed as 'sf' to this method) represents the SP at the time - // the current managed method was invoked and thus, is the SP of the caller. This is - // the value of DispatcherContext->EstablisherFrame as well. - // 2) DispatcherContext->ControlPC is the pc in the current managed method for which personality - // routine has been invoked. - // 3) DispatcherContext->ContextRecord contains the context record of the caller (and thus, IP - // in the caller). Most of the times, these values will be distinct. However, recursion - // may result in them being the same (case "run2" of baseservices\Regression\V1\Threads\functional\CS_TryFinally.exe - // is an example). In such a case, we ensure that EstablisherFrame value is the same as - // the SP in DispatcherContext->ContextRecord (which is (1) above). - // - // In second pass on ARM & ARM64: - // - // 1) EstablisherFrame (passed as 'sf' to this method) represents the SP at the time - // the current managed method was invoked and thus, is the SP of the caller. This is - // the value of DispatcherContext->EstablisherFrame as well. - // 2) DispatcherContext->ControlPC is the pc in the current managed method for which personality - // routine has been invoked. - // 3) DispatcherContext->ContextRecord contains the context record of the current managed method - // for which the personality routine is invoked. - // - // - pThread->InitRegDisplay(pcfThisFrame->pRD, pDispatcherContext->ContextRecord, true); - - bool fAdjustRegdisplayControlPC = false; - - // The "if" check below is trying to determine when we have a valid current context in DC->ContextRecord and whether, or not, - // RegDisplay needs to be fixed up to set SP and ControlPC to have the values for the current frame for which personality routine - // is invoked. - // - // We do this based upon the current pass for the exception tracker as this will also handle the case when current frame - // and its caller have the same return address (i.e. ControlPc). This can happen in cases when, due to certain JIT optimizations, the following callstack - // - // A -> B -> A -> C - // - // Could get transformed to the one below when B is inlined in the first (left-most) A resulting in: - // - // A -> A -> C - // - // In this case, during 1st pass, when personality routine is invoked for the second A, DC->ControlPc could have the same - // value as DC->ContextRecord->Rip even though the DC->ContextRecord actually represents caller context (of first A). - // As a result, we will not initialize the value of SP and controlPC in RegDisplay for the current frame (frame for - // which personality routine was invoked - second A in the optimized scenario above) resulting in frame specific lookup (e.g. - // GenericArgType) to happen incorrectly (against first A). - // - // Thus, we should always use the pass identification in ExceptionTracker to determine when we need to perform the fixup below. - if (pCurrentTracker->IsInFirstPass()) - { - pCurrentTracker->InitializeCurrentContextForCrawlFrame(pcfThisFrame, pDispatcherContext, sf); - } - else - { -#if defined(TARGET_ARM) || defined(TARGET_ARM64) || defined(TARGET_LOONGARCH64) || defined(TARGET_RISCV64) - // See the comment above the call to InitRegDisplay for this assertion. - _ASSERTE(pDispatcherContext->ControlPc == GetIP(pDispatcherContext->ContextRecord)); -#endif // TARGET_ARM || TARGET_ARM64 || TARGET_LOONGARCH64 || TARGET_RISCV64 - -#ifdef ESTABLISHER_FRAME_ADDRESS_IS_CALLER_SP - // Simply setup the callerSP during the second pass in the caller context. - // This is used in setting up the "EnclosingClauseCallerSP" in ExceptionTracker::ProcessManagedCallFrame - // when the termination handlers are invoked. - ::SetSP(pcfThisFrame->pRD->pCallerContext, sf.SP); - pcfThisFrame->pRD->IsCallerSPValid = TRUE; -#endif // ESTABLISHER_FRAME_ADDRESS_IS_CALLER_SP - } - -#ifdef ADJUST_PC_UNWOUND_TO_CALL - // Further below, we will adjust the ControlPC based upon whether we are at a callsite or not. - // We need to do this for "RegDisplay.ControlPC" field as well so that when data structures like - // EECodeInfo initialize themselves using this field, they will have the correct absolute value - // that is in sync with the "relOffset" we calculate below. - // - // However, we do this *only* when "ControlPCForEHSearch" is the same as "DispatcherContext->ControlPC", - // indicating we are not using the thread-abort reraise loop prevention logic. - // - if (pDispatcherContext->ControlPc == ControlPCForEHSearch) - { - // Since DispatcherContext->ControlPc is used to initialize the - // RegDisplay.ControlPC field, assert that it is the same - // as the ControlPC we are going to use to initialize the CrawlFrame - // with as well. - _ASSERTE(pcfThisFrame->pRD->ControlPC == ControlPCForEHSearch); - fAdjustRegdisplayControlPC = true; - - } -#endif // ADJUST_PC_UNWOUND_TO_CALL - -#if defined(TARGET_ARM) - // Remove the Thumb bit - ControlPCForEHSearch = ThumbCodeToDataPointer(ControlPCForEHSearch); -#endif - -#ifdef ADJUST_PC_UNWOUND_TO_CALL - // If the OS indicated that the IP is a callsite, then adjust the ControlPC by decrementing it - // by two. This is done because unwinding at callsite will make ControlPC point to the - // instruction post the callsite. If a protected region ends "at" the callsite, then - // not doing this adjustment will result in a one-off error that can result in us not finding - // a handler. - // - // For async exceptions (e.g. AV), this will be false. - // - // We decrement by two to be in accordance with how the kernel does as well. - if (pDispatcherContext->ControlPcIsUnwound) - { - ControlPCForEHSearch -= STACKWALK_CONTROLPC_ADJUST_OFFSET; - if (fAdjustRegdisplayControlPC == true) - { - // Once the check above is removed, the assignment below should - // be done unconditionally. - pcfThisFrame->pRD->ControlPC = ControlPCForEHSearch; - // On ARM & ARM64, the IP is either at the callsite (post the adjustment above) - // or at the instruction at which async exception took place. - pcfThisFrame->isIPadjusted = true; - } - } -#endif // ADJUST_PC_UNWOUND_TO_CALL - - pcfThisFrame->codeInfo.Init(ControlPCForEHSearch); - - if (pcfThisFrame->codeInfo.IsValid()) - { - pcfThisFrame->isFrameless = true; - pcfThisFrame->pFunc = pcfThisFrame->codeInfo.GetMethodDesc(); - - *puMethodStartPC = pcfThisFrame->codeInfo.GetStartAddress(); - } - else - { - pcfThisFrame->isFrameless = false; - pcfThisFrame->pFunc = NULL; - - *puMethodStartPC = 0; - } - - pcfThisFrame->pThread = pThread; - - Frame* pTopFrame = pThread->GetFrame(); - if (pcfThisFrame->isFrameless && (pcfThisFrame->isIPadjusted == false) && (pcfThisFrame->GetRelOffset() == 0)) - { - // If we are here, then either a hardware generated exception happened at the first instruction - // of a managed method an exception was thrown at that location. - // - // Adjusting IP in such a case will lead us into unknown code - it could be native code or some - // other JITted code. - // - // Hence, we will flag that the IP is already adjusted. - pcfThisFrame->isIPadjusted = true; - - EH_LOG((LL_INFO100, "ExceptionTracker::InitializeCrawlFrame: Exception at offset zero of the method (MethodDesc %p); setting IP as adjusted.\n", - pcfThisFrame->pFunc)); - } - - pcfThisFrame->pFirstGSCookie = NULL; - pcfThisFrame->pCurGSCookie = NULL; - - pcfThisFrame->isFilterFuncletCached = FALSE; -} - -bool ExceptionTracker::UpdateScannedStackRange(StackFrame sf, bool fIsFirstPass) -{ - CONTRACTL - { - // Since this function will modify the scanned stack range, which is also accessed during the GC stackwalk, - // we invoke it in COOP mode so that the access to this range is synchronized. - MODE_COOPERATIVE; - GC_TRIGGERS; - THROWS; - } - CONTRACTL_END; - - // - // collapse trackers if a nested exception passes a previous exception - // - - HandleNestedExceptionEscape(sf, fIsFirstPass); - - // - // update stack bounds - // - BOOL fUnwindingToFindResumeFrame = m_ExceptionFlags.UnwindingToFindResumeFrame(); - - if (m_ScannedStackRange.Contains(sf)) - { - // If we're unwinding to find the resume frame and we're examining the topmost previously scanned frame, - // then we can't ignore it because we could resume here due to an escaped nested exception. - if (!fUnwindingToFindResumeFrame || (m_ScannedStackRange.GetUpperBound() != sf)) - { - // been there, done that. - EH_LOG((LL_INFO100, " IGNOREFRAME: This frame has been processed already\n")); - return false; - } - } - else - { - if (sf < m_ScannedStackRange.GetLowerBound()) - { - m_ScannedStackRange.ExtendLowerBound(sf); - } - - if (sf > m_ScannedStackRange.GetUpperBound()) - { - m_ScannedStackRange.ExtendUpperBound(sf); - } - - DebugLogTrackerRanges(" C"); - } - - return true; -} - void CheckForRudeAbort(Thread* pThread, bool fIsFirstPass) { if (fIsFirstPass && pThread->IsRudeAbort()) @@ -1416,1627 +1081,66 @@ void CheckForRudeAbort(Thread* pThread, bool fIsFirstPass) } } -void ExceptionTracker::FirstPassIsComplete() -{ - m_ExceptionFlags.ResetUnwindingToFindResumeFrame(); - m_pSkipToParentFunctionMD = NULL; -} - -void ExceptionTracker::SecondPassIsComplete(MethodDesc* pMD, StackFrame sfResumeStackFrame) -{ - EH_LOG((LL_INFO100, " second pass unwind completed\n")); - - m_pMethodDescOfCatcher = pMD; - m_sfResumeStackFrame = sfResumeStackFrame; -} - -CLRUnwindStatus ExceptionTracker::ProcessOSExceptionNotification( - PEXCEPTION_RECORD pExceptionRecord, - PCONTEXT pContextRecord, - PDISPATCHER_CONTEXT pDispatcherContext, - DWORD dwExceptionFlags, - StackFrame sf, - Thread* pThread, - StackTraceState STState, - PVOID pICFSetAsLimitFrame) -{ - CONTRACTL - { - MODE_ANY; - GC_TRIGGERS; - THROWS; - } - CONTRACTL_END; - - CLRUnwindStatus status = UnwindPending; - - CrawlFrame cfThisFrame; - REGDISPLAY regdisp; - UINT_PTR uMethodStartPC; - UINT_PTR uCallerSP; - - DWORD_PTR ControlPc = pDispatcherContext->ControlPc; - - ExceptionTracker::InitializeCrawlFrame(&cfThisFrame, pThread, sf, ®disp, pDispatcherContext, ControlPc, &uMethodStartPC, this); - -#ifndef ESTABLISHER_FRAME_ADDRESS_IS_CALLER_SP - uCallerSP = EECodeManager::GetCallerSp(cfThisFrame.pRD); -#else // !ESTABLISHER_FRAME_ADDRESS_IS_CALLER_SP - uCallerSP = sf.SP; -#endif // ESTABLISHER_FRAME_ADDRESS_IS_CALLER_SP - - EH_LOG((LL_INFO100, "ProcessCrawlFrame: PSP: " FMT_ADDR " EstablisherFrame: " FMT_ADDR "\n", DBG_ADDR(uCallerSP), DBG_ADDR(sf.SP))); - - bool fIsFirstPass = !(dwExceptionFlags & EXCEPTION_UNWINDING); - bool fTargetUnwind = !!(dwExceptionFlags & EXCEPTION_TARGET_UNWIND); - - // If a thread abort was raised after a catch block's execution, we would have saved - // the index and EstablisherFrame of the EH clause corresponding to the handler that executed. - // Fetch that locally and reset the state against the thread if we are in the unwind pass. - // - // It should be kept in mind that by the virtue of copying the information below, we will - // have it available for the first frame seen during the unwind pass (which will be the - // frame where ThreadAbort was raised after the catch block) for us to skip any termination - // handlers that may be present prior to the EH clause whose index we saved. - DWORD dwTACatchHandlerClauseIndex = pThread->m_dwIndexClauseForCatch; - StackFrame sfEstablisherOfActualHandlerFrame = pThread->m_sfEstablisherOfActualHandlerFrame; - if (!fIsFirstPass) - { - pThread->m_dwIndexClauseForCatch = 0; - pThread->m_sfEstablisherOfActualHandlerFrame.Clear(); - } - - bool fProcessThisFrame = false; - bool fCrawlFrameIsDirty = false; - - // - // - // Refer to the detailed comment in ExceptionTracker::ProcessManagedCallFrame for more context. - // In summary, if we have reached the target of the unwind, then we need to fix CallerSP (for - // GC reference reporting) if we have been asked to. - // - // This will be done only when we reach the frame that is handling the exception. - // - // - if (fTargetUnwind && (m_fFixupCallerSPForGCReporting == true)) - { - m_fFixupCallerSPForGCReporting = false; - this->m_EnclosingClauseInfoForGCReporting.SetEnclosingClauseCallerSP(uCallerSP); - } - - // Refer to detailed comment below. - PTR_Frame pICFForUnwindTarget = NULL; - - CheckForRudeAbort(pThread, fIsFirstPass); - - bool fIsFrameLess = cfThisFrame.IsFrameless(); - GSCookie* pGSCookie = NULL; - bool fSetLastUnwoundEstablisherFrame = false; - - // - // process any frame since the last frame we've seen - // - { - GCX_COOP_THREAD_EXISTS(pThread); - - // UpdateScannedStackRange needs to be invoked in COOP mode since - // the stack range can also be accessed during GC stackwalk. - fProcessThisFrame = UpdateScannedStackRange(sf, fIsFirstPass); - - MethodDesc *pMD = cfThisFrame.GetFunction(); - - Frame* pFrame = GetLimitFrame(); // next frame to process - - while (((UINT_PTR)pFrame) < uCallerSP) - { - // InlinedCallFrames (ICF) are allocated, initialized and linked to the Frame chain - // by the code generated by the JIT for a method containing a PInvoke. - // - // On platforms where USE_PER_FRAME_PINVOKE_INIT is not defined, - // the JIT generates code that links in the ICF - // at the start of the method and unlinks it towards the method end. - // Thus, ICF is present on the Frame chain at any given point so long as the - // method containing the PInvoke is on the stack. - // - // Now, if the method containing ICF catches an exception, we will reset the Frame chain - // with the LimitFrame, that is computed below, after the catch handler returns. Since this - // computation is done relative to the CallerSP (on both X64 and ARM), we will end up - // removing the ICF from the Frame chain as that will always be below (stack growing down) - // the CallerSP since it lives in the stack space of the current managed frame. - // - // As a result, if there is another PInvoke call after the catch block, it will expect - // the ICF to be present and without one, execution will go south. - // - // To account for this ICF codegen difference, in the EH system we check if the current - // Frame is an ICF or not. If it is and lies inside the current managed method, we - // keep a reference to it and reset the LimitFrame to this saved reference before we - // return back to invoke the catch handler. - // - // Thus, if there is another PInvoke call post the catch handler, it will find ICF as expected. - // - // This is based upon the following assumptions: - // - // 1) There will be no other explicit Frame inserted above the ICF inside the - // managed method containing ICF. That is, ICF is the top-most explicit frame - // in the managed method (and thus, lies in the current managed frame). - // - // 2) There is only one ICF per managed method containing one (or more) PInvoke(s). - // - // 3) We only do this if the current frame is the one handling the exception. This is to - // address the scenario of keeping any ICF from frames lower in the stack active. - // - // 4) The ExceptionUnwind method of the ICF is a no-op. As noted above, we save a reference - // to the ICF and yet continue to process the frame chain. During unwind, this implies - // that we will end up invoking the ExceptionUnwind methods of all frames that lie - // below the caller SP of the managed frame handling the exception. And since the handling - // managed frame contains an ICF, it will be the topmost frame that will lie - // below the callerSP for which we will invoke ExceptionUnwind. - // - // Thus, ICF::ExceptionUnwind should not do anything significant. If any of these assumptions - // break, then the next best thing will be to make the JIT link/unlink the frame dynamically - // - // If the current method executing is from precompiled ReadyToRun code, each PInvoke is wrapped - // by calls to the JIT_PInvokeBegin and JIT_PInvokeEnd helpers, - // which push and pop the ICF to the current thread. The ICF is not - // linked during the method prolog, and unlinked at the epilog. - // In that case, we need to unlink the ICF during unwinding here. - // On platforms where USE_PER_FRAME_PINVOKE_INIT is defined, the JIT generates code that links in - // the ICF immediately before and after a PInvoke in non-IL-stubs, like ReadyToRun. - // See the usages for USE_PER_FRAME_PINVOKE_INIT for more information. - - if (fTargetUnwind && (pFrame->GetFrameIdentifier() == FrameIdentifier::InlinedCallFrame)) - { - PTR_InlinedCallFrame pICF = (PTR_InlinedCallFrame)pFrame; - // Does it live inside the current managed method? It will iff: - // - // 1) ICF address is higher than the current frame's SP (which we get from DispatcherContext), AND - // 2) ICF address is below callerSP. - if ((GetSP(pDispatcherContext->ContextRecord) < (TADDR)pICF) && - ((UINT_PTR)pICF < uCallerSP)) - { - pICFForUnwindTarget = pFrame; - - // When unwinding an exception in ReadyToRun, the JIT_PInvokeEnd helper which unlinks the ICF from - // the thread will be skipped. This is because unlike jitted code, each pinvoke is wrapped by calls - // to the JIT_PInvokeBegin and JIT_PInvokeEnd helpers, which push and pop the ICF on the thread. The - // ICF is not linked at the method prolog and unlined at the epilog when running R2R code. Since the - // JIT_PInvokeEnd helper will be skipped, we need to unlink the ICF here. If the executing method - // has another pinvoke, it will re-link the ICF again when the JIT_PInvokeBegin helper is called. - - TADDR returnAddress = ((InlinedCallFrame*)pFrame)->m_pCallerReturnAddress; -#ifdef USE_PER_FRAME_PINVOKE_INIT - // If we're setting up the frame for each P/Invoke for the given platform, - // then we do this for all P/Invokes except ones in IL stubs. - // IL stubs link the frame in for the whole stub, so if an exception is thrown during marshalling, - // the ICF will be on the frame chain and inactive. - if (returnAddress != (TADDR)NULL && !ExecutionManager::GetCodeMethodDesc(returnAddress)->IsILStub()) -#else - // If we aren't setting up the frame for each P/Invoke (instead setting up once per method), - // then ReadyToRun code is the only code using the per-P/Invoke logic. - if (ExecutionManager::IsReadyToRunCode(returnAddress)) -#endif - { - pICFForUnwindTarget = pICFForUnwindTarget->Next(); - } - } - } - - cfThisFrame.CheckGSCookies(); - - if (fProcessThisFrame) - { - ExceptionTracker::InitializeCrawlFrameForExplicitFrame(&cfThisFrame, pFrame, pMD); - fCrawlFrameIsDirty = true; - - status = ProcessExplicitFrame( - &cfThisFrame, - sf, - fIsFirstPass, - STState); - cfThisFrame.CheckGSCookies(); - } - - if (!fIsFirstPass) - { - // - // notify Frame of unwind - // - pFrame->ExceptionUnwind(); - - // If we have not yet set the initial explicit frame processed by this tracker, then - // set it now. - if (m_pInitialExplicitFrame == NULL) - { - m_pInitialExplicitFrame = pFrame; - } - } - - pFrame = pFrame->Next(); - m_pLimitFrame = pFrame; - - if (UnwindPending != status) - { - goto lExit; - } - } - - if (fCrawlFrameIsDirty) - { - // If crawlframe is dirty, it implies that it got modified as part of explicit frame processing. Thus, we shall - // reinitialize it here. - ExceptionTracker::InitializeCrawlFrame(&cfThisFrame, pThread, sf, ®disp, pDispatcherContext, ControlPc, &uMethodStartPC, this); - } - - if (fIsFrameLess) - { - pGSCookie = (GSCookie*)cfThisFrame.GetCodeManager()->GetGSCookieAddr(cfThisFrame.pRD, - &cfThisFrame.codeInfo, - 0 /* CodeManFlags */, - &cfThisFrame.codeManState); - if (pGSCookie) - { - // The following function call sets the GS cookie pointers and checks the cookie. - cfThisFrame.SetCurGSCookie(pGSCookie); - } - - status = HandleFunclets(&fProcessThisFrame, fIsFirstPass, - cfThisFrame.GetFunction(), cfThisFrame.IsFunclet(), sf); - } - - if ((!fIsFirstPass) && (!fProcessThisFrame)) - { - // If we are unwinding and not processing the current frame, it implies that - // this frame has been unwound for one of the following reasons: - // - // 1) We have already seen it due to nested exception processing, OR - // 2) We are skipping frames to find a funclet's parent and thus, its been already - // unwound. - // - // If the current frame is NOT the target of unwind, update the last unwound - // establisher frame. We don't do this for "target of unwind" since it has the catch handler, for a - // duplicate EH clause reported in the funclet, that needs to be invoked and thus, may have valid - // references to report for GC reporting. - // - // If we are not skipping the managed frame, then LastUnwoundEstablisherFrame will be updated later in this method, - // just before we return back to our caller. - if (!fTargetUnwind) - { - SetLastUnwoundEstablisherFrame(sf); - fSetLastUnwoundEstablisherFrame = true; - } - } - - // GCX_COOP_THREAD_EXISTS ends here and we may switch to preemp mode now (if applicable). - } - - // - // now process managed call frame if needed - // - if (fIsFrameLess) - { - if (fProcessThisFrame) - { - status = ProcessManagedCallFrame( - &cfThisFrame, - sf, - StackFrame::FromEstablisherFrame(pDispatcherContext->EstablisherFrame), - pExceptionRecord, - STState, - uMethodStartPC, - dwExceptionFlags, - dwTACatchHandlerClauseIndex, - sfEstablisherOfActualHandlerFrame); - - if (pGSCookie) - { - cfThisFrame.CheckGSCookies(); - } - } - - if (fTargetUnwind && (UnwindPending == status)) - { - SecondPassIsComplete(cfThisFrame.GetFunction(), sf); - status = SecondPassComplete; - } - } - -lExit: - - // If we are unwinding and have returned successfully from unwinding the frame, then mark it as the last unwound frame for the current - // exception. We don't do this if the frame is target of unwind (i.e. handling the exception) since catch block invocation may have references to be - // reported (if a GC happens during catch block invocation). - // - // If an exception escapes out of a funclet (this is only possible for fault/finally/catch clauses), then we will not return here. - // Since this implies that the funclet no longer has any valid references to report, we will need to set the LastUnwoundEstablisherFrame - // close to the point we detect the exception has escaped the funclet. This is done in ExceptionTracker::CallHandler and marks the - // frame that invoked (and thus, contained) the funclet as the LastUnwoundEstablisherFrame. - // - // Note: Do no add any GC triggering code between the return from ProcessManagedCallFrame and setting of the LastUnwoundEstablisherFrame - if ((!fIsFirstPass) && (!fTargetUnwind) && (!fSetLastUnwoundEstablisherFrame)) - { - GCX_COOP(); - SetLastUnwoundEstablisherFrame(sf); - } - - if (FirstPassComplete == status) - { - FirstPassIsComplete(); - } - - if (fTargetUnwind && (status == SecondPassComplete)) - { - // If we have got a ICF to set as the LimitFrame, do that now. - // The Frame chain is still intact and would be updated using - // the LimitFrame (done after the catch handler returns). - // - // NOTE: This should be done as the last thing before we return - // back to invoke the catch handler. - if (pICFForUnwindTarget != NULL) - { - m_pLimitFrame = pICFForUnwindTarget; - pICFSetAsLimitFrame = (PVOID)pICFForUnwindTarget; - } - - // Since second pass is complete and we have reached - // the frame containing the catch funclet, reset the enclosing - // clause SP for the catch funclet, if applicable, to be the CallerSP of the - // current frame. - // - // Refer to the detailed comment about this code - // in ExceptionTracker::ProcessManagedCallFrame. - if (m_fResetEnclosingClauseSPForCatchFunclet) - { -#ifdef ESTABLISHER_FRAME_ADDRESS_IS_CALLER_SP - // DispatcherContext->EstablisherFrame's value - // represents the CallerSP of the current frame. - UINT_PTR EnclosingClauseCallerSP = (UINT_PTR)pDispatcherContext->EstablisherFrame; -#else // ESTABLISHER_FRAME_ADDRESS_IS_CALLER_SP - // Extract the CallerSP from RegDisplay - REGDISPLAY *pRD = cfThisFrame.GetRegisterSet(); - _ASSERTE(pRD->IsCallerContextValid || pRD->IsCallerSPValid); - UINT_PTR EnclosingClauseCallerSP = (UINT_PTR)GetSP(pRD->pCallerContext); -#endif // !ESTABLISHER_FRAME_ADDRESS_IS_CALLER_SP - m_EnclosingClauseInfo = EnclosingClauseInfo(false, cfThisFrame.GetRelOffset(), EnclosingClauseCallerSP); - } - m_fResetEnclosingClauseSPForCatchFunclet = FALSE; - } - - // If we are unwinding and the exception was not caught in managed code and we have reached the - // topmost frame we saw in the first pass, then reset thread abort state if this is the last managed - // code personality routine on the stack. - if ((fIsFirstPass == false) && (this->GetTopmostStackFrameFromFirstPass() == sf) && (GetCatchToCallPC() == 0)) - { - ExceptionTracker::ResetThreadAbortStatus(pThread, &cfThisFrame, sf); - } - - // - // fill in the out parameter - // - return status; -} - -// static -void ExceptionTracker::DebugLogTrackerRanges(_In_z_ const char *pszTag) -{ -#ifdef _DEBUG - CONTRACTL - { - MODE_ANY; - GC_NOTRIGGER; - NOTHROW; - } - CONTRACTL_END; - - Thread* pThread = GetThreadNULLOk(); - ExceptionTrackerBase* pTracker = pThread ? pThread->GetExceptionState()->m_pCurrentTracker : NULL; - - int i = 0; - - while (pTracker) - { - EH_LOG((LL_INFO100, "%s:|%02d| %p: (%p %p) %s\n", pszTag, i, pTracker, pTracker->m_ScannedStackRange.GetLowerBound().SP, pTracker->m_ScannedStackRange.GetUpperBound().SP, - pTracker->IsInFirstPass() ? "1st pass" : "2nd pass" - )); - pTracker = pTracker->m_pPrevNestedInfo; - i++; - } -#endif // _DEBUG -} - - -bool ExceptionTracker::HandleNestedExceptionEscape(StackFrame sf, bool fIsFirstPass) -{ - CONTRACTL - { - // Since this function can modify the scanned stack range, which is also accessed during the GC stackwalk, - // we invoke it in COOP mode so that the access to this range is synchronized. - MODE_COOPERATIVE; - GC_NOTRIGGER; - NOTHROW; - } - CONTRACTL_END; - - bool fResult = false; - - DebugLogTrackerRanges(" A"); - - ExceptionTrackerBase* pPreviousTracker = m_pPrevNestedInfo; - - while (pPreviousTracker && pPreviousTracker->m_ScannedStackRange.IsSupersededBy(sf)) - { - // - // If the previous tracker (representing exception E1 and whose scanned stack range is superseded by the current frame) - // is in the first pass AND current tracker (representing exceptio E2) has not seen the current frame AND we are here, - // it implies that we had a nested exception while the previous tracker was in the first pass. - // - // This can happen in the following scenarios: - // - // 1) An exception escapes a managed filter (which are invoked in the first pass). However, - // that is not possible since any exception escaping them is swallowed by the runtime. - // If someone does longjmp from within the filter, then that is illegal and unsupported. - // - // 2) While processing an exception (E1), either us or native code caught it, triggering unwind. However, before the - // first managed frame was processed for unwind, another native frame (below the first managed frame on the stack) - // did a longjmp to go past us or raised another exception from one of their termination handlers. - // - // Thus, we will never get a chance to switch our tracker for E1 to 2nd pass (which would be done when - // ExceptionTracker::GetOrCreateTracker will be invoked for the first managed frame) since the longjmp, or the - // new-exception would result in a new tracker being setup. - // - // Below is an example of such a case that does longjmp - // ---------------------------------------------------- - // - // NativeA (does setjmp) -> ManagedFunc -> NativeB - // - // - // NativeB could be implemented as: - // - // __try { // raise exception } __finally { longjmp(jmp1, 1); } - // - // "jmp1" is the jmp_buf setup by NativeA by calling setjmp. - // - // ManagedFunc could be implemented as: - // - // try { - // try { NativeB(); } - // finally { Console.WriteLine("Finally in ManagedFunc"); } - // } - // catch(Exception ex} { Console.WriteLine("Caught"); } - // - // - // In case of nested exception, we combine the stack range (see below) since we have already seen those frames - // in the specified pass for the previous tracker. However, in the example above, the current tracker (in 2nd pass) - // has not see the frames which the previous tracker (which is in the first pass) has seen. - // - // On a similar note, the __finally in the example above could also do a "throw 1;". In such a case, we would expect - // that the catch in ManagedFunc would catch the exception (since "throw 1;" would be represented as SEHException in - // the runtime). However, during first pass, when the exception enters ManagedFunc, the current tracker would not have - // processed the ManagedFunc frame, while the previous tracker (for E1) would have. If we proceed to combine the stack - // ranges, we will omit examining the catch clause in ManagedFunc. - // - // Thus, we cannot combine the stack range yet and must let each frame, already scanned by the previous - // tracker, be also processed by the current (longjmp) tracker if not already done. - // - // Note: This is not a concern if the previous tracker (for exception E1) is in the second pass since any escaping exception (E2) - // would come out of a finally/fault funclet and the runtime's funclet skipping logic will deal with it correctly. - - if (pPreviousTracker->IsInFirstPass() && (!this->m_ScannedStackRange.Contains(sf))) - { - // Allow all stackframes seen by previous tracker to be seen by the current - // tracker as well. - if (sf <= pPreviousTracker->m_ScannedStackRange.GetUpperBound()) - { - EH_LOG((LL_INFO100, " - not updating current tracker bounds for escaped exception since\n")); - EH_LOG((LL_INFO100, " - active tracker (%p; %s) has not seen the current frame [", this, this->IsInFirstPass()?"FirstPass":"SecondPass")); - EH_LOG((LL_INFO100, " - SP = %p", sf.SP)); - EH_LOG((LL_INFO100, "]\n")); - EH_LOG((LL_INFO100, " - which the previous (%p) tracker has processed.\n", pPreviousTracker)); - return fResult; - } - } - - EH_LOG((LL_INFO100, " nested exception ESCAPED\n")); - EH_LOG((LL_INFO100, " - updating current tracker stack bounds\n")); - m_ScannedStackRange.CombineWith(sf, &pPreviousTracker->m_ScannedStackRange); - - // - // Only the topmost tracker can be in the first pass. - // - // (Except in the case where we have an exception thrown in a filter, - // which should never escape the filter, and thus, will never supersede - // the previous exception. This is why we cannot walk the entire list - // of trackers to assert that they're all in the right mode.) - // - // CONSISTENCY_CHECK(!pPreviousTracker->IsInFirstPass()); - - // If our modes don't match, don't actually delete the supersceded exception. - // If we did, we would lose valueable state on which frames have been scanned - // on the second pass if an exception is thrown during the 2nd pass. - - // Advance the current tracker pointer now, since it may be deleted below. - pPreviousTracker = (PTR_ExceptionTracker)pPreviousTracker->m_pPrevNestedInfo; - - if (!fIsFirstPass) - { - - // During unwind, at each frame we collapse exception trackers only once i.e. there cannot be multiple - // exception trackers that are collapsed at each frame. Store the information of collapsed exception - // tracker in current tracker to be able to find the parent frame when nested exception escapes. - m_csfEHClauseOfCollapsedTracker = m_pPrevNestedInfo->m_EHClauseInfo.GetCallerStackFrameForEHClause(); - m_EnclosingClauseInfoOfCollapsedTracker = ((PTR_ExceptionTracker)m_pPrevNestedInfo)->m_EnclosingClauseInfoForGCReporting; - - EH_LOG((LL_INFO100, " - removing previous tracker\n")); - - ExceptionTracker* pTrackerToFree = (PTR_ExceptionTracker)m_pPrevNestedInfo; - m_pPrevNestedInfo = pTrackerToFree->m_pPrevNestedInfo; - -#if defined(DEBUGGING_SUPPORTED) - if (g_pDebugInterface != NULL) - { - g_pDebugInterface->DeleteInterceptContext(pTrackerToFree->m_DebuggerExState.GetDebuggerInterceptContext()); - } -#endif // DEBUGGING_SUPPORTED - - CONSISTENCY_CHECK(pTrackerToFree->IsValid()); - FreeTrackerMemory(pTrackerToFree, memBoth); - } - - DebugLogTrackerRanges(" B"); - } - - return fResult; -} - -#if defined(DEBUGGING_SUPPORTED) -BOOL NotifyDebuggerOfStub(Thread* pThread, Frame* pCurrentFrame) -{ - LIMITED_METHOD_CONTRACT; - - BOOL fDeliveredFirstChanceNotification = FALSE; - - _ASSERTE(GetThreadNULLOk() == pThread); - - GCX_COOP(); - - // 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 ICorDebugInternalFrame stack range. - if (CORDebuggerAttached()) - { - if (pCurrentFrame->GetTransitionType() == Frame::TT_M2U) - { - // Use -1 for the backing store pointer whenever we use the address of a frame as the stack pointer. - EEToDebuggerExceptionInterfaceWrapper::FirstChanceManagedException(pThread, - (SIZE_T)0, - (SIZE_T)pCurrentFrame); - fDeliveredFirstChanceNotification = TRUE; - } - } - - return fDeliveredFirstChanceNotification; -} -#endif // DEBUGGING_SUPPORTED - -CLRUnwindStatus ExceptionTracker::ProcessExplicitFrame( - CrawlFrame* pcfThisFrame, - StackFrame sf, - BOOL fIsFirstPass, - StackTraceState& STState - ) -{ - CONTRACTL - { - MODE_COOPERATIVE; - GC_TRIGGERS; - THROWS; - PRECONDITION(!pcfThisFrame->IsFrameless()); - PRECONDITION(pcfThisFrame->GetFrame() != FRAME_TOP); - } - CONTRACTL_END; - - Frame* pFrame = pcfThisFrame->GetFrame(); - - EH_LOG((LL_INFO100, " [ ProcessExplicitFrame: pFrame: " FMT_ADDR " pMD: " FMT_ADDR " %s PASS ]\n", DBG_ADDR(pFrame), DBG_ADDR(pFrame->GetFunction()), fIsFirstPass ? "FIRST" : "SECOND")); - - if (FRAME_TOP == pFrame) - { - goto lExit; - } - - if (!m_ExceptionFlags.UnwindingToFindResumeFrame()) - { - // - // update our exception stacktrace - // - - BOOL bIsNewException = FALSE; - BOOL bSkipLastElement = FALSE; - - if (STS_FirstRethrowFrame == STState) - { - bSkipLastElement = TRUE; - } - else - if (STS_NewException == STState) - { - bIsNewException = TRUE; - } - - // Normally, we need to notify the profiler in two cases: - // 1) a brand new exception is thrown, and - // 2) an exception is rethrown. - // However, in this case, if the explicit frame doesn't correspond to a MD, we don't set STState to STS_Append, - // so the next managed call frame we process will give another ExceptionThrown() callback to the profiler. - // So we give the callback below, only in the case when we append to the stack trace. - - MethodDesc* pMD = pcfThisFrame->GetFunction(); - if (pMD) - { - Thread* pThread = m_pThread; - - if (fIsFirstPass) - { - // - // notify profiler of new/rethrown exception - // - if (bSkipLastElement || bIsNewException) - { - GCX_COOP(); - EEToProfilerExceptionInterfaceWrapper::ExceptionThrown(pThread); - UpdatePerformanceMetrics(pcfThisFrame, bSkipLastElement, bIsNewException); - } - - // - // Update stack trace - // - if (!bSkipLastElement) - { - StackTraceInfo::AppendElement(m_hThrowable, 0, sf.SP, pMD, pcfThisFrame); - } - - // - // make callback to debugger and/or profiler - // -#if defined(DEBUGGING_SUPPORTED) - if (NotifyDebuggerOfStub(pThread, pFrame)) - { - // Deliver the FirstChanceNotification after the debugger, if not already delivered. - if (!this->DeliveredFirstChanceNotification()) - { - ExceptionNotifications::DeliverFirstChanceNotification(); - } - } -#endif // DEBUGGING_SUPPORTED - - STState = STS_Append; - } - } - } - -lExit: - return UnwindPending; -} - -CLRUnwindStatus ExceptionTracker::HandleFunclets(bool* pfProcessThisFrame, bool fIsFirstPass, - MethodDesc * pMD, bool fFunclet, StackFrame sf) -{ - CONTRACTL - { - MODE_ANY; - GC_NOTRIGGER; - NOTHROW; - } - CONTRACTL_END; - - BOOL fUnwindingToFindResumeFrame = m_ExceptionFlags.UnwindingToFindResumeFrame(); - - // - // handle out-of-line finallys - // - - // In the second pass, we always want to execute this code. - // In the first pass, we only execute this code if we are not unwinding to find the resume frame. - // We do this to avoid calling the same filter more than once. Search for "UnwindingToFindResumeFrame" - // to find a more elaborate comment in ProcessManagedCallFrame(). - - // If we are in the first pass and we are unwinding to find the resume frame, then make sure the flag is cleared. - if (fIsFirstPass && fUnwindingToFindResumeFrame) - { - m_pSkipToParentFunctionMD = NULL; - } - else - { - // - // this 'skip to parent function MD' code only seems to be needed - // in the case where we call a finally funclet from the normal - // execution codepath. Is there a better way to achieve the same - // goal? Also, will recursion break us in any corner cases? - // [ThrowInFinallyNestedInTryTest] - // [GoryManagedPresentTest] - // - - // - // this was done for AMD64, but i don't understand why AMD64 needed the workaround.. - // (the workaround is the "double call on parent method" part.) - // - - // - // If we encounter a funclet, we need to skip all call frames up - // to and including its parent method call frame. The reason - // behind this is that a funclet is logically part of the parent - // method has all the clauses that covered its logical location - // in the parent covering its body. - // - if (((UINT_PTR)m_pSkipToParentFunctionMD) & 1) - { - EH_LOG((LL_INFO100, " IGNOREFRAME: SKIPTOPARENT: skipping to parent\n")); - *pfProcessThisFrame = false; - if ((((UINT_PTR)pMD) == (((UINT_PTR)m_pSkipToParentFunctionMD) & ~((UINT_PTR)1))) && !fFunclet) - { - EH_LOG((LL_INFO100, " SKIPTOPARENT: found parent for funclet pMD = %p, sf.SP = %p, will stop skipping frames\n", pMD, sf.SP)); - _ASSERTE(0 == (((UINT_PTR)sf.SP) & 1)); - m_pSkipToParentFunctionMD = (MethodDesc*)sf.SP; - - _ASSERTE(!fUnwindingToFindResumeFrame); - } - } - else if (fFunclet) - { - EH_LOG((LL_INFO100, " SKIPTOPARENT: found funclet pMD = %p, will start skipping frames\n", pMD)); - _ASSERTE(0 == (((UINT_PTR)pMD) & 1)); - m_pSkipToParentFunctionMD = (MethodDesc*)(((UINT_PTR)pMD) | 1); - } - else - { - if (sf.SP == ((UINT_PTR)m_pSkipToParentFunctionMD)) - { - EH_LOG((LL_INFO100, " IGNOREFRAME: SKIPTOPARENT: got double call on parent method\n")); - *pfProcessThisFrame = false; - } - else if (m_pSkipToParentFunctionMD && (sf.SP > ((UINT_PTR)m_pSkipToParentFunctionMD))) - { - EH_LOG((LL_INFO100, " SKIPTOPARENT: went past parent method\n")); - m_pSkipToParentFunctionMD = NULL; - } - } - } - - return UnwindPending; -} - -CLRUnwindStatus ExceptionTracker::ProcessManagedCallFrame( - CrawlFrame* pcfThisFrame, - StackFrame sf, - StackFrame sfEstablisherFrame, - EXCEPTION_RECORD* pExceptionRecord, - StackTraceState STState, - UINT_PTR uMethodStartPC, - DWORD dwExceptionFlags, - DWORD dwTACatchHandlerClauseIndex, - StackFrame sfEstablisherOfActualHandlerFrame - ) -{ - CONTRACTL - { - MODE_ANY; - GC_TRIGGERS; - THROWS; - PRECONDITION(pcfThisFrame->IsFrameless()); - } - CONTRACTL_END; - - UINT_PTR uControlPC = (UINT_PTR)GetControlPC(pcfThisFrame->GetRegisterSet()); - CLRUnwindStatus ReturnStatus = UnwindPending; - - MethodDesc* pMD = pcfThisFrame->GetFunction(); - - bool fIsFirstPass = !(dwExceptionFlags & EXCEPTION_UNWINDING); - bool fIsFunclet = pcfThisFrame->IsFunclet(); - - CONSISTENCY_CHECK(IsValid()); - CONSISTENCY_CHECK(ThrowableIsValid() || !fIsFirstPass); - CONSISTENCY_CHECK(pMD != 0); - - EH_LOG((LL_INFO100, " [ ProcessManagedCallFrame this=%p, %s PASS ]\n", this, (fIsFirstPass ? "FIRST" : "SECOND"))); - - EH_LOG((LL_INFO100, " [ method: %s%s, %s ]\n", - (fIsFunclet ? "FUNCLET of " : ""), - pMD->m_pszDebugMethodName, pMD->m_pszDebugClassName)); - - Thread *pThread = GetThread(); - INDEBUG( DumpClauses(pcfThisFrame->GetJitManager(), pcfThisFrame->GetMethodToken(), uMethodStartPC, uControlPC) ); - - bool fIsILStub = pMD->IsILStub(); - bool fGiveDebuggerAndProfilerNotification = !fIsILStub; - BOOL fUnwindingToFindResumeFrame = m_ExceptionFlags.UnwindingToFindResumeFrame(); - - bool fIgnoreThisFrame = false; - bool fProcessThisFrameToFindResumeFrameOnly = false; - - MethodDesc * pUserMDForILStub = NULL; - Frame * pILStubFrame = NULL; - if (fIsILStub && !fIsFunclet) // only make this callback on the main method body of IL stubs - pUserMDForILStub = GetUserMethodForILStub(pThread, sf.SP, pMD, &pILStubFrame); - - // Doing rude abort. Skip all non-constrained execution region code. - // When rude abort is initiated, we cannot intercept any exceptions. - if (pThread->IsRudeAbortInitiated()) - { - // If we are unwinding to find the real resume frame, then we cannot ignore frames yet. - // We need to make sure we find the correct resume frame before starting to ignore frames. - if (fUnwindingToFindResumeFrame) - { - fProcessThisFrameToFindResumeFrameOnly = true; - } - else - { - EH_LOG((LL_INFO100, " IGNOREFRAME: rude abort/CE\n")); - fIgnoreThisFrame = true; - } - } - - // - // BEGIN resume frame processing code - // - // Often times, we'll run into the situation where the actual resume call frame - // is not the same call frame that we see the catch clause in. The reason for this - // is that catch clauses get duplicated down to cover funclet code ranges. When we - // see a catch clause covering our control PC, but it is marked as a duplicate, we - // need to continue to unwind until we find the same clause that isn't marked as a - // duplicate. This will be the correct resume frame. - // - // We actually achieve this skipping by observing that if we are catching at a - // duplicated clause, all the call frames we should be skipping have already been - // processed by a previous exception dispatch. So if we allow the unwind to - // continue, we will immediately bump into the ExceptionTracker for the previous - // dispatch, and our resume frame will be the last frame seen by that Tracker. - // - // Note that we will have visited all the EH clauses for a particular method when we - // see its first funclet (the funclet which is closest to the leaf). We need to make - // sure we don't process any EH clause again when we see other funclets or the parent - // method until we get to the real resume frame. The real resume frame may be another - // funclet, which is why we can't blindly skip all funclets until we see the parent - // method frame. - // - // If the exception is handled by the method, then UnwindingToFindResumeFrame takes - // care of the skipping. We basically skip everything when we are unwinding to find - // the resume frame. If the exception is not handled by the method, then we skip all the - // funclets until we get to the parent method. The logic to handle this is in - // HandleFunclets(). In the first pass, HandleFunclets() only kicks - // in if we are not unwinding to find the resume frame. - // - // Then on the second pass, we need to process frames up to the initial place where - // we saw the catch clause, which means upto and including part of the resume stack - // frame. Then we need to skip the call frames up to the real resume stack frame - // and resume. - // - // In the second pass, we have the same problem with skipping funclets as in the first - // pass. However, in this case, we know exactly which frame is our target unwind frame - // (EXCEPTION_TARGET_UNWIND will be set). So we blindly unwind until we see the parent - // method, or until the target unwind frame. - PTR_EXCEPTION_CLAUSE_TOKEN pLimitClauseToken = NULL; - if (!fIgnoreThisFrame && !fIsFirstPass && !m_sfResumeStackFrame.IsNull() && (sf >= m_sfResumeStackFrame)) - { - EH_LOG((LL_INFO100, " RESUMEFRAME: sf is %p and m_sfResumeStackFrame: %p\n", sf.SP, m_sfResumeStackFrame.SP)); - EH_LOG((LL_INFO100, " RESUMEFRAME: %s initial resume frame: %p\n", (sf == m_sfResumeStackFrame) ? "REACHED" : "PASSED" , m_sfResumeStackFrame.SP)); - - // process this frame to call handlers - EH_LOG((LL_INFO100, " RESUMEFRAME: Found last frame to process finallys in, need to process only part of call frame\n")); - EH_LOG((LL_INFO100, " RESUMEFRAME: Limit clause token: %p\n", m_pClauseForCatchToken)); - pLimitClauseToken = m_pClauseForCatchToken; - - // The limit clause is the same as the clause we're catching at. It is used - // as the last clause we process in the "inital resume frame". Anything further - // down the list of clauses is skipped along with all call frames up to the actual - // resume frame. - CONSISTENCY_CHECK_MSG(sf == m_sfResumeStackFrame, "Passed initial resume frame and fIgnoreThisFrame wasn't set!"); - } - // - // END resume frame code - // - - if (!fIgnoreThisFrame) - { - BOOL fFoundHandler = FALSE; - DWORD_PTR dwHandlerStartPC = 0; - - BOOL bIsNewException = FALSE; - BOOL bSkipLastElement = FALSE; - bool fUnwindFinished = false; - - if (STS_FirstRethrowFrame == STState) - { - bSkipLastElement = TRUE; - } - else - if (STS_NewException == STState) - { - bIsNewException = TRUE; - } - - // We need to notify the profiler on the first pass in two cases: - // 1) a brand new exception is thrown, and - // 2) an exception is rethrown. - if (fIsFirstPass && (bSkipLastElement || bIsNewException)) - { - GCX_COOP(); - EEToProfilerExceptionInterfaceWrapper::ExceptionThrown(pThread); - UpdatePerformanceMetrics(pcfThisFrame, bSkipLastElement, bIsNewException); - } - - if (!fUnwindingToFindResumeFrame) - { - // - // update our exception stacktrace, ignoring IL stubs - // - if (fIsFirstPass && !pMD->IsILStub()) - { - GCX_COOP(); - - if (!bSkipLastElement) - { - StackTraceInfo::AppendElement(m_hThrowable, uControlPC, sf.SP, pMD, pcfThisFrame); - } - } - - // - // make callback to debugger and/or profiler - // - if (fGiveDebuggerAndProfilerNotification) - { - if (fIsFirstPass) - { - EEToProfilerExceptionInterfaceWrapper::ExceptionSearchFunctionEnter(pMD); - - // Notfiy the debugger that we are on the first pass for a managed exception. - // Note that this callback is made for every managed frame. - EEToDebuggerExceptionInterfaceWrapper::FirstChanceManagedException(pThread, uControlPC, sf.SP); - -#if defined(DEBUGGING_SUPPORTED) - _ASSERTE(this == pThread->GetExceptionState()->m_pCurrentTracker); - - // check if the current exception has been intercepted. - if (m_ExceptionFlags.DebuggerInterceptInfo()) - { - // According to the x86 implementation, we don't need to call the ExceptionSearchFunctionLeave() - // profiler callback. - StackFrame sfInterceptStackFrame; - m_DebuggerExState.GetDebuggerInterceptInfo(NULL, NULL, - reinterpret_cast(&(sfInterceptStackFrame.SP)), - NULL, NULL); - - // Save the target unwind frame just like we do when we find a catch clause. - m_sfResumeStackFrame = sfInterceptStackFrame; - ReturnStatus = FirstPassComplete; - goto lExit; - } -#endif // DEBUGGING_SUPPORTED - - // Attempt to deliver the first chance notification to the AD only *AFTER* the debugger - // has done that, provided we have not already delivered it. - if (!this->DeliveredFirstChanceNotification()) - { - ExceptionNotifications::DeliverFirstChanceNotification(); - } - } - else - { -#if defined(DEBUGGING_SUPPORTED) - _ASSERTE(this == pThread->GetExceptionState()->m_pCurrentTracker); - - // check if the exception is intercepted. - if (m_ExceptionFlags.DebuggerInterceptInfo()) - { - MethodDesc* pInterceptMD = NULL; - StackFrame sfInterceptStackFrame; - - // check if we have reached the interception point yet - m_DebuggerExState.GetDebuggerInterceptInfo(&pInterceptMD, NULL, - reinterpret_cast(&(sfInterceptStackFrame.SP)), - NULL, NULL); - - // If the exception has gone unhandled in the first pass, we wouldn't have a chance - // to set the target unwind frame. Check for this case now. - if (m_sfResumeStackFrame.IsNull()) - { - m_sfResumeStackFrame = sfInterceptStackFrame; - } - _ASSERTE(m_sfResumeStackFrame == sfInterceptStackFrame); - - if ((pInterceptMD == pMD) && - (sfInterceptStackFrame == sf)) - { - // If we have reached the stack frame at which the exception is intercepted, - // then finish the second pass prematurely. - SecondPassIsComplete(pMD, sf); - ReturnStatus = SecondPassComplete; - goto lExit; - } - } -#endif // DEBUGGING_SUPPORTED - - // According to the x86 implementation, we don't need to call the ExceptionUnwindFunctionEnter() - // profiler callback when an exception is intercepted. - EEToProfilerExceptionInterfaceWrapper::ExceptionUnwindFunctionEnter(pMD); - } - } - - } - - { - IJitManager* pJitMan = pcfThisFrame->GetJitManager(); - const METHODTOKEN& MethToken = pcfThisFrame->GetMethodToken(); - - EH_CLAUSE_ENUMERATOR EnumState; - unsigned EHCount = pJitMan->InitializeEHEnumeration(MethToken, &EnumState); - - if (!fIsFirstPass) - { - // For a method that may have nested funclets, it is possible that a reference may be - // dead at the point where control flow left the method but may become active once - // a funclet is executed. - // - // Upon returning from the funclet but before the next funclet is invoked, a GC - // may happen if we are in preemptive mode. Since the GC stackwalk will commence - // at the original IP at which control left the method, it can result in the reference - // not being updated (since it was dead at the point control left the method) if the object - // is moved during GC. - // - // To address this, we will indefinitely switch to COOP mode while enumerating, and invoking, - // funclets. - // - // This switch is also required for another scenario: we may be in unwind phase and the current frame - // may not have any termination handlers to be invoked (i.e. it may have zero EH clauses applicable to - // the unwind phase). If we do not switch to COOP mode for such a frame, we could remain in preemp mode. - // Upon returning back from ProcessOSExceptionNotification in ProcessCLRException, when we attempt to - // switch to COOP mode to update the LastUnwoundEstablisherFrame, we could get blocked due to an - // active GC, prior to performing the update. - // - // In this case, if the GC stackwalk encounters the current frame and attempts to check if it has been - // unwound by an exception, then while it has been unwound (especially since it had no termination handlers) - // logically, it will not figure out as unwound and thus, GC stackwalk would attempt to report references from - // it, which is incorrect. - // - // Thus, when unwinding, we will always switch to COOP mode indefinitely, irrespective of whether - // the frame has EH clauses to be processed or not. - GCX_COOP_NO_DTOR(); - - // We will also forbid any GC to happen between successive funclet invocations. - // This will be automatically undone when the contract goes off the stack as the method - // returns back to its caller. - BEGINFORBIDGC(); - } - - for (unsigned i = 0; i < EHCount; i++) - { - EE_ILEXCEPTION_CLAUSE EHClause; - PTR_EXCEPTION_CLAUSE_TOKEN pEHClauseToken = pJitMan->GetNextEHClause(&EnumState, &EHClause); - - EH_LOG((LL_INFO100, " considering %s clause [%x,%x), ControlPc is %s clause (offset %x)", - (IsFault(&EHClause) ? "fault" : - (IsFinally(&EHClause) ? "finally" : - (IsFilterHandler(&EHClause) ? "filter" : - (IsTypedHandler(&EHClause) ? "typed" : "unknown")))), - EHClause.TryStartPC, - EHClause.TryEndPC, - (ClauseCoversPC(&EHClause, pcfThisFrame->GetRelOffset()) ? "inside" : "outside"), - pcfThisFrame->GetRelOffset() - )); - - LOG((LF_EH, LL_INFO100, "\n")); - - // If we have a valid EstablisherFrame for the managed frame where - // ThreadAbort was raised after the catch block, then see if we - // have reached that frame during the exception dispatch. If we - // have, then proceed to skip applicable EH clauses. - if ((!sfEstablisherOfActualHandlerFrame.IsNull()) && (sfEstablisherFrame == sfEstablisherOfActualHandlerFrame)) - { - // We should have a valid index of the EH clause (corresponding to a catch block) after - // which thread abort was raised? - _ASSERTE(dwTACatchHandlerClauseIndex > 0); - { - // Since we have the index, check if the current EH clause index - // is less then saved index. If it is, then it implies that - // we are evaluating clauses that lie "before" the EH clause - // for the catch block "after" which thread abort was raised. - // - // Since ThreadAbort has to make forward progress, we will - // skip evaluating any such EH clauses. Two things can happen: - // - // 1) We will find clauses representing handlers beyond the - // catch block after which ThreadAbort was raised. Since this is - // what we want, we evaluate them. - // - // 2) There wont be any more clauses implying that the catch block - // after which the exception was raised was the outermost - // handler in the method. Thus, the exception will escape out, - // which is semantically the correct thing to happen. - // - // The premise of this check is based upon a JIT compiler's implementation - // detail: when it generates EH clauses, JIT compiler will order them from - // top->bottom (when reading a method) and inside->out when reading nested - // clauses. - // - // This assumption is not new since the basic EH type-matching is reliant - // on this very assumption. However, now we have one more candidate that - // gets to rely on it. - // - // Eventually, this enables forward progress of thread abort exception. - if (i <= (dwTACatchHandlerClauseIndex -1)) - { - EH_LOG((LL_INFO100, " skipping the evaluation of EH clause (index=%d) since we cannot process an exception in a handler\n", i)); - EH_LOG((LL_INFO100, " that exists prior to the one (index=%d) after which ThreadAbort was [re]raised.\n", dwTACatchHandlerClauseIndex)); - continue; - } - } - } - - - // see comment above where we set pLimitClauseToken - if (pEHClauseToken == pLimitClauseToken) - { - EH_LOG((LL_INFO100, " found limit clause, stopping clause enumeration\n")); - - // - // - // If we are here, the exception has been identified to be handled by a duplicate catch clause - // that is protecting the current funclet. The call to SetEnclosingClauseInfo (below) - // will setup the CallerSP (for GC reference reporting) to be the SP of the - // of the caller of current funclet (where the exception has happened, or is escaping from). - // - // However, we need the CallerSP to be set as the SP of the caller of the - // actual frame that will contain (and invoke) the catch handler corresponding to - // the duplicate clause. But that isn't available right now and we can only know - // once we unwind upstack to reach the target frame. - // - // Thus, upon reaching the target frame and before invoking the catch handler, - // we will fix up the CallerSP (for GC reporting) to be that of the caller of the - // target frame that will be invoking the actual catch handler. - // - // - // - // for catch clauses - SetEnclosingClauseInfo(fIsFunclet, - pcfThisFrame->GetRelOffset(), - GetSP(pcfThisFrame->GetRegisterSet()->pCallerContext)); - fUnwindFinished = true; - m_fFixupCallerSPForGCReporting = true; - break; - } - - BOOL fTermHandler = IsFaultOrFinally(&EHClause); - fFoundHandler = FALSE; - - if (( fIsFirstPass && fTermHandler) || - (!fIsFirstPass && !fTermHandler)) - { - continue; - } - - if (ClauseCoversPC(&EHClause, pcfThisFrame->GetRelOffset())) - { - EH_LOG((LL_INFO100, " clause covers ControlPC\n")); - - dwHandlerStartPC = pJitMan->GetCodeAddressForRelOffset(MethToken, EHClause.HandlerStartPC); +// static +void ExceptionTracker::DebugLogTrackerRanges(_In_z_ const char *pszTag) +{ +#ifdef _DEBUG + CONTRACTL + { + MODE_ANY; + GC_NOTRIGGER; + NOTHROW; + } + CONTRACTL_END; - if (fUnwindingToFindResumeFrame) - { - CONSISTENCY_CHECK(fIsFirstPass); - if (!fTermHandler) - { - // m_pClauseForCatchToken can only be NULL for continuable exceptions, but we should never - // get here if we are handling continuable exceptions. fUnwindingToFindResumeFrame is - // only true at the end of the first pass. - _ASSERTE(m_pClauseForCatchToken != NULL); - - // handlers match and not duplicate? - EH_LOG((LL_INFO100, " RESUMEFRAME: catch handler: [%x,%x], this handler: [%x,%x] %s\n", - m_ClauseForCatch.HandlerStartPC, - m_ClauseForCatch.HandlerEndPC, - EHClause.HandlerStartPC, - EHClause.HandlerEndPC, - IsDuplicateClause(&EHClause) ? "[duplicate]" : "")); - - if ((m_ClauseForCatch.HandlerStartPC == EHClause.HandlerStartPC) && - (m_ClauseForCatch.HandlerEndPC == EHClause.HandlerEndPC)) - { - EH_LOG((LL_INFO100, " RESUMEFRAME: found clause with same handler as catch\n")); - if (!IsDuplicateClause(&EHClause)) - { - CONSISTENCY_CHECK(fIsFirstPass); - - if (fProcessThisFrameToFindResumeFrameOnly) - { - EH_LOG((LL_INFO100, " RESUMEFRAME: identified real resume frame, \ - but rude thread abort is initiated: %p\n", sf.SP)); - - // We have found the real resume frame. However, rude thread abort - // has been initiated. Thus, we need to continue the first pass - // as if we have not found a handler yet. To do so, we need to - // reset all the information we have saved when we find the handler. - m_ExceptionFlags.ResetUnwindingToFindResumeFrame(); - - m_uCatchToCallPC = 0; - m_pClauseForCatchToken = NULL; - - m_sfResumeStackFrame.Clear(); - ReturnStatus = UnwindPending; - } - else - { - EH_LOG((LL_INFO100, " RESUMEFRAME: identified real resume frame: %p\n", sf.SP)); - - // Save off the index and the EstablisherFrame of the EH clause of the non-duplicate handler - // that decided to handle the exception. We may need it - // if a ThreadAbort is raised after the catch block - // executes. - m_dwIndexClauseForCatch = i + 1; - m_sfEstablisherOfActualHandlerFrame = sfEstablisherFrame; -#ifndef ESTABLISHER_FRAME_ADDRESS_IS_CALLER_SP - m_sfCallerOfActualHandlerFrame = EECodeManager::GetCallerSp(pcfThisFrame->pRD); -#else // !ESTABLISHER_FRAME_ADDRESS_IS_CALLER_SP - // On ARM & ARM64, the EstablisherFrame is the value of SP at the time a function was called and before it's prolog - // executed. Effectively, it is the SP of the caller. - m_sfCallerOfActualHandlerFrame = sfEstablisherFrame.SP; -#endif // ESTABLISHER_FRAME_ADDRESS_IS_CALLER_SP - - ReturnStatus = FirstPassComplete; - } - } - break; - } - } - } - else if (IsFilterHandler(&EHClause)) - { - DWORD_PTR dwResult = EXCEPTION_CONTINUE_SEARCH; - DWORD_PTR dwFilterStartPC; + Thread* pThread = GetThreadNULLOk(); + ExceptionTrackerBase* pTracker = pThread ? pThread->GetExceptionState()->m_pCurrentTracker : NULL; - dwFilterStartPC = pJitMan->GetCodeAddressForRelOffset(MethToken, EHClause.FilterOffset); + int i = 0; - EH_LOG((LL_INFO100, " calling filter\n")); + while (pTracker) + { + EH_LOG((LL_INFO100, "%s:|%02d| %p: (%p %p) %s\n", pszTag, i, pTracker, pTracker->m_ScannedStackRange.GetLowerBound().SP, pTracker->m_ScannedStackRange.GetUpperBound().SP, + pTracker->IsInFirstPass() ? "1st pass" : "2nd pass" + )); + pTracker = pTracker->m_pPrevNestedInfo; + i++; + } +#endif // _DEBUG +} - // @todo : If user code throws a StackOverflowException and we have plenty of stack, - // we probably don't want to be so strict in not calling handlers. - if (! IsStackOverflowException()) - { - // Save the current EHClause Index and Establisher of the clause post which - // ThreadAbort was raised. This is done an exception handled inside a filter - // reset the state that was setup before the filter was invoked. - // - // We dont have to do this for finally/fault clauses since they execute - // in the second pass and by that time, we have already skipped the required - // EH clauses in the applicable stackframe. - DWORD dwPreFilterTACatchHandlerClauseIndex = dwTACatchHandlerClauseIndex; - StackFrame sfPreFilterEstablisherOfActualHandlerFrame = sfEstablisherOfActualHandlerFrame; - - EX_TRY - { - // We want to call filters even if the thread is aborting, so suppress abort - // checks while the filter runs. - ThreadPreventAsyncHolder preventAbort(TRUE); - - // for filter clauses - SetEnclosingClauseInfo(fIsFunclet, - pcfThisFrame->GetRelOffset(), - GetSP(pcfThisFrame->GetRegisterSet()->pCallerContext)); - - CONTEXT *pContext = NULL; -#ifdef USE_FUNCLET_CALL_HELPER - // On ARM & ARM64, the OS passes us the CallerSP for the frame for which personality routine has been invoked. - // Since IL filters are invoked in the first pass, we pass this CallerSP to the filter funclet which will - // then lookup the actual frame pointer value using it since we dont have a frame pointer to pass to it - // directly. - // - // Assert our invariants (we had set them up in InitializeCrawlFrame): - REGDISPLAY *pCurRegDisplay = pcfThisFrame->GetRegisterSet(); -#ifndef USE_CURRENT_CONTEXT_IN_FILTER - // 1) In first pass, we dont have a valid current context IP - _ASSERTE(GetIP(pCurRegDisplay->pCurrentContext) == 0); - pContext = pCurRegDisplay->pCallerContext; -#else - pContext = pCurRegDisplay->pCurrentContext; -#endif // !USE_CURRENT_CONTEXT_IN_FILTER -#ifdef USE_CALLER_SP_IN_FUNCLET - // 2) Our caller context and caller SP are valid - _ASSERTE(pCurRegDisplay->IsCallerContextValid && pCurRegDisplay->IsCallerSPValid); - // 3) CallerSP is intact - _ASSERTE(GetSP(pCurRegDisplay->pCallerContext) == GetRegdisplaySP(pCurRegDisplay)); -#endif // USE_CALLER_SP_IN_FUNCLET -#endif // USE_FUNCLET_CALL_HELPER - { - // CallHandler expects to be in COOP mode. - GCX_COOP(); - dwResult = CallHandler(dwFilterStartPC, sf, &EHClause, pMD, Filter, pContext); - } - } - EX_CATCH - { - // 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); - - // we've returned from the filter abruptly, now out of managed code - m_EHClauseInfo.SetManagedCodeEntered(FALSE); - - EH_LOG((LL_INFO100, " filter threw an exception\n")); - - // notify profiler - EEToProfilerExceptionInterfaceWrapper::ExceptionSearchFilterLeave(); - m_EHClauseInfo.ResetInfo(); - - // continue search - } - EX_END_CATCH(SwallowAllExceptions); - - // Reset the EH clause Index and Establisher of the TA reraise clause - pThread->m_dwIndexClauseForCatch = dwPreFilterTACatchHandlerClauseIndex; - pThread->m_sfEstablisherOfActualHandlerFrame = sfPreFilterEstablisherOfActualHandlerFrame; - - if (pThread->IsRudeAbortInitiated()) - { - EH_LOG((LL_INFO100, " IGNOREFRAME: rude abort\n")); - goto lExit; - } - } - else - { - EH_LOG((LL_INFO100, " STACKOVERFLOW: filter not called due to lack of guard page\n")); - // continue search - } - if (EXCEPTION_EXECUTE_HANDLER == dwResult) - { - fFoundHandler = TRUE; - } - else if (EXCEPTION_CONTINUE_SEARCH != dwResult) - { - // - // Behavior is undefined according to the spec. Let's not execute the handler. - // - } - EH_LOG((LL_INFO100, " filter returned %s\n", (fFoundHandler ? "EXCEPTION_EXECUTE_HANDLER" : "EXCEPTION_CONTINUE_SEARCH"))); - } - else if (IsTypedHandler(&EHClause)) - { - GCX_COOP(); +#if defined(DEBUGGING_SUPPORTED) +BOOL NotifyDebuggerOfStub(Thread* pThread, Frame* pCurrentFrame) +{ + LIMITED_METHOD_CONTRACT; - TypeHandle thrownType = TypeHandle(); - OBJECTREF oThrowable = m_pThread->GetThrowable(); - if (oThrowable != NULL) - { - oThrowable = PossiblyUnwrapThrowable(oThrowable, pcfThisFrame->GetAssembly()); - thrownType = oThrowable->GetTypeHandle(); - } + BOOL fDeliveredFirstChanceNotification = FALSE; - if (!thrownType.IsNull()) - { - if (EHClause.ClassToken == mdTypeRefNil) - { - // this is a catch(...) - fFoundHandler = TRUE; - } - else - { - TypeHandle typeHnd; - EX_TRY - { - typeHnd = pJitMan->ResolveEHClause(&EHClause, pcfThisFrame); - } - EX_CATCH_EX(Exception) - { - SString msg; - GET_EXCEPTION()->GetMessage(msg); - msg.Insert(msg.Begin(), W("Cannot resolve EH clause:\n")); - EEPOLICY_HANDLE_FATAL_ERROR_WITH_MESSAGE(COR_E_FAILFAST, msg.GetUnicode()); - } - EX_END_CATCH(RethrowTransientExceptions); - - EH_LOG((LL_INFO100, - " clause type = %s\n", - (!typeHnd.IsNull() ? typeHnd.GetMethodTable()->GetDebugClassName() - : ""))); - EH_LOG((LL_INFO100, - " thrown type = %s\n", - thrownType.GetMethodTable()->GetDebugClassName())); - - fFoundHandler = !typeHnd.IsNull() && ExceptionIsOfRightType(typeHnd, thrownType); - } - } - } - else - { - _ASSERTE(fTermHandler); - fFoundHandler = TRUE; - } + _ASSERTE(GetThreadNULLOk() == pThread); - if (fFoundHandler) - { - if (fIsFirstPass) - { - _ASSERTE(IsFilterHandler(&EHClause) || IsTypedHandler(&EHClause)); - - EH_LOG((LL_INFO100, " found catch at 0x%p, sp = 0x%p\n", dwHandlerStartPC, sf.SP)); - m_uCatchToCallPC = dwHandlerStartPC; - m_pClauseForCatchToken = pEHClauseToken; - m_ClauseForCatch = EHClause; - - m_sfResumeStackFrame = sf; - -#if defined(DEBUGGING_SUPPORTED) || defined(PROFILING_SUPPORTED) - // - // notify the debugger and profiler - // - if (fGiveDebuggerAndProfilerNotification) - { - EEToProfilerExceptionInterfaceWrapper::ExceptionSearchCatcherFound(pMD); - } - - if (fIsILStub) - { - // - // NotifyOfCHFFilter has two behaviors - // * Notifify debugger, get interception info and unwind (function will not return) - // In this case, m_sfResumeStackFrame is expected to be NULL or the frame of interception. - // We NULL it out because we get the interception event after this point. - // * Notifify debugger and return. - // In this case the normal EH proceeds and we need to reset m_sfResumeStackFrame to the sf catch handler. - // TODO: remove this call and try to report the IL catch handler in the IL stub itself. - m_sfResumeStackFrame.Clear(); - EEToDebuggerExceptionInterfaceWrapper::NotifyOfCHFFilter((EXCEPTION_POINTERS*)&m_ptrs, pILStubFrame); - m_sfResumeStackFrame = sf; - } - else - { - // We don't need to do anything special for continuable exceptions after calling - // this callback. We are going to start unwinding anyway. - EEToDebuggerExceptionInterfaceWrapper::FirstChanceManagedExceptionCatcherFound(pThread, pMD, (TADDR) uMethodStartPC, sf.SP, - &EHClause); - } - - // If the exception is intercepted, then the target unwind frame may not be the - // stack frame we are currently processing, so clear it now. We'll set it - // later in second pass. - if (pThread->GetExceptionState()->GetFlags()->DebuggerInterceptInfo()) - { - m_sfResumeStackFrame.Clear(); - } -#endif //defined(DEBUGGING_SUPPORTED) || defined(PROFILING_SUPPORTED) - - // - // BEGIN resume frame code - // - EH_LOG((LL_INFO100, " RESUMEFRAME: initial resume stack frame: %p\n", sf.SP)); - - if (IsDuplicateClause(&EHClause)) - { - EH_LOG((LL_INFO100, " RESUMEFRAME: need to unwind to find real resume frame\n")); - m_ExceptionFlags.SetUnwindingToFindResumeFrame(); - - // This is a duplicate catch funclet. As a result, we will continue to let the - // exception dispatch proceed upstack to find the actual frame where the - // funclet lives. - // - // At the same time, we also need to save the CallerSP of the frame containing - // the catch funclet (like we do for other funclets). If the current frame - // represents a funclet that was invoked by JITted code, then we will save - // the caller SP of the current frame when we see it during the 2nd pass - - // refer to the use of "pLimitClauseToken" in the code above. - // - // However, that is not the callerSP of the frame containing the catch funclet - // as the actual frame containing the funclet (and where it will be executed) - // is the one that will be the target of unwind during the first pass. - // - // To correctly get that, we will determine if the current frame is a funclet - // and if it was invoked from JITted code. If this is true, then current frame - // represents a finally funclet invoked non-exceptionally (from its parent frame - // or yet another funclet). In such a case, we will set a flag indicating that - // we need to reset the enclosing clause SP for the catch funclet and later, - // when 2nd pass reaches the actual frame containing the catch funclet to be - // executed, we will update the enclosing clause SP if the - // "m_fResetEnclosingClauseSPForCatchFunclet" flag is set, just prior to - // invoking the catch funclet. - if (fIsFunclet) - { - REGDISPLAY* pCurRegDisplay = pcfThisFrame->GetRegisterSet(); - _ASSERTE(pCurRegDisplay->IsCallerContextValid); - TADDR adrReturnAddressFromFunclet = PCODEToPINSTR(GetIP(pCurRegDisplay->pCallerContext)) - STACKWALK_CONTROLPC_ADJUST_OFFSET; - m_fResetEnclosingClauseSPForCatchFunclet = ExecutionManager::IsManagedCode(adrReturnAddressFromFunclet); - } - - ReturnStatus = UnwindPending; - break; - } - - EH_LOG((LL_INFO100, " RESUMEFRAME: no extra unwinding required, real resume frame: %p\n", sf.SP)); - - // Save off the index and the EstablisherFrame of the EH clause of the non-duplicate handler - // that decided to handle the exception. We may need it - // if a ThreadAbort is raised after the catch block - // executes. - m_dwIndexClauseForCatch = i + 1; - m_sfEstablisherOfActualHandlerFrame = sfEstablisherFrame; - -#ifndef ESTABLISHER_FRAME_ADDRESS_IS_CALLER_SP - m_sfCallerOfActualHandlerFrame = EECodeManager::GetCallerSp(pcfThisFrame->pRD); -#else // !ESTABLISHER_FRAME_ADDRESS_IS_CALLER_SP - m_sfCallerOfActualHandlerFrame = sfEstablisherFrame.SP; -#endif // ESTABLISHER_FRAME_ADDRESS_IS_CALLER_SP - // - // END resume frame code - // - - ReturnStatus = FirstPassComplete; - break; - } - else - { - EH_LOG((LL_INFO100, " found finally/fault at 0x%p\n", dwHandlerStartPC)); - _ASSERTE(fTermHandler); - - // @todo : If user code throws a StackOverflowException and we have plenty of stack, - // we probably don't want to be so strict in not calling handlers. - if (!IsStackOverflowException()) - { - DWORD_PTR dwStatus; - - // for finally clauses - SetEnclosingClauseInfo(fIsFunclet, - pcfThisFrame->GetRelOffset(), - GetSP(pcfThisFrame->GetRegisterSet()->pCallerContext)); - - // We have switched to indefinite COOP mode just before this loop started. - // Since we also forbid GC during second pass, disable it now since - // invocation of managed code can result in a GC. - ENDFORBIDGC(); - dwStatus = CallHandler(dwHandlerStartPC, sf, &EHClause, pMD, FaultFinally, pcfThisFrame->GetRegisterSet()->pCurrentContext); - - // Once we return from a funclet, forbid GC again (refer to comment before start of the loop for details) - BEGINFORBIDGC(); - } - else - { - EH_LOG((LL_INFO100, " STACKOVERFLOW: finally not called due to lack of guard page\n")); - // continue search - } - - // - // will continue to find next fault/finally in this call frame - // - } - } // if fFoundHandler - } // if clause covers PC - } // foreach eh clause - } // if stack frame is far enough away from guard page + GCX_COOP(); - // - // notify the profiler - // - if (fGiveDebuggerAndProfilerNotification) + // 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 ICorDebugInternalFrame stack range. + if (CORDebuggerAttached()) + { + if (pCurrentFrame->GetTransitionType() == Frame::TT_M2U) { - if (fIsFirstPass) - { - if (!fUnwindingToFindResumeFrame) - { - EEToProfilerExceptionInterfaceWrapper::ExceptionSearchFunctionLeave(pMD); - } - } - else - { - if (!fUnwindFinished) - { - EEToProfilerExceptionInterfaceWrapper::ExceptionUnwindFunctionLeave(pMD); - } - } + // Use -1 for the backing store pointer whenever we use the address of a frame as the stack pointer. + EEToDebuggerExceptionInterfaceWrapper::FirstChanceManagedException(pThread, + (SIZE_T)0, + (SIZE_T)pCurrentFrame); + fDeliveredFirstChanceNotification = TRUE; } - } // fIgnoreThisFrame + } -lExit: - return ReturnStatus; + return fDeliveredFirstChanceNotification; } +#endif // DEBUGGING_SUPPORTED #undef OPTIONAL_SO_CLEANUP_UNWIND @@ -3251,339 +1355,6 @@ void ExceptionTracker::PopTrackers( return; } -// -// static -ExceptionTracker* ExceptionTracker::GetOrCreateTracker( - UINT_PTR ControlPc, - StackFrame sf, - EXCEPTION_RECORD* pExceptionRecord, - CONTEXT* pContextRecord, - BOOL bAsynchronousThreadStop, - bool fIsFirstPass, - StackTraceState* pStackTraceState - ) -{ - CONTRACT(ExceptionTracker*) - { - MODE_ANY; - GC_TRIGGERS; - NOTHROW; - PRECONDITION(CheckPointer(pStackTraceState)); - POSTCONDITION(CheckPointer(RETVAL)); - } - CONTRACT_END; - - Thread* pThread = GetThread(); - ThreadExceptionState* pExState = pThread->GetExceptionState(); - ExceptionTracker* pTracker = (ExceptionTracker*)pExState->m_pCurrentTracker; - CONSISTENCY_CHECK((NULL == pTracker) || (pTracker->IsValid())); - - bool fCreateNewTracker = false; - bool fIsRethrow = false; - bool fTransitionFromSecondToFirstPass = false; - - // Initialize the out parameter. - *pStackTraceState = STS_Append; - - if (NULL != pTracker) - { - fTransitionFromSecondToFirstPass = fIsFirstPass && !pTracker->IsInFirstPass(); - -#ifndef TARGET_UNIX - // We don't check this on PAL where the scanned stack range needs to - // be reset after unwinding a sequence of native frames. - CONSISTENCY_CHECK(!pTracker->m_ScannedStackRange.IsEmpty()); -#endif // TARGET_UNIX - - if (pTracker->m_ExceptionFlags.IsRethrown()) - { - EH_LOG((LL_INFO100, ">>continued processing of RETHROWN exception\n")); - // this is the first time we've seen a rethrown exception, reuse the tracker and reset some state - - fCreateNewTracker = true; - fIsRethrow = true; - } - else - if ((pTracker->m_ptrs.ExceptionRecord != pExceptionRecord) && fIsFirstPass) - { - EH_LOG((LL_INFO100, ">>NEW exception (exception records do not match)\n")); - fCreateNewTracker = true; - } - else - if (sf >= pTracker->m_ScannedStackRange.GetUpperBound()) - { - // We can't have a transition from 1st pass to 2nd pass in this case. - _ASSERTE( ( sf == pTracker->m_ScannedStackRange.GetUpperBound() ) || - ( fIsFirstPass || !pTracker->IsInFirstPass() ) ); - - if (fTransitionFromSecondToFirstPass) - { - // We just transition from 2nd pass to 1st pass without knowing it. - // This means that some unmanaged frame outside of the EE catches the previous exception, - // so we should trash the current tracker and create a new one. - EH_LOG((LL_INFO100, ">>NEW exception (the previous second pass finishes at some unmanaged frame outside of the EE)\n")); - { - GCX_COOP(); - ExceptionTracker::PopTrackers(sf, false); - } - - fCreateNewTracker = true; - } - else - { - EH_LOG((LL_INFO100, ">>continued processing of PREVIOUS exception\n")); - // previously seen exception, reuse the tracker - - *pStackTraceState = STS_Append; - } - } - else - if (pTracker->m_ScannedStackRange.Contains(sf)) - { - EH_LOG((LL_INFO100, ">>continued processing of PREVIOUS exception (revisiting previously processed frames)\n")); - } - else - { - // nested exception - EH_LOG((LL_INFO100, ">>new NESTED exception\n")); - fCreateNewTracker = true; - } - } - else - { - EH_LOG((LL_INFO100, ">>NEW exception\n")); - fCreateNewTracker = true; - } - - if (fCreateNewTracker) - { -#ifdef _DEBUG - if (STATUS_STACK_OVERFLOW == pExceptionRecord->ExceptionCode) - { - CONSISTENCY_CHECK(pExceptionRecord->NumberParameters >= 2); - UINT_PTR uFaultAddress = pExceptionRecord->ExceptionInformation[1]; - UINT_PTR uStackLimit = (UINT_PTR)pThread->GetCachedStackLimit(); - - EH_LOG((LL_INFO100, "STATUS_STACK_OVERFLOW accessing address %p %s\n", - uFaultAddress)); - - UINT_PTR uDispatchStackAvailable; - - uDispatchStackAvailable = uFaultAddress - uStackLimit - HARD_GUARD_REGION_SIZE; - - EH_LOG((LL_INFO100, "%x bytes available for SO processing\n", uDispatchStackAvailable)); - } - else if ((IsComPlusException(pExceptionRecord)) && - (pThread->GetThrowableAsHandle() == g_pPreallocatedStackOverflowException)) - { - EH_LOG((LL_INFO100, "STACKOVERFLOW: StackOverflowException manually thrown\n")); - } -#endif // _DEBUG - - ExceptionTracker* pNewTracker; - - pNewTracker = GetTrackerMemory(); - if (!pNewTracker) - { - if (NULL != pExState->m_OOMTracker.m_pThread) - { - // Fatal error: we spun and could not allocate another tracker - // and our existing emergency tracker is in use. - EEPOLICY_HANDLE_FATAL_ERROR(COR_E_EXECUTIONENGINE); - } - - pNewTracker = &pExState->m_OOMTracker; - } - - new (pNewTracker) ExceptionTracker(ControlPc, - pExceptionRecord, - pContextRecord); - - CONSISTENCY_CHECK(pNewTracker->IsValid()); - CONSISTENCY_CHECK(pThread == pNewTracker->m_pThread); - - EH_LOG((LL_INFO100, "___________________________________________\n")); - EH_LOG((LL_INFO100, "creating new tracker object 0x%p, thread = 0x%p\n", pNewTracker, pThread)); - - GCX_COOP(); - - // We always create a throwable in the first pass when we first see an exception. - // - // On 64bit, every time the exception passes beyond a boundary (e.g. RPInvoke call, or CallDescrWorker call), - // the exception trackers that were created below (stack growing down) that boundary are released, during the 2nd pass, - // if the exception was not caught in managed code. This is because the catcher is in native code and managed exception - // data structures are for use of VM only when the exception is caught in managed code. Also, passing by such - // boundaries is our only opportunity to release such internal structures and not leak the memory. - // - // However, in certain case, release of exception trackers at each boundary can prove to be a bit aggressive. - // Take the example below where "VM" prefix refers to a VM frame and "M" prefix refers to a managed frame on the stack. - // - // VM1 -> M1 - VM2 - (via RPinvoke) -> M2 - // - // Let M2 throw E2 that remains unhandled in managed code (i.e. M1 also does not catch it) but is caught in VM1. - // Note that the acting of throwing an exception also sets it as the LastThrownObject (LTO) against the thread. - // - // Since this is native code (as mentioned in the comments above, there is no distinction made between VM native - // code and external native code) that caught the exception, when the unwind goes past the "Reverse Pinvoke" boundary, - // its personality routine will release the tracker for E2. Thus, only the LTO (which is off the Thread object and not - // the exception tracker) is indicative of type of the last exception thrown. - // - // As the unwind goes up the stack, we come across M1 and, since the original tracker was released, we create a new - // tracker in the 2nd pass that does not contain details like the active exception object. A managed finally executes in M1 - // that throws and catches E1 inside the finally block. Thus, LTO is updated to indicate E1 as the last exception thrown. - // When the exception is caught in VM1 and VM attempts to get LTO, it gets E1, which is incorrect as it was handled within the finally. - // Semantically, it should have got E2 as the LTO. - // - // To address, this we will *also* create a throwable during second pass for most exceptions - // since most of them have had the corresponding first pass. If we are processing - // an exception's second pass, we would have processed its first pass as well and thus, already - // created a throwable that would be setup as the LastThrownObject (LTO) against the Thread. - // - // The only exception to this rule is the longjump - this exception only has second pass - // Thus, if we are in second pass and exception in question is longjump, then do not create a throwable. - // - // In the case of the scenario above, when we attempt to create a new exception tracker, during the unwind, - // for M1, we will also setup E2 as the throwable in the tracker. As a result, when the finally in M1 throws - // and catches the exception, the LTO is correctly updated against the thread (see SafeUpdateLastThrownObject) - // and thus, when VM requests for the LTO, it gets E2 as expected. - bool fCreateThrowableForCurrentPass = true; - if (pExceptionRecord->ExceptionCode == STATUS_LONGJUMP) - { - // Long jump is only in second pass of exception dispatch - _ASSERTE(!fIsFirstPass); - fCreateThrowableForCurrentPass = false; - } - - // When dealing with SQL Hosting like scenario, a real SO - // may be caught in native code. As a result, CRT will perform - // STATUS_UNWIND_CONSOLIDATE that will result in replacing - // the exception record in ProcessCLRException. This replaced - // exception record will point to the exception record for original - // SO for which we will not have created a throwable in the first pass - // due to the SO-specific early exit code in ProcessCLRException. - // - // Thus, if we see that we are here for SO in the 2nd pass, then - // we shouldn't attempt to create a throwable. - if ((!fIsFirstPass) && (pExceptionRecord->ExceptionCode == STATUS_STACK_OVERFLOW)) - { - fCreateThrowableForCurrentPass = false; - } - -#ifdef _DEBUG - if ((!fIsFirstPass) && (fCreateThrowableForCurrentPass == true)) - { - // We should have a LTO available if we are creating - // a throwable during second pass. - _ASSERTE(pThread->LastThrownObjectHandle() != NULL); - } -#endif // _DEBUG - - bool fCreateThrowable = (fCreateThrowableForCurrentPass || (bAsynchronousThreadStop && !pThread->IsAsyncPrevented())); - OBJECTREF oThrowable = NULL; - - if (fCreateThrowable) - { - if (fIsRethrow) - { - oThrowable = ObjectFromHandle(pTracker->m_hThrowable); - } - else - { - // this can take a nested exception - oThrowable = CreateThrowable(pExceptionRecord, bAsynchronousThreadStop); - } - } - - GCX_FORBID(); // we haven't protected oThrowable - - if (pExState->m_pCurrentTracker != pNewTracker) // OOM can make this false - { - pNewTracker->m_pPrevNestedInfo = pExState->m_pCurrentTracker; - pTracker = pNewTracker; - pThread->GetExceptionState()->m_pCurrentTracker = pTracker; - } - - if (fCreateThrowable) - { - CONSISTENCY_CHECK(oThrowable != NULL); - CONSISTENCY_CHECK(NULL == pTracker->m_hThrowable); - - pThread->SafeSetThrowables(oThrowable); - } - INDEBUG(oThrowable = NULL); - - if (fIsRethrow) - { - *pStackTraceState = STS_FirstRethrowFrame; - } - else - { - *pStackTraceState = STS_NewException; - } - - _ASSERTE(pTracker->m_pLimitFrame == NULL); - pTracker->ResetLimitFrame(); - } - - if (!fIsFirstPass) - { - { - // Refer to the comment around ExceptionTracker::HasFrameBeenUnwoundByAnyActiveException - // for details on the usage of this COOP switch. - GCX_COOP(); - - if (pTracker->IsInFirstPass()) - { - CONSISTENCY_CHECK_MSG(fCreateNewTracker || pTracker->m_ScannedStackRange.Contains(sf), - "Tracker did not receive a first pass!"); - - // Save the topmost StackFrame the tracker saw in the first pass before we reset the - // scanned stack range. - pTracker->m_sfFirstPassTopmostFrame = pTracker->m_ScannedStackRange.GetUpperBound(); - - // We have to detect this transition because otherwise we break when unmanaged code - // catches our exceptions. - EH_LOG((LL_INFO100, ">>tracker transitioned to second pass\n")); - pTracker->m_ScannedStackRange.Reset(); - - pTracker->m_ExceptionFlags.SetUnwindHasStarted(); - if (pTracker->m_ExceptionFlags.UnwindingToFindResumeFrame()) - { - // UnwindingToFindResumeFrame means that in the first pass, we determine that a method - // catches the exception, but the method frame we are inspecting is a funclet method frame - // and is not the correct frame to resume execution. We need to resume to the correct - // method frame before starting the second pass. The correct method frame is most likely - // the parent method frame, but it can also be another funclet method frame. - // - // If the exception transitions from first pass to second pass before we find the parent - // method frame, there is only one possibility: some other thread has initiated a rude - // abort on the current thread, causing us to skip processing of all method frames. - _ASSERTE(pThread->IsRudeAbortInitiated()); - } - // Lean on the safe side and just reset everything unconditionally. - pTracker->FirstPassIsComplete(); - - EEToDebuggerExceptionInterfaceWrapper::ManagedExceptionUnwindBegin(pThread); - - pTracker->ResetLimitFrame(); - } - else - { - // In the second pass, there's a possibility that UMThunkUnwindFrameChainHandler() has - // popped some frames off the frame chain underneath us. Check for this case here. - if (pTracker->m_pLimitFrame < pThread->GetFrame()) - { - pTracker->ResetLimitFrame(); - } - } - } - } - - _ASSERTE(pTracker->m_pLimitFrame >= pThread->GetFrame()); - - RETURN pTracker; -} - void ExceptionTracker::ResetLimitFrame() { WRAPPER_NO_CONTRACT; @@ -6614,38 +4385,6 @@ void ExceptionTrackerBase::EnumMemoryRegions(CLRDataEnumMemoryFlags flags) } #endif // DACCESS_COMPILE -#ifndef DACCESS_COMPILE -// This is a thin wrapper around ResetThreadAbortState. Its primarily used to -// instantiate CrawlFrame, when required, for walking the stack on IA64. -// -// The "when required" part are the set of conditions checked prior to the call to -// this method in ExceptionTracker::ProcessOSExceptionNotification (and asserted in -// ResetThreadabortState). -// -// Also, since CrawlFrame ctor is protected, it can only be instantiated by friend -// types (which ExceptionTracker is). - -// static -void ExceptionTracker::ResetThreadAbortStatus(PTR_Thread pThread, CrawlFrame *pCf, StackFrame sfCurrentStackFrame) -{ - CONTRACTL - { - NOTHROW; - GC_NOTRIGGER; - MODE_ANY; - PRECONDITION(pThread != NULL); - PRECONDITION(pCf != NULL); - PRECONDITION(!sfCurrentStackFrame.IsNull()); - } - CONTRACTL_END; - - if (pThread->IsAbortRequested()) - { - ResetThreadAbortState(pThread, pCf, sfCurrentStackFrame); - } -} -#endif //!DACCESS_COMPILE - #ifndef DACCESS_COMPILE // Mark the pinvoke frame as invoking CallCatchFunclet (and similar) for collided unwind detection void MarkInlinedCallFrameAsFuncletCall(Frame* pFrame) diff --git a/src/coreclr/vm/exceptionhandling.h b/src/coreclr/vm/exceptionhandling.h index e8f6ac5f9a9d29..a7e9018f613674 100644 --- a/src/coreclr/vm/exceptionhandling.h +++ b/src/coreclr/vm/exceptionhandling.h @@ -305,57 +305,6 @@ class ExceptionTracker : public ExceptionTrackerBase STS_NewException, }; - static void InitializeCrawlFrame(CrawlFrame* pcfThisFrame, Thread* pThread, StackFrame sf, REGDISPLAY* pRD, - PT_DISPATCHER_CONTEXT pDispatcherContext, DWORD_PTR ControlPCForEHSearch, - UINT_PTR* puMethodStartPC, - ExceptionTracker *pCurrentTracker); - - void InitializeCurrentContextForCrawlFrame(CrawlFrame* pcfThisFrame, PT_DISPATCHER_CONTEXT pDispatcherContext, StackFrame sfEstablisherFrame); - - static void InitializeCrawlFrameForExplicitFrame(CrawlFrame* pcfThisFrame, Frame* pFrame, MethodDesc *pMD); - -#ifndef DACCESS_COMPILE - static void ResetThreadAbortStatus(PTR_Thread pThread, CrawlFrame *pCf, StackFrame sfCurrentStackFrame); -#endif // !DACCESS_COMPILE - - CLRUnwindStatus ProcessOSExceptionNotification( - PEXCEPTION_RECORD pExceptionRecord, - PT_CONTEXT pContextRecord, - PT_DISPATCHER_CONTEXT pDispatcherContext, - DWORD dwExceptionFlags, - StackFrame sf, - Thread* pThread, - StackTraceState STState, - PVOID pICFSetAsLimitFrame - ); - - CLRUnwindStatus ProcessExplicitFrame( - CrawlFrame* pcfThisFrame, - StackFrame sf, - BOOL fIsFirstPass, - StackTraceState& STState - ); - - CLRUnwindStatus ProcessManagedCallFrame( - CrawlFrame* pcfThisFrame, - StackFrame sf, - StackFrame sfEstablisherFrame, - EXCEPTION_RECORD* pExceptionRecord, - StackTraceState STState, - UINT_PTR uMethodStartPC, - DWORD dwExceptionFlags, - DWORD dwTACatchHandlerClauseIndex, - StackFrame sfEstablisherOfActualHandlerFrame - ); - - bool UpdateScannedStackRange(StackFrame sf, bool fIsFirstPass); - - void FirstPassIsComplete(); - void SecondPassIsComplete(MethodDesc* pMD, StackFrame sfResumeStackFrame); - - CLRUnwindStatus HandleFunclets(bool* pfProcessThisFrame, bool fIsFirstPass, - MethodDesc * pMD, bool fFunclet, StackFrame sf); - static OBJECTREF CreateThrowable( PEXCEPTION_RECORD pExceptionRecord, BOOL bAsynchronousThreadStop @@ -373,12 +322,6 @@ class ExceptionTracker : public ExceptionTrackerBase static bool IsInStackRegionUnwoundByCurrentException(CrawlFrame * pCF); static bool HasFrameBeenUnwoundByAnyActiveException(CrawlFrame * pCF); - void SetCurrentEstablisherFrame(StackFrame sfEstablisher) - { - LIMITED_METHOD_CONTRACT; - - m_sfCurrentEstablisherFrame = sfEstablisher; - } StackFrame GetCurrentEstablisherFrame() { @@ -467,15 +410,6 @@ class ExceptionTracker : public ExceptionTrackerBase static void PopTrackerIfEscaping(void* pvStackPointer); - static ExceptionTracker* - GetOrCreateTracker(UINT_PTR ControlPc, - StackFrame sf, - EXCEPTION_RECORD* pExceptionRecord, - T_CONTEXT* pContextRecord, - BOOL bAsynchronousThreadStop, - bool fIsFirstPass, - StackTraceState* pSTState); - static void ResumeExecution(T_CONTEXT* pContextRecord); @@ -529,8 +463,6 @@ class ExceptionTracker : public ExceptionTrackerBase INDEBUG(inline BOOL ThrowableIsValid()); - bool HandleNestedExceptionEscape(StackFrame sf, bool fIsFirstPass); - #if defined(DEBUGGING_SUPPORTED) void MakeCallbacksRelatedToHandler(bool fBeforeCallingHandler, From 66c03030b901faa03bd33463d042e70e1df158b2 Mon Sep 17 00:00:00 2001 From: Filip Navara Date: Tue, 1 Apr 2025 22:49:48 +0200 Subject: [PATCH 2/6] Delete more --- src/coreclr/vm/exceptionhandling.cpp | 28 ---------------------------- src/coreclr/vm/exceptionhandling.h | 2 -- 2 files changed, 30 deletions(-) diff --git a/src/coreclr/vm/exceptionhandling.cpp b/src/coreclr/vm/exceptionhandling.cpp index 5568604cea03b2..4ae72cbbabb252 100644 --- a/src/coreclr/vm/exceptionhandling.cpp +++ b/src/coreclr/vm/exceptionhandling.cpp @@ -1081,34 +1081,6 @@ void CheckForRudeAbort(Thread* pThread, bool fIsFirstPass) } } -// static -void ExceptionTracker::DebugLogTrackerRanges(_In_z_ const char *pszTag) -{ -#ifdef _DEBUG - CONTRACTL - { - MODE_ANY; - GC_NOTRIGGER; - NOTHROW; - } - CONTRACTL_END; - - Thread* pThread = GetThreadNULLOk(); - ExceptionTrackerBase* pTracker = pThread ? pThread->GetExceptionState()->m_pCurrentTracker : NULL; - - int i = 0; - - while (pTracker) - { - EH_LOG((LL_INFO100, "%s:|%02d| %p: (%p %p) %s\n", pszTag, i, pTracker, pTracker->m_ScannedStackRange.GetLowerBound().SP, pTracker->m_ScannedStackRange.GetUpperBound().SP, - pTracker->IsInFirstPass() ? "1st pass" : "2nd pass" - )); - pTracker = pTracker->m_pPrevNestedInfo; - i++; - } -#endif // _DEBUG -} - #if defined(DEBUGGING_SUPPORTED) BOOL NotifyDebuggerOfStub(Thread* pThread, Frame* pCurrentFrame) diff --git a/src/coreclr/vm/exceptionhandling.h b/src/coreclr/vm/exceptionhandling.h index a7e9018f613674..5eace883556c87 100644 --- a/src/coreclr/vm/exceptionhandling.h +++ b/src/coreclr/vm/exceptionhandling.h @@ -415,8 +415,6 @@ class ExceptionTracker : public ExceptionTrackerBase void ResetLimitFrame(); - static void DebugLogTrackerRanges(_In_z_ const char *pszTag); - bool IsStackOverflowException(); #if defined(TARGET_UNIX) && !defined(CROSS_COMPILE) From 11ced0a5c564a54d9417af32b909f68a5e8d17b3 Mon Sep 17 00:00:00 2001 From: Filip Navara Date: Tue, 1 Apr 2025 22:51:56 +0200 Subject: [PATCH 3/6] Delete more --- src/coreclr/vm/exceptionhandling.cpp | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/src/coreclr/vm/exceptionhandling.cpp b/src/coreclr/vm/exceptionhandling.cpp index 4ae72cbbabb252..d671c7814e144f 100644 --- a/src/coreclr/vm/exceptionhandling.cpp +++ b/src/coreclr/vm/exceptionhandling.cpp @@ -1063,24 +1063,6 @@ bool FixNonvolatileRegisters(UINT_PTR uOriginalSP, return true; } -void CheckForRudeAbort(Thread* pThread, bool fIsFirstPass) -{ - if (fIsFirstPass && pThread->IsRudeAbort()) - { - GCX_COOP(); - OBJECTREF throwable = pThread->GetThrowable(); - if (throwable == NULL || !IsExceptionOfType(kThreadAbortException, &throwable)) - { - pThread->SafeSetThrowables(CLRException::GetBestThreadAbortException()); - } - - if (!pThread->IsRudeAbortInitiated()) - { - pThread->PreWorkForThreadAbort(); - } - } -} - #if defined(DEBUGGING_SUPPORTED) BOOL NotifyDebuggerOfStub(Thread* pThread, Frame* pCurrentFrame) From 9786620a4441a1b3087bef5604e863048805c106 Mon Sep 17 00:00:00 2001 From: Filip Navara Date: Tue, 1 Apr 2025 23:06:16 +0200 Subject: [PATCH 4/6] Delete more --- src/coreclr/vm/exceptionhandling.cpp | 498 --------------------------- src/coreclr/vm/exceptionhandling.h | 19 - 2 files changed, 517 deletions(-) diff --git a/src/coreclr/vm/exceptionhandling.cpp b/src/coreclr/vm/exceptionhandling.cpp index d671c7814e144f..7a4841f73d84b3 100644 --- a/src/coreclr/vm/exceptionhandling.cpp +++ b/src/coreclr/vm/exceptionhandling.cpp @@ -134,12 +134,6 @@ static void DoEHLog(DWORD lvl, _In_z_ const char *fmt, ...); TrackerAllocator g_theTrackerAllocator; uint32_t g_exceptionCount; -bool FixNonvolatileRegisters(UINT_PTR uOriginalSP, - Thread* pThread, - CONTEXT* pContextRecord, - bool fAborting - ); - void FixContext(PCONTEXT pContextRecord) { #define FIXUPREG(reg, value) \ @@ -367,88 +361,6 @@ StackWalkAction UpdateObjectRefInResumeContextCallback(CrawlFrame* pCF, LPVOID p } -// -// Locates the locations of the nonvolatile registers. This will be used to -// retrieve the latest values of the object references before we resume -// execution from an exception. -// -//static -bool ExceptionTracker::FindNonvolatileRegisterPointers(Thread* pThread, UINT_PTR uOriginalSP, REGDISPLAY* pRegDisplay, TADDR uResumeFrameFP) -{ - CONTRACTL - { - MODE_ANY; - NOTHROW; - GC_NOTRIGGER; - } - CONTRACTL_END; - - // - // Find the highest frame below the resume frame that will update the - // REGDISPLAY. A normal StackWalkFrames will RtlVirtualUnwind through all - // managed frames on the stack, so this avoids some unnecessary work. The - // frame we find will have all of the nonvolatile registers/other state - // needed to start a managed unwind from that point. - // - Frame *pHighestFrameWithRegisters = NULL; - Frame *pFrame = pThread->GetFrame(); - - while ((UINT_PTR)pFrame < uOriginalSP) - { - if (pFrame->NeedsUpdateRegDisplay()) - pHighestFrameWithRegisters = pFrame; - - pFrame = pFrame->Next(); - } - - // - // Do a stack walk from this frame. This may find a higher frame within - // the resume frame (ex. inlined pinvoke frame). This will also update - // the REGDISPLAY pointers if any intervening managed frames saved - // nonvolatile registers. - // - - UpdateObjectRefInResumeContextCallbackState state; - - state.uResumeSP = uOriginalSP; - state.uResumeFrameFP = uResumeFrameFP; - state.uICFCalleeSavedFP = 0; - state.pHighestFrameWithRegisters = pHighestFrameWithRegisters; - - INDEBUG(state.nFrames = 0); - INDEBUG(state.fFound = false); - - pThread->StackWalkFramesEx(pRegDisplay, &UpdateObjectRefInResumeContextCallback, &state, 0, pHighestFrameWithRegisters); - - // For managed exceptions, we should at least find a HelperMethodFrame (the one we put in IL_Throw()). - // For native exceptions such as AV's, we should at least find the FaultingExceptionFrame. - // If we don't find anything, then we must have hit an SO when we are trying to erect an HMF. - // Bail out in such situations. - // - // Note that pinvoke frames may be inlined in a managed method, so we cannot use the child SP (a.k.a. the current SP) - // to check for explicit frames "higher" on the stack ("higher" here means closer to the leaf frame). The stackwalker - // knows how to deal with inlined pinvoke frames, and it issues callbacks for them before issuing the callback for the - // containing managed method. So we have to do this check after we are done with the stackwalk. - pHighestFrameWithRegisters = state.pHighestFrameWithRegisters; - if (pHighestFrameWithRegisters == NULL) - { - return false; - } - - CONSISTENCY_CHECK(state.nFrames); - CONSISTENCY_CHECK(state.fFound); - CONSISTENCY_CHECK(NULL != pHighestFrameWithRegisters); - - // - // Now the REGDISPLAY has been unwound to the resume frame. The - // nonvolatile registers will either point into pHighestFrameWithRegisters, - // an inlined pinvoke frame, or into calling managed frames. - // - - return true; -} - - //static void ExceptionTracker::UpdateNonvolatileRegisters(CONTEXT *pContextRecord, REGDISPLAY *pRegDisplay, bool fAborting) { @@ -618,248 +530,6 @@ bool ExceptionTracker::IsStackOverflowException() return false; } -UINT_PTR ExceptionTracker::CallCatchHandler(CONTEXT* pContextRecord, bool* pfAborting /*= NULL*/) -{ - CONTRACTL - { - MODE_COOPERATIVE; - GC_TRIGGERS; - THROWS; - - PRECONDITION(CheckPointer(pContextRecord, NULL_OK)); - } - CONTRACTL_END; - - UINT_PTR uResumePC = 0; - ULONG_PTR ulRelOffset; - StackFrame sfStackFp = m_sfResumeStackFrame; - Thread* pThread = m_pThread; - MethodDesc* pMD = m_pMethodDescOfCatcher; - bool fIntercepted = false; - - ThreadExceptionState* pExState = pThread->GetExceptionState(); - -#if defined(DEBUGGING_SUPPORTED) - - // If the exception is intercepted, use the information stored in the DebuggerExState to resume the - // exception instead of calling the catch clause (there may not even be one). - if (pExState->GetFlags()->DebuggerInterceptInfo()) - { - _ASSERTE(pMD != NULL); - - // retrieve the interception information - pExState->GetDebuggerState()->GetDebuggerInterceptInfo(NULL, NULL, (PBYTE*)&(sfStackFp.SP), &ulRelOffset, NULL); - - PCODE pStartAddress = pMD->GetNativeCode(); - - EECodeInfo codeInfo(pStartAddress); - _ASSERTE(codeInfo.IsValid()); - - // Note that the value returned for ulRelOffset is actually the offset, - // so we need to adjust it to get the actual IP. - _ASSERTE(FitsIn(ulRelOffset)); - uResumePC = codeInfo.GetJitManager()->GetCodeAddressForRelOffset(codeInfo.GetMethodToken(), static_cast(ulRelOffset)); - - // Either we haven't set m_uResumeStackFrame (for unhandled managed exceptions), or we have set it - // and it equals to MemoryStackFp. - _ASSERTE(m_sfResumeStackFrame.IsNull() || m_sfResumeStackFrame == sfStackFp); - - fIntercepted = true; - } -#endif // DEBUGGING_SUPPORTED - - _ASSERTE(!sfStackFp.IsNull()); - - m_sfResumeStackFrame.Clear(); - m_pMethodDescOfCatcher = NULL; - - _ASSERTE(pContextRecord); - - // - // call the handler - // - EH_LOG((LL_INFO100, " calling catch at 0x%p\n", m_uCatchToCallPC)); - - // do not call the catch clause if the exception is intercepted - if (!fIntercepted) - { - _ASSERTE(m_uCatchToCallPC != 0 && m_pClauseForCatchToken != NULL); - uResumePC = CallHandler(m_uCatchToCallPC, sfStackFp, &m_ClauseForCatch, pMD, Catch, pContextRecord); - } - else - { - // Since the exception has been intercepted and we could resuming execution at any - // user-specified arbitrary location, reset the EH clause index and EstablisherFrame - // we may have saved for addressing any potential ThreadAbort raise. - // - // This is done since the saved EH clause index is related to the catch block executed, - // which does not happen in interception. As user specifies where we resume execution, - // we let that behaviour override the index and pretend as if we have no index available. - m_dwIndexClauseForCatch = 0; - m_sfEstablisherOfActualHandlerFrame.Clear(); - m_sfCallerOfActualHandlerFrame.Clear(); - } - - EH_LOG((LL_INFO100, " resume address should be 0x%p\n", uResumePC)); - - // - // Our tracker may have gone away at this point, don't reference it. - // - - return FinishSecondPass(pThread, uResumePC, sfStackFp, pContextRecord, this, pfAborting); -} - -// static -UINT_PTR ExceptionTracker::FinishSecondPass( - Thread* pThread, - UINT_PTR uResumePC, - StackFrame sf, - CONTEXT* pContextRecord, - ExceptionTracker* pTracker, - bool* pfAborting /*= NULL*/) -{ - CONTRACTL - { - MODE_COOPERATIVE; - GC_NOTRIGGER; - NOTHROW; - PRECONDITION(CheckPointer(pThread, NULL_NOT_OK)); - PRECONDITION(CheckPointer((void*)uResumePC, NULL_NOT_OK)); - PRECONDITION(CheckPointer(pContextRecord, NULL_OK)); - } - CONTRACTL_END; - - // Between the time when we pop the ExceptionTracker for the current exception and the time - // when we actually resume execution, it is unsafe to start a funclet-skipping stackwalk. - // So we set a flag here to indicate that we are in this time window. The only user of this - // information right now is the profiler. - ThreadExceptionFlagHolder tefHolder(ThreadExceptionState::TEF_InconsistentExceptionState); - -#ifdef DEBUGGING_SUPPORTED - // This must be done before we pop the trackers. - BOOL fIntercepted = pThread->GetExceptionState()->GetFlags()->DebuggerInterceptInfo(); -#endif // DEBUGGING_SUPPORTED - - // Since we may [re]raise ThreadAbort post the catch block execution, - // save the index, and Establisher, of the EH clause corresponding to the handler - // we just executed before we release the tracker. This will be used to ensure that reraise - // proceeds forward and not get stuck in a loop. Refer to - // ExceptionTracker::ProcessManagedCallFrame for details. - DWORD ehClauseCurrentHandlerIndex = pTracker->GetCatchHandlerExceptionClauseIndex(); - StackFrame sfEstablisherOfActualHandlerFrame = pTracker->GetEstablisherOfActualHandlingFrame(); - - EH_LOG((LL_INFO100, "second pass finished\n")); - EH_LOG((LL_INFO100, "cleaning up ExceptionTracker state\n")); - - // Release the exception trackers till the current (specified) frame. - ExceptionTracker::PopTrackers(sf, true); - - // This will set the last thrown to be either null if we have handled all the exceptions in the nested chain or - // to whatever the current exception is. - // - // 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 last thrown object. - // - // If an exception is active, we dont want to reset the LastThrownObject to NULL as the active exception - // might be represented by a tracker created in the second pass (refer to - // CEHelper::SetupCorruptionSeverityForActiveExceptionInUnwindPass to understand how exception trackers can be - // created in the 2nd pass on 64bit) that does not have a throwable attached to it. Thus, if this exception - // is caught in the VM and it attempts to get the LastThrownObject using GET_THROWABLE macro, then it should be available. - // - // But, if the active exception tracker remains consistent in the 2nd pass (which will happen if the exception is caught - // in managed code), then the call to SafeUpdateLastThrownObject below will automatically update the LTO as per the - // active exception. - if (!pThread->GetExceptionState()->IsExceptionInProgress()) - { - pThread->SafeSetLastThrownObject(NULL); - } - - // Sync managed exception state, for the managed thread, based upon any active exception tracker - pThread->SyncManagedExceptionState(false); - - // - // If we are aborting, we should not resume execution. Instead, we raise another - // exception. However, we do this by resuming execution at our thread redirecter - // function (RedirectForThrowControl), which is the same process we use for async - // thread stops. This redirecter function will cover the stack frame and register - // stack frame and then throw an exception. When we first see the exception thrown - // by this redirecter, we fixup the context for the thread stackwalk by copying - // pThread->m_OSContext into the dispatcher context and restarting the exception - // dispatch. As a result, we need to save off the "correct" resume context before - // we resume so the exception processing can work properly after redirect. A side - // benefit of this mechanism is that it makes synchronous and async thread abort - // use exactly the same codepaths. - // - UINT_PTR uAbortAddr = 0; - -#if defined(DEBUGGING_SUPPORTED) - // Don't honour thread abort requests at this time for intercepted exceptions. - if (fIntercepted) - { - uAbortAddr = 0; - } - else -#endif // !DEBUGGING_SUPPORTED - { - CopyOSContext(pThread->m_OSContext, pContextRecord); - SetIP(pThread->m_OSContext, (PCODE)uResumePC); -#if defined(TARGET_UNIX) && defined(TARGET_X86) - uAbortAddr = NULL; -#else - uAbortAddr = (UINT_PTR)COMPlusCheckForAbort(uResumePC); -#endif - } - - if (uAbortAddr) - { - if (pfAborting != NULL) - { - *pfAborting = true; - } - - EH_LOG((LL_INFO100, "thread abort in progress, resuming thread under control...\n")); - - // We are aborting, so keep the reference to the current EH clause index. - // We will use this when the exception is reraised and we begin commencing - // exception dispatch. This is done in ExceptionTracker::ProcessOSExceptionNotification. - // - // The "if" condition below can be false if the exception has been intercepted (refer to - // ExceptionTracker::CallCatchHandler for details) - if ((ehClauseCurrentHandlerIndex > 0) && (!sfEstablisherOfActualHandlerFrame.IsNull())) - { - pThread->m_dwIndexClauseForCatch = ehClauseCurrentHandlerIndex; - pThread->m_sfEstablisherOfActualHandlerFrame = sfEstablisherOfActualHandlerFrame; - } - - CONSISTENCY_CHECK(CheckPointer(pContextRecord)); - - STRESS_LOG1(LF_EH, LL_INFO10, "resume under control: ip: %p\n", uResumePC); - -#ifdef TARGET_AMD64 -#ifdef TARGET_UNIX - pContextRecord->Rdi = uResumePC; - pContextRecord->Rsi = GetIP(pThread->GetAbortContext()); -#else - pContextRecord->Rcx = uResumePC; -#endif -#elif defined(TARGET_ARM) || defined(TARGET_ARM64) - // On ARM & ARM64, we save off the original PC in Lr. This is the same as done - // in HandleManagedFault for H/W generated exceptions. - pContextRecord->Lr = uResumePC; -#elif defined(TARGET_LOONGARCH64) || defined(TARGET_RISCV64) - pContextRecord->Ra = uResumePC; -#endif - - uResumePC = uAbortAddr; - } - - CONSISTENCY_CHECK(pThread->DetermineIfGuardPagePresent()); - - EH_LOG((LL_INFO100, "FinishSecondPass complete, uResumePC = %p, current SP = %p\n", uResumePC, GetCurrentSP())); - return uResumePC; -} - void CleanUpForSecondPass(Thread* pThread, bool fIsSO, LPVOID MemoryStackFpForFrameChain, LPVOID MemoryStackFp); static void PopExplicitFrames(Thread *pThread, void *targetSp, void *targetCallerSp, bool popGCFrames = true) @@ -1018,51 +688,6 @@ ProcessCLRException(IN PEXCEPTION_RECORD pExceptionRecord, UNREACHABLE(); } -// When we hit a native exception such as an AV in managed code, we put up a FaultingExceptionFrame which saves all the -// non-volatile registers. The GC may update these registers if they contain object references. However, the CONTEXT -// with which we are going to resume execution doesn't have these updated values. Thus, we need to fix up the non-volatile -// registers in the CONTEXT with the updated ones stored in the FaultingExceptionFrame. To do so properly, we need -// to perform a full stackwalk. -bool FixNonvolatileRegisters(UINT_PTR uOriginalSP, - Thread* pThread, - CONTEXT* pContextRecord, - bool fAborting - ) -{ - CONTRACTL - { - MODE_COOPERATIVE; - NOTHROW; - GC_NOTRIGGER; - } - CONTRACTL_END; - - CONTEXT _ctx = {}; - - // Ctor will initialize it to NULL - REGDISPLAY regdisp; - - pThread->FillRegDisplay(®disp, &_ctx); - - bool fFound = ExceptionTracker::FindNonvolatileRegisterPointers(pThread, uOriginalSP, ®disp, GetFP(pContextRecord)); - if (!fFound) - { - return false; - } - - { - // - // GC must NOT occur once the frames have been popped because - // the values in the unwound CONTEXT are not GC-protected. - // - GCX_FORBID(); - - ExceptionTracker::UpdateNonvolatileRegisters(pContextRecord, ®disp, fAborting); - } - - return true; -} - #if defined(DEBUGGING_SUPPORTED) BOOL NotifyDebuggerOfStub(Thread* pThread, Frame* pCurrentFrame) @@ -1150,108 +775,6 @@ static inline TADDR GetFrameRestoreBase(PCONTEXT pContextRecord) #endif // USE_FUNCLET_CALL_HELPER -DWORD_PTR ExceptionTracker::CallHandler( - UINT_PTR uHandlerStartPC, - StackFrame sf, - EE_ILEXCEPTION_CLAUSE* pEHClause, - MethodDesc* pMD, - EHFuncletType funcletType, - PCONTEXT pContextRecord) -{ - STATIC_CONTRACT_THROWS; - STATIC_CONTRACT_GC_TRIGGERS; - STATIC_CONTRACT_MODE_COOPERATIVE; - - DWORD_PTR dwResumePC; - OBJECTREF throwable; - HandlerFn* pfnHandler = (HandlerFn*)uHandlerStartPC; - - EH_LOG((LL_INFO100, " calling handler at 0x%p, sp = 0x%p\n", uHandlerStartPC, sf.SP)); - - Thread* pThread = GetThread(); - - // The first parameter specifies whether we want to make callbacks before (true) or after (false) - // calling the handler. - MakeCallbacksRelatedToHandler(true, pThread, pMD, pEHClause, uHandlerStartPC, sf); - - _ASSERTE(pThread->DetermineIfGuardPagePresent()); - - throwable = PossiblyUnwrapThrowable(pThread->GetThrowable(), pMD->GetAssembly()); - - // Stores the current SP and BSP, which will be the caller SP and BSP for the funclet. - // Note that we are making the assumption here that the SP and BSP don't change from this point - // forward until we actually make the call to the funclet. If it's not the case then we will need - // some sort of assembly wrappers to help us out. - CallerStackFrame csfFunclet = CallerStackFrame((UINT_PTR)GetCurrentSP()); - this->m_EHClauseInfo.SetManagedCodeEntered(TRUE); - this->m_EHClauseInfo.SetCallerStackFrame(csfFunclet); - - switch(funcletType) - { - case EHFuncletType::Filter: - ETW::ExceptionLog::ExceptionFilterBegin(pMD, (PVOID)uHandlerStartPC); - break; - case EHFuncletType::FaultFinally: - ETW::ExceptionLog::ExceptionFinallyBegin(pMD, (PVOID)uHandlerStartPC); - break; - case EHFuncletType::Catch: - ETW::ExceptionLog::ExceptionCatchBegin(pMD, (PVOID)uHandlerStartPC); - break; - } - -#ifdef USE_FUNCLET_CALL_HELPER - // Invoke the funclet. We pass throwable only when invoking the catch block. - // Since the actual caller of the funclet is the assembly helper, pass the reference - // to the CallerStackFrame instance so that it can be updated. - CallerStackFrame* pCallerStackFrame = this->m_EHClauseInfo.GetCallerStackFrameForEHClauseReference(); - UINT_PTR *pFuncletCallerSP = &(pCallerStackFrame->SP); - if (funcletType != EHFuncletType::Filter) - { - dwResumePC = CallEHFunclet((funcletType == EHFuncletType::Catch)?OBJECTREFToObject(throwable):(Object *)NULL, - CastHandlerFn(pfnHandler), - GetFirstNonVolatileRegisterAddress(pContextRecord), - pFuncletCallerSP); - } - else - { - // For invoking IL filter funclet, we pass the CallerSP to the funclet using which - // it will retrieve the framepointer for accessing the locals in the parent - // method. - dwResumePC = CallEHFilterFunclet(OBJECTREFToObject(throwable), - GetFrameRestoreBase(pContextRecord), - CastHandlerFn(pfnHandler), - pFuncletCallerSP); - } -#else // USE_FUNCLET_CALL_HELPER - // - // Invoke the funclet. - // - dwResumePC = pfnHandler(sf.SP, OBJECTREFToObject(throwable)); -#endif // !USE_FUNCLET_CALL_HELPER - - switch(funcletType) - { - case EHFuncletType::Filter: - ETW::ExceptionLog::ExceptionFilterEnd(); - break; - case EHFuncletType::FaultFinally: - ETW::ExceptionLog::ExceptionFinallyEnd(); - break; - case EHFuncletType::Catch: - ETW::ExceptionLog::ExceptionCatchEnd(); - ETW::ExceptionLog::ExceptionThrownEnd(); - break; - } - - this->m_EHClauseInfo.SetManagedCodeEntered(FALSE); - - // The first parameter specifies whether we want to make callbacks before (true) or after (false) - // calling the handler. - MakeCallbacksRelatedToHandler(false, pThread, pMD, pEHClause, uHandlerStartPC, sf); - - return dwResumePC; -} - #undef OPTIONAL_SO_CLEANUP_UNWIND #define OPTIONAL_SO_CLEANUP_UNWIND(pThread, pFrame) @@ -1309,13 +832,6 @@ void ExceptionTracker::PopTrackers( return; } -void ExceptionTracker::ResetLimitFrame() -{ - WRAPPER_NO_CONTRACT; - - m_pLimitFrame = m_pThread->GetFrame(); -} - // // static void ExceptionTracker::ResumeExecution(CONTEXT* pContextRecord) @@ -1376,20 +892,6 @@ OBJECTREF ExceptionTracker::CreateThrowable( return oThrowable; } -// -//static -BOOL ExceptionTracker::ClauseCoversPC( - EE_ILEXCEPTION_CLAUSE* pEHClause, - DWORD dwOffset - ) -{ - // TryStartPC and TryEndPC are offsets relative to the start - // of the method so we can just compare them to the offset returned - // by JitCodeToMethodInfo. - // - return ((pEHClause->TryStartPC <= dwOffset) && (dwOffset < pEHClause->TryEndPC)); -} - #if defined(DEBUGGING_SUPPORTED) bool ExceptionTracker::IsFilterStartOffset(EE_ILEXCEPTION_CLAUSE* pEHClause, DWORD_PTR dwHandlerStartPC) diff --git a/src/coreclr/vm/exceptionhandling.h b/src/coreclr/vm/exceptionhandling.h index 5eace883556c87..a9ccaf717b43c1 100644 --- a/src/coreclr/vm/exceptionhandling.h +++ b/src/coreclr/vm/exceptionhandling.h @@ -413,8 +413,6 @@ class ExceptionTracker : public ExceptionTrackerBase static void ResumeExecution(T_CONTEXT* pContextRecord); - void ResetLimitFrame(); - bool IsStackOverflowException(); #if defined(TARGET_UNIX) && !defined(CROSS_COMPILE) @@ -428,18 +426,6 @@ class ExceptionTracker : public ExceptionTrackerBase #endif // TARGET_UNIX && !CROSS_COMPILE private: - DWORD_PTR - CallHandler(UINT_PTR dwHandlerStartPC, - StackFrame sf, - EE_ILEXCEPTION_CLAUSE* pEHClause, - MethodDesc* pMD, - EHFuncletType funcletType, - PT_CONTEXT pContextRecord); - - inline static BOOL - ClauseCoversPC(EE_ILEXCEPTION_CLAUSE* pEHClause, - DWORD dwOffset); - static bool IsFilterStartOffset(EE_ILEXCEPTION_CLAUSE* pEHClause, DWORD_PTR dwHandlerStartPC); @@ -500,11 +486,6 @@ class ExceptionTracker : public ExceptionTrackerBase public: - static UINT_PTR FinishSecondPass(Thread* pThread, UINT_PTR uResumePC, StackFrame sf, - T_CONTEXT* pContextRecord, ExceptionTracker *pTracker, bool* pfAborting = NULL); - UINT_PTR CallCatchHandler(T_CONTEXT* pContextRecord, bool* pfAborting = NULL); - - static bool FindNonvolatileRegisterPointers(Thread* pThread, UINT_PTR uOriginalSP, REGDISPLAY* pRegDisplay, TADDR uResumeFrameFP); static void UpdateNonvolatileRegisters(T_CONTEXT* pContextRecord, REGDISPLAY *pRegDisplay, bool fAborting); PTR_Frame GetLimitFrame() From 6420584ce8a24c37027a795e2a0748851aa3de8a Mon Sep 17 00:00:00 2001 From: Filip Navara Date: Tue, 1 Apr 2025 23:16:34 +0200 Subject: [PATCH 5/6] Delete more --- src/coreclr/vm/exceptionhandling.cpp | 494 --------------------------- src/coreclr/vm/exceptionhandling.h | 25 -- 2 files changed, 519 deletions(-) diff --git a/src/coreclr/vm/exceptionhandling.cpp b/src/coreclr/vm/exceptionhandling.cpp index 7a4841f73d84b3..7743374ba3a865 100644 --- a/src/coreclr/vm/exceptionhandling.cpp +++ b/src/coreclr/vm/exceptionhandling.cpp @@ -49,10 +49,6 @@ ClrUnwindEx(EXCEPTION_RECORD* pExceptionRecord, UINT_PTR TargetFrameSp); #endif // !TARGET_UNIX -#if defined(TARGET_UNIX) && !defined(DACCESS_COMPILE) -VOID UnwindManagedExceptionPass2(PAL_SEHException& ex, CONTEXT* unwindStartContext); -#endif // TARGET_UNIX && !DACCESS_COMPILE - #ifdef USE_CURRENT_CONTEXT_IN_FILTER inline void CaptureNonvolatileRegisters(PKNONVOLATILE_CONTEXT pNonvolatileContext, PCONTEXT pContext) { @@ -832,34 +828,6 @@ void ExceptionTracker::PopTrackers( return; } -// -// static -void ExceptionTracker::ResumeExecution(CONTEXT* pContextRecord) -{ - // - // This method never returns, so it will leave its - // state on the thread if useing dynamic contracts. - // - STATIC_CONTRACT_MODE_COOPERATIVE; - STATIC_CONTRACT_GC_NOTRIGGER; - STATIC_CONTRACT_NOTHROW; - - AMD64_ONLY(STRESS_LOG4(LF_GCROOTS, LL_INFO100, "Resuming after exception at %p, rbx=%p, rsi=%p, rdi=%p\n", - GetIP(pContextRecord), - pContextRecord->Rbx, - pContextRecord->Rsi, - pContextRecord->Rdi)); - - EH_LOG((LL_INFO100, "resuming execution at 0x%p\n", GetIP(pContextRecord))); - EH_LOG((LL_INFO100, "^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n")); - - ClrRestoreNonvolatileContext(pContextRecord); - - UNREACHABLE(); - // - // doesn't return - // -} // // static @@ -1063,18 +1031,7 @@ inline bool ExceptionTracker::IsValid() return fRetVal; } -BOOL ExceptionTracker::ThrowableIsValid() -{ - GCX_COOP(); - CONSISTENCY_CHECK(IsValid()); - - BOOL isValid = FALSE; - - isValid = (m_pThread->GetThrowable() != NULL); - - return isValid; -} // // static UINT_PTR ExceptionTracker::DebugComputeNestingLevel() @@ -1237,432 +1194,8 @@ static VOID UpdateContextForPropagationCallback( UpdateContextForPropagationCallback(ex.ManagedToNativeExceptionCallback, ex.ManagedToNativeExceptionCallbackContext, startContext); } -//--------------------------------------------------------------------------------------- -// -// This functions performs an unwind procedure for a managed exception. The stack is unwound -// until the target frame is reached. For each frame we use its PC value to find -// a handler using information that has been built by JIT. -// -// Arguments: -// ex - the PAL_SEHException representing the managed exception -// unwindStartContext - the context that the unwind should start at. Either the original exception -// context (when the exception didn't cross native frames) or the first managed -// frame after crossing native frames. -// -VOID UnwindManagedExceptionPass2(PAL_SEHException& ex, CONTEXT* unwindStartContext) -{ - UINT_PTR controlPc; - PVOID sp; - EXCEPTION_DISPOSITION disposition; - CONTEXT* currentFrameContext; - CONTEXT* callerFrameContext; - CONTEXT contextStorage; - DISPATCHER_CONTEXT dispatcherContext; - EECodeInfo codeInfo; - ULONG_PTR establisherFrame = 0; - PVOID handlerData; - - // Indicate that we are performing second pass. - ex.GetExceptionRecord()->ExceptionFlags = EXCEPTION_UNWINDING; - - currentFrameContext = unwindStartContext; - callerFrameContext = &contextStorage; - - memset(&dispatcherContext, 0, sizeof(DISPATCHER_CONTEXT)); - disposition = ExceptionContinueSearch; - - do - { - controlPc = GetIP(currentFrameContext); - - codeInfo.Init(controlPc); - - dispatcherContext.FunctionEntry = codeInfo.GetFunctionEntry(); - dispatcherContext.ControlPc = controlPc; - dispatcherContext.ImageBase = codeInfo.GetModuleBase(); -#ifdef ADJUST_PC_UNWOUND_TO_CALL - dispatcherContext.ControlPcIsUnwound = !!(currentFrameContext->ContextFlags & CONTEXT_UNWOUND_TO_CALL); -#endif - // Check whether we have a function table entry for the current controlPC. - // If yes, then call RtlVirtualUnwind to get the establisher frame pointer. - if (dispatcherContext.FunctionEntry != NULL) - { - // Create a copy of the current context because we don't want - // the current context record to be updated by RtlVirtualUnwind. - memcpy(callerFrameContext, currentFrameContext, sizeof(CONTEXT)); - RtlVirtualUnwind(UNW_FLAG_EHANDLER, - dispatcherContext.ImageBase, - dispatcherContext.ControlPc, - dispatcherContext.FunctionEntry, - callerFrameContext, - &handlerData, - &establisherFrame, - NULL); - - // Make sure that the establisher frame pointer is within stack boundaries - // and we did not go below that target frame. - // TODO: make sure the establisher frame is properly aligned. - if (!Thread::IsAddressInCurrentStack((void*)establisherFrame) || establisherFrame > ex.TargetFrameSp) - { - // TODO: add better error handling - UNREACHABLE(); - } - - dispatcherContext.EstablisherFrame = establisherFrame; - dispatcherContext.ContextRecord = currentFrameContext; - - EXCEPTION_RECORD* exceptionRecord = ex.GetExceptionRecord(); - - if (establisherFrame == ex.TargetFrameSp) - { - // We have reached the frame that will handle the exception. - ex.GetExceptionRecord()->ExceptionFlags |= EXCEPTION_TARGET_UNWIND; - ExceptionTracker* pTracker = (ExceptionTracker*)GetThread()->GetExceptionState()->GetCurrentExceptionTracker(); - pTracker->TakeExceptionPointersOwnership(&ex); - } - - // Perform unwinding of the current frame - disposition = ProcessCLRException(exceptionRecord, - (void*)establisherFrame, - currentFrameContext, - &dispatcherContext); - - if (disposition == ExceptionContinueSearch) - { - // Exception handler not found. Try the parent frame. - CONTEXT* temp = currentFrameContext; - currentFrameContext = callerFrameContext; - callerFrameContext = temp; - } - else - { - UNREACHABLE(); - } - } - else - { - Thread::VirtualUnwindLeafCallFrame(currentFrameContext); - } - - controlPc = GetIP(currentFrameContext); - sp = (PVOID)GetSP(currentFrameContext); - - // Check whether we are crossing managed-to-native boundary - if (!ExecutionManager::IsManagedCode(controlPc)) - { - // Return back to the UnwindManagedExceptionPass1 and let it unwind the native frames - { - GCX_COOP(); - // Pop all frames that are below the block of native frames and that would be - // in the unwound part of the stack when UnwindManagedExceptionPass2 is resumed - // at the next managed frame. - - UnwindFrameChain(GetThread(), sp); - // We are going to reclaim the stack range that was scanned by the exception tracker - // until now. We need to reset the explicit frames range so that if GC fires before - // we recreate the tracker at the first managed frame after unwinding the native - // frames, it doesn't attempt to scan the reclaimed stack range. - // We also need to reset the scanned stack range since the scanned frames will be - // obsolete after the unwind of the native frames completes. - ExceptionTracker* pTracker = (ExceptionTracker*)GetThread()->GetExceptionState()->GetCurrentExceptionTracker(); - - // The tracker will only be non-null in the case when we handle the managed exception in - // the runtime native code. - if (pTracker != NULL) - pTracker->CleanupBeforeNativeFramesUnwind(); - } - - if (ex.HasPropagateExceptionCallback()) - { - // A propagation callback was supplied. - STRESS_LOG3(LF_EH, LL_INFO100, "Deferring exception propagation to Callback = %p, IP = %p, SP = %p \n", ex.ManagedToNativeExceptionCallback, controlPc, sp); - - UpdateContextForPropagationCallback(ex, currentFrameContext); - ExceptionTracker::ResumeExecution(currentFrameContext); - } - else - { - // Now we need to unwind the native frames until we reach managed frames again or the exception is - // handled in the native code. - STRESS_LOG2(LF_EH, LL_INFO100, "Unwinding native frames starting at IP = %p, SP = %p \n", controlPc, sp); - PAL_ThrowExceptionFromContext(currentFrameContext, &ex); - } - UNREACHABLE(); - } - - } while (Thread::IsAddressInCurrentStack(sp) && (establisherFrame != ex.TargetFrameSp)); - -#ifdef DEBUGGER_EXCEPTION_INTERCEPTION_SUPPORTED - if ((establisherFrame == ex.TargetFrameSp) && (ex.TargetIp != 0)) - { -#ifdef TARGET_AMD64 -#define RETURN_REG Rax -#elif defined(TARGET_ARM64) -#define RETURN_REG X0 -#elif defined(TARGET_ARM) -#define RETURN_REG R0 -#elif defined(TARGET_X86) -#define RETURN_REG Eax -#else -#error Missing definition of RETURN_REG for the current architecture -#endif - currentFrameContext->RETURN_REG = ex.ReturnValue; - if (ex.GetExceptionRecord()->ExceptionCode != STATUS_UNWIND_CONSOLIDATE) - { - SetIP(currentFrameContext, ex.TargetIp); - } - ExceptionTracker::ResumeExecution(currentFrameContext); - } -#endif // DEBUGGER_EXCEPTION_INTERCEPTION_SUPPORTED - - _ASSERTE(!"UnwindManagedExceptionPass2: Unwinding failed. Reached the end of the stack"); - EEPOLICY_HANDLE_FATAL_ERROR(COR_E_EXECUTIONENGINE); -} - extern void* g_hostingApiReturnAddress; -//--------------------------------------------------------------------------------------- -// -// This functions performs dispatching of a managed exception. -// It tries to find an exception handler by examining each frame in the call stack. -// The search is started from the managed frame caused the exception to be thrown. -// For each frame we use its PC value to find a handler using information that -// has been built by JIT. If an exception handler is found then this function initiates -// the second pass to unwind the stack and execute the handler. -// -// Arguments: -// ex - a PAL_SEHException that stores information about the managed -// exception that needs to be dispatched. -// frameContext - the context of the first managed frame of the exception call stack -// -VOID DECLSPEC_NORETURN UnwindManagedExceptionPass1(PAL_SEHException& ex, CONTEXT* frameContext) -{ - CONTEXT unwindStartContext; - EXCEPTION_DISPOSITION disposition; - DISPATCHER_CONTEXT dispatcherContext; - EECodeInfo codeInfo; - UINT_PTR controlPc; - ULONG_PTR establisherFrame = 0; - PVOID handlerData; - -#ifdef FEATURE_HIJACK - GetThread()->UnhijackThread(); -#endif - - controlPc = GetIP(frameContext); - unwindStartContext = *frameContext; - - if (!ExecutionManager::IsManagedCode(GetIP(ex.GetContextRecord()))) - { - // This is the first time we see the managed exception, set its context to the managed frame that has caused - // the exception to be thrown - *ex.GetContextRecord() = *frameContext; - - // Move the exception address to the first managed frame on the stack except for the hardware exceptions - // stemming from from a native code out of the well known runtime helpers - if (!ex.IsExternal) - { - ex.GetExceptionRecord()->ExceptionAddress = (VOID*)controlPc; - } - } - - ex.GetExceptionRecord()->ExceptionFlags = 0; - - memset(&dispatcherContext, 0, sizeof(DISPATCHER_CONTEXT)); - disposition = ExceptionContinueSearch; - - do - { - codeInfo.Init(controlPc); - dispatcherContext.FunctionEntry = codeInfo.GetFunctionEntry(); - dispatcherContext.ControlPc = controlPc; - dispatcherContext.ImageBase = codeInfo.GetModuleBase(); -#ifdef ADJUST_PC_UNWOUND_TO_CALL - dispatcherContext.ControlPcIsUnwound = !!(frameContext->ContextFlags & CONTEXT_UNWOUND_TO_CALL); -#endif - - // Check whether we have a function table entry for the current controlPC. - // If yes, then call RtlVirtualUnwind to get the establisher frame pointer - // and then check whether an exception handler exists for the frame. - if (dispatcherContext.FunctionEntry != NULL) - { -#ifdef USE_CURRENT_CONTEXT_IN_FILTER - KNONVOLATILE_CONTEXT currentNonVolatileContext; - CaptureNonvolatileRegisters(¤tNonVolatileContext, frameContext); -#endif // USE_CURRENT_CONTEXT_IN_FILTER - - RtlVirtualUnwind(UNW_FLAG_EHANDLER, - dispatcherContext.ImageBase, - dispatcherContext.ControlPc, - dispatcherContext.FunctionEntry, - frameContext, - &handlerData, - &establisherFrame, - NULL); - - // Make sure that the establisher frame pointer is within stack boundaries. - // TODO: make sure the establisher frame is properly aligned. - if (!Thread::IsAddressInCurrentStack((void*)establisherFrame)) - { - // TODO: add better error handling - UNREACHABLE(); - } - - dispatcherContext.EstablisherFrame = establisherFrame; -#ifdef USE_CURRENT_CONTEXT_IN_FILTER - dispatcherContext.CurrentNonVolatileContextRecord = ¤tNonVolatileContext; -#endif // USE_CURRENT_CONTEXT_IN_FILTER - dispatcherContext.ContextRecord = frameContext; - - // Find exception handler in the current frame - disposition = ProcessCLRException(ex.GetExceptionRecord(), - (void*)establisherFrame, - ex.GetContextRecord(), - &dispatcherContext); - - if (disposition == ExceptionContinueSearch) - { - // Exception handler not found. Try the parent frame. - controlPc = GetIP(frameContext); - } - else if (disposition == ExceptionStackUnwind) - { - // The first pass is complete. We have found the frame that - // will handle the exception. Start the second pass. - ex.TargetFrameSp = establisherFrame; - UnwindManagedExceptionPass2(ex, &unwindStartContext); - } - else - { - UNREACHABLE(); - } - } - else - { - controlPc = Thread::VirtualUnwindLeafCallFrame(frameContext); - } - - bool invalidRevPInvoke; -#ifdef USE_GC_INFO_DECODER - GcInfoDecoder gcInfoDecoder(codeInfo.GetGCInfoToken(), DECODE_REVERSE_PINVOKE_VAR); - invalidRevPInvoke = gcInfoDecoder.GetReversePInvokeFrameStackSlot() != NO_REVERSE_PINVOKE_FRAME; -#else // USE_GC_INFO_DECODER - hdrInfo gcHdrInfo; - DecodeGCHdrInfo(codeInfo.GetGCInfoToken(), 0, &gcHdrInfo); - invalidRevPInvoke = gcHdrInfo.revPInvokeOffset != INVALID_REV_PINVOKE_OFFSET; -#endif // USE_GC_INFO_DECODER - - if (invalidRevPInvoke) - { - ExceptionTracker* pTracker = (ExceptionTracker*)GetThread()->GetExceptionState()->GetCurrentExceptionTracker(); - - void* callbackCxt = NULL; - Interop::ManagedToNativeExceptionCallback callback = Interop::GetPropagatingExceptionCallback( - &codeInfo, - pTracker->GetThrowableAsHandle(), - &callbackCxt); - - // If a callback doesn't exist we immediately crash. - if (callback == NULL) - { - // Propagating exception from a method marked by UnmanagedCallersOnly attribute is prohibited on Unix - if (!GetThread()->HasThreadStateNC(Thread::TSNC_ProcessedUnhandledException)) - { - LONG disposition = InternalUnhandledExceptionFilter_Worker(&ex.ExceptionPointers); - _ASSERTE(disposition == EXCEPTION_CONTINUE_SEARCH); - } - CrashDumpAndTerminateProcess(1); - } - else - { - ex.SetPropagateExceptionCallback(callback, callbackCxt); - _ASSERTE(ex.HasPropagateExceptionCallback()); - - BOOL success = PAL_VirtualUnwind(frameContext, NULL); - if (!success) - { - _ASSERTE(!"UnwindManagedExceptionPass1: PAL_VirtualUnwind failed for propagate exception scenario"); - EEPOLICY_HANDLE_FATAL_ERROR(COR_E_EXECUTIONENGINE); - } - - UINT_PTR sp = GetSP(frameContext); - ex.TargetFrameSp = sp; - UnwindManagedExceptionPass2(ex, &unwindStartContext); - } - - UNREACHABLE(); - } - - // Check whether we are crossing managed-to-native boundary - while (!ExecutionManager::IsManagedCode(controlPc)) - { - if (AdjustContextForVirtualStub(NULL, frameContext)) - { - controlPc = GetIP(frameContext); - break; - } - - if (IsIPInWriteBarrierCodeCopy(controlPc)) - { - // Pretend we were executing the barrier function at its original location so that the unwinder can unwind the frame - controlPc = AdjustWriteBarrierIP(controlPc); - SetIP(frameContext, controlPc); - } - - UINT_PTR sp = GetSP(frameContext); - - BOOL success = PAL_VirtualUnwind(frameContext, NULL); - if (!success) - { - _ASSERTE(!"UnwindManagedExceptionPass1: PAL_VirtualUnwind failed"); - EEPOLICY_HANDLE_FATAL_ERROR(COR_E_EXECUTIONENGINE); - } - - controlPc = GetIP(frameContext); - - STRESS_LOG2(LF_EH, LL_INFO100, "Processing exception at native frame: IP = %p, SP = %p \n", controlPc, sp); - - // Consider the exception unhandled if the unwinding cannot proceed further or if it went past the coreclr_initialize or coreclr_execute_assembly - if ((controlPc == 0) || (controlPc == (UINT_PTR)g_hostingApiReturnAddress)) - { - if (!GetThread()->HasThreadStateNC(Thread::TSNC_ProcessedUnhandledException)) - { - LONG disposition = InternalUnhandledExceptionFilter_Worker(&ex.ExceptionPointers); - _ASSERTE(disposition == EXCEPTION_CONTINUE_SEARCH); - } - CrashDumpAndTerminateProcess(1); - UNREACHABLE(); - } - - UINT_PTR parentSp = GetSP(frameContext); - - // Find all holders on this frame that are in scopes embedded in each other and call their filters. - NativeExceptionHolderBase* holder = nullptr; - while ((holder = NativeExceptionHolderBase::FindNextHolder(holder, (void*)sp, (void*)parentSp)) != nullptr) - { - EXCEPTION_DISPOSITION disposition = holder->InvokeFilter(ex); - if (disposition == EXCEPTION_EXECUTE_HANDLER) - { - // Switch to pass 2 - STRESS_LOG1(LF_EH, LL_INFO100, "First pass finished, found native handler, TargetFrameSp = %p\n", sp); - - ex.TargetFrameSp = sp; - UnwindManagedExceptionPass2(ex, &unwindStartContext); - UNREACHABLE(); - } - - // The EXCEPTION_CONTINUE_EXECUTION is not supported and should never be returned by a filter - _ASSERTE(disposition == EXCEPTION_CONTINUE_SEARCH); - } - } - - } while (Thread::IsAddressInCurrentStack((void*)GetSP(frameContext))); - - _ASSERTE(!"UnwindManagedExceptionPass1: Failed to find a handler. Reached the end of the stack"); - EEPOLICY_HANDLE_FATAL_ERROR(COR_E_EXECUTIONENGINE); - UNREACHABLE(); -} - VOID DECLSPEC_NORETURN DispatchManagedException(PAL_SEHException& ex, bool isHardwareException) { if (!isHardwareException) @@ -3803,33 +3336,6 @@ void ExceptionTracker::ReleaseResources() #endif // DACCESS_COMPILE } -void ExceptionTracker::SetEnclosingClauseInfo(bool fEnclosingClauseIsFunclet, - DWORD dwEnclosingClauseOffset, - UINT_PTR uEnclosingClauseCallerSP) -{ - // Preserve the details of the current frame for GC reporting before - // we apply the nested exception logic below. - this->m_EnclosingClauseInfoForGCReporting = EnclosingClauseInfo(fEnclosingClauseIsFunclet, - dwEnclosingClauseOffset, - uEnclosingClauseCallerSP); - if (this->m_pPrevNestedInfo != NULL) - { - PTR_ExceptionTracker pPrevTracker = (PTR_ExceptionTracker)this->m_pPrevNestedInfo; - CallerStackFrame csfPrevEHClause = pPrevTracker->m_EHClauseInfo.GetCallerStackFrameForEHClause(); - - // Just propagate the information if this is a nested exception. - if (csfPrevEHClause.SP == uEnclosingClauseCallerSP) - { - this->m_EnclosingClauseInfo = pPrevTracker->m_EnclosingClauseInfo; - return; - } - } - - this->m_EnclosingClauseInfo = EnclosingClauseInfo(fEnclosingClauseIsFunclet, - dwEnclosingClauseOffset, - uEnclosingClauseCallerSP); -} - #ifdef DACCESS_COMPILE void ExceptionTrackerBase::EnumMemoryRegions(CLRDataEnumMemoryFlags flags) diff --git a/src/coreclr/vm/exceptionhandling.h b/src/coreclr/vm/exceptionhandling.h index a9ccaf717b43c1..290d611b5dc9d0 100644 --- a/src/coreclr/vm/exceptionhandling.h +++ b/src/coreclr/vm/exceptionhandling.h @@ -410,9 +410,6 @@ class ExceptionTracker : public ExceptionTrackerBase static void PopTrackerIfEscaping(void* pvStackPointer); - static void - ResumeExecution(T_CONTEXT* pContextRecord); - bool IsStackOverflowException(); #if defined(TARGET_UNIX) && !defined(CROSS_COMPILE) @@ -429,24 +426,6 @@ class ExceptionTracker : public ExceptionTrackerBase static bool IsFilterStartOffset(EE_ILEXCEPTION_CLAUSE* pEHClause, DWORD_PTR dwHandlerStartPC); - inline BOOL CanAllocateMemory() - { - CONTRACTL - { - MODE_COOPERATIVE; - NOTHROW; - GC_NOTRIGGER; - } - CONTRACTL_END; - - OBJECTREF oThrowable = GetThrowable(); - - return !(oThrowable == CLRException::GetPreallocatedOutOfMemoryException()) && - !(oThrowable == CLRException::GetPreallocatedStackOverflowException()); - } - - INDEBUG(inline BOOL ThrowableIsValid()); - #if defined(DEBUGGING_SUPPORTED) void MakeCallbacksRelatedToHandler(bool fBeforeCallingHandler, @@ -584,10 +563,6 @@ private: ; void ReleaseResources(); - void SetEnclosingClauseInfo(bool fEnclosingClauseIsFunclet, - DWORD dwEnclosingClauseOffset, - UINT_PTR uEnclosingClauseCallerSP); - struct EnclosingClauseInfo { public: From 8fbac5875adb2dc04c7f76bd84169b627e8c287a Mon Sep 17 00:00:00 2001 From: Filip Navara Date: Tue, 1 Apr 2025 23:38:01 +0200 Subject: [PATCH 6/6] Delete more --- src/coreclr/vm/exceptionhandling.cpp | 100 --------------------------- src/coreclr/vm/exceptionhandling.h | 33 --------- 2 files changed, 133 deletions(-) diff --git a/src/coreclr/vm/exceptionhandling.cpp b/src/coreclr/vm/exceptionhandling.cpp index 7743374ba3a865..046f8fea6d14a0 100644 --- a/src/coreclr/vm/exceptionhandling.cpp +++ b/src/coreclr/vm/exceptionhandling.cpp @@ -516,16 +516,6 @@ LPCSTR DebugGetExceptionDispositionName(EXCEPTION_DISPOSITION disp) } #endif // _DEBUG -bool ExceptionTracker::IsStackOverflowException() -{ - if (m_pThread->GetThrowableAsHandle() == g_pPreallocatedStackOverflowException) - { - return true; - } - - return false; -} - void CleanUpForSecondPass(Thread* pThread, bool fIsSO, LPVOID MemoryStackFpForFrameChain, LPVOID MemoryStackFp); static void PopExplicitFrames(Thread *pThread, void *targetSp, void *targetCallerSp, bool popGCFrames = true) @@ -862,96 +852,6 @@ OBJECTREF ExceptionTracker::CreateThrowable( #if defined(DEBUGGING_SUPPORTED) -bool ExceptionTracker::IsFilterStartOffset(EE_ILEXCEPTION_CLAUSE* pEHClause, DWORD_PTR dwHandlerStartPC) -{ - EECodeInfo codeInfo((PCODE)dwHandlerStartPC); - _ASSERTE(codeInfo.IsValid()); - - return pEHClause->FilterOffset == codeInfo.GetRelOffset(); -} - -void ExceptionTracker::MakeCallbacksRelatedToHandler( - bool fBeforeCallingHandler, - Thread* pThread, - MethodDesc* pMD, - EE_ILEXCEPTION_CLAUSE* pEHClause, - DWORD_PTR dwHandlerStartPC, - StackFrame sf - ) -{ - // Here we need to make an extra check for filter handlers because we could be calling the catch handler - // associated with a filter handler and yet the EH clause we have saved is for the filter handler. - BOOL fIsFilterHandler = IsFilterHandler(pEHClause) && ExceptionTracker::IsFilterStartOffset(pEHClause, dwHandlerStartPC); - BOOL fIsFaultOrFinallyHandler = IsFaultOrFinally(pEHClause); - - if (fBeforeCallingHandler) - { - StackFrame sfToStore = sf; - if ((this->m_pPrevNestedInfo != NULL) && - (((ExceptionTracker*)this->m_pPrevNestedInfo)->m_EnclosingClauseInfo == this->m_EnclosingClauseInfo)) - { - // If this is a nested exception which has the same enclosing clause as the previous exception, - // we should just propagate the clause info from the previous exception. - sfToStore = this->m_pPrevNestedInfo->m_EHClauseInfo.GetStackFrameForEHClause(); - } - m_EHClauseInfo.SetInfo(COR_PRF_CLAUSE_NONE, (UINT_PTR)dwHandlerStartPC, sfToStore); - - if (pMD->IsILStub()) - { - return; - } - - if (fIsFilterHandler) - { - m_EHClauseInfo.SetEHClauseType(COR_PRF_CLAUSE_FILTER); - EEToDebuggerExceptionInterfaceWrapper::ExceptionFilter(pMD, (TADDR) dwHandlerStartPC, pEHClause->FilterOffset, (BYTE*)sf.SP); - - EEToProfilerExceptionInterfaceWrapper::ExceptionSearchFilterEnter(pMD); - } - else - { - EEToDebuggerExceptionInterfaceWrapper::ExceptionHandle(pMD, (TADDR) dwHandlerStartPC, pEHClause->HandlerStartPC, (BYTE*)sf.SP); - - if (fIsFaultOrFinallyHandler) - { - m_EHClauseInfo.SetEHClauseType(COR_PRF_CLAUSE_FINALLY); - EEToProfilerExceptionInterfaceWrapper::ExceptionUnwindFinallyEnter(pMD); - } - else - { - m_EHClauseInfo.SetEHClauseType(COR_PRF_CLAUSE_CATCH); - EEToProfilerExceptionInterfaceWrapper::ExceptionCatcherEnter(pThread, pMD); - - DACNotify::DoExceptionCatcherEnterNotification(pMD, pEHClause->HandlerStartPC); - } - } - } - else - { - if (pMD->IsILStub()) - { - return; - } - - if (fIsFilterHandler) - { - EEToProfilerExceptionInterfaceWrapper::ExceptionSearchFilterLeave(); - } - else - { - if (fIsFaultOrFinallyHandler) - { - EEToProfilerExceptionInterfaceWrapper::ExceptionUnwindFinallyLeave(); - } - else - { - EEToProfilerExceptionInterfaceWrapper::ExceptionCatcherLeave(); - } - } - m_EHClauseInfo.ResetInfo(); - } -} - #ifdef DEBUGGER_EXCEPTION_INTERCEPTION_SUPPORTED //--------------------------------------------------------------------------------------- // diff --git a/src/coreclr/vm/exceptionhandling.h b/src/coreclr/vm/exceptionhandling.h index 290d611b5dc9d0..15f59d50d515c5 100644 --- a/src/coreclr/vm/exceptionhandling.h +++ b/src/coreclr/vm/exceptionhandling.h @@ -323,13 +323,6 @@ class ExceptionTracker : public ExceptionTrackerBase static bool HasFrameBeenUnwoundByAnyActiveException(CrawlFrame * pCF); - StackFrame GetCurrentEstablisherFrame() - { - LIMITED_METHOD_CONTRACT; - - return m_sfCurrentEstablisherFrame; - } - void SetLastUnwoundEstablisherFrame(StackFrame sfEstablisher) { LIMITED_METHOD_CONTRACT; @@ -407,11 +400,6 @@ class ExceptionTracker : public ExceptionTrackerBase static void PopTrackers(void* pvStackPointer); - static void - PopTrackerIfEscaping(void* pvStackPointer); - - bool IsStackOverflowException(); - #if defined(TARGET_UNIX) && !defined(CROSS_COMPILE) void TakeExceptionPointersOwnership(PAL_SEHException* ex) { @@ -423,27 +411,6 @@ class ExceptionTracker : public ExceptionTrackerBase #endif // TARGET_UNIX && !CROSS_COMPILE private: - static bool - IsFilterStartOffset(EE_ILEXCEPTION_CLAUSE* pEHClause, DWORD_PTR dwHandlerStartPC); - -#if defined(DEBUGGING_SUPPORTED) - void - MakeCallbacksRelatedToHandler(bool fBeforeCallingHandler, - Thread* pThread, - MethodDesc* pMD, - EE_ILEXCEPTION_CLAUSE* pEHClause, - DWORD_PTR dwHandlerStartPC, - StackFrame sf); -#else // !DEBUGGING_SUPPORTED - void - MakeCallbacksRelatedToHandler(bool fBeforeCallingHandler, - Thread* pThread, - MethodDesc* pMD, - EE_ILEXCEPTION_CLAUSE* pEHClause, - DWORD_PTR dwHandlerStartPC, - StackFrame sf) {return;} -#endif // !DEBUGGING_SUPPORTED - // private helpers static StackFrame GetCallerSPOfParentOfNonExceptionallyInvokedFunclet(CrawlFrame *pCF);