Skip to content

Commit 54e7818

Browse files
authored
Fix unhandled exception reporting corner cases (#118450)
* Fix unhandled exception reporting corner cases There are several issues with processing exceptions that are not handled by managed code on Windows: * Sometimes the AppDomain.UnhandledException event is sent multiple times * Exception flowing to foreign native code is reported as unhandled even when it is actually caught by the native code * In rare cases, the unhandled exception stack trace is doubled This change fixes them by not reporting the unhandled exception in the SfiNext on Windows. It just raises the underlying SEH exception there with the thread marked so that the personality routines for the managed frames won't run the managed exception handling code. If the exception is truly unhandled, the `InternalUnhandledExceptionFilter_Worker` will be called by the unhandled exception filter installed for the process and report the exception as unhandled. If that exception ends up being caught by a foreign native code, then nothing will be reported. Close #115215 * Ensure AppDomain.CurrentDomain.UnhandledException occurs before finally
1 parent bbe64d0 commit 54e7818

File tree

2 files changed

+26
-34
lines changed

2 files changed

+26
-34
lines changed

src/coreclr/vm/exceptionhandling.cpp

Lines changed: 25 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -579,32 +579,21 @@ ProcessCLRException(IN PEXCEPTION_RECORD pExceptionRecord,
579579
}
580580
#endif
581581

582-
if (pThread->HasThreadStateNC(Thread::TSNC_UnhandledException2ndPass) && !(pExceptionRecord->ExceptionFlags & EXCEPTION_UNWINDING))
583-
{
584-
// We are in the 1st pass of exception handling, but the thread mark says that it has already executed 2nd pass
585-
// of unhandled exception handling. That means that some external native code on top of the stack has caught the
586-
// exception that runtime considered to be unhandled, and a new native exception was thrown on the current thread.
587-
// We need to reset the flags below so that we no longer block exception handling for the managed frames.
588-
pThread->ResetThreadStateNC(Thread::TSNC_UnhandledException2ndPass);
589-
pThread->ResetThreadStateNC(Thread::TSNC_ProcessedUnhandledException);
590-
}
591-
592582
// Also skip all frames when processing unhandled exceptions. That allows them to reach the host app
593583
// level and let 3rd party the chance to handle them.
594-
if (pThread->HasThreadStateNC(Thread::TSNC_ProcessedUnhandledException))
584+
if (pThread->HasThreadStateNC(Thread::TSNC_SkipManagedPersonalityRoutine))
595585
{
596586
if (pExceptionRecord->ExceptionFlags & EXCEPTION_UNWINDING)
597587
{
598-
if (!pThread->HasThreadStateNC(Thread::TSNC_UnhandledException2ndPass))
588+
// The 3rd argument passes to PopExplicitFrame is normally the parent SP to correctly handle InlinedCallFrame embbeded
589+
// in parent managed frame. But at this point there are no further managed frames are on the stack, so we can pass NULL.
590+
// Also don't pop the GC frames, their destructor will pop them as the exception propagates.
591+
// NOTE: this needs to be popped in the 2nd pass to ensure that crash dumps and Watson get the dump with these still
592+
// present.
593+
ExInfo *pExInfo = (ExInfo*)pThread->GetExceptionState()->GetCurrentExceptionTracker();
594+
if (pExInfo != NULL)
599595
{
600-
pThread->SetThreadStateNC(Thread::TSNC_UnhandledException2ndPass);
601596
GCX_COOP();
602-
// The 3rd argument passes to PopExplicitFrame is normally the parent SP to correctly handle InlinedCallFrame embbeded
603-
// in parent managed frame. But at this point there are no further managed frames are on the stack, so we can pass NULL.
604-
// Also don't pop the GC frames, their destructor will pop them as the exception propagates.
605-
// NOTE: this needs to be popped in the 2nd pass to ensure that crash dumps and Watson get the dump with these still
606-
// present.
607-
ExInfo *pExInfo = (ExInfo*)pThread->GetExceptionState()->GetCurrentExceptionTracker();
608597
void *sp = (void*)GetRegdisplaySP(pExInfo->m_frameIter.m_crawl.GetRegisterSet());
609598
PopExplicitFrames(pThread, sp, NULL /* targetCallerSp */, false /* popGCFrames */);
610599
ExInfo::PopExInfos(pThread, sp);
@@ -3757,7 +3746,10 @@ extern "C" CLR_BOOL QCALLTYPE SfiInit(StackFrameIterator* pThis, CONTEXT* pStack
37573746
Thread* pThread = GET_THREAD();
37583747
ExInfo* pExInfo = (ExInfo*)pThread->GetExceptionState()->GetCurrentExceptionTracker();
37593748

3760-
pThread->ResetThreadStateNC(Thread::TSNC_ProcessedUnhandledException);
3749+
if (pExInfo->m_passNumber == 1)
3750+
{
3751+
pThread->ResetThreadStateNC(Thread::TSNC_SkipManagedPersonalityRoutine);
3752+
}
37613753

37623754
BEGIN_QCALL;
37633755

@@ -3872,14 +3864,13 @@ extern "C" CLR_BOOL QCALLTYPE SfiInit(StackFrameIterator* pThis, CONTEXT* pStack
38723864
}
38733865
else
38743866
{
3867+
// There are no managed frames on the stack
38753868
EH_LOG((LL_INFO100, "SfiInit: No more managed frames found on stack\n"));
3876-
// There are no managed frames on the stack, fail fast and report unhandled exception
3877-
LONG disposition = InternalUnhandledExceptionFilter_Worker((EXCEPTION_POINTERS *)&pExInfo->m_ptrs);
38783869
#ifdef HOST_WINDOWS
3879-
CreateCrashDumpIfEnabled(/* fSOException */ FALSE);
3880-
GetThread()->SetThreadStateNC(Thread::TSNC_ProcessedUnhandledException);
3870+
GetThread()->SetThreadStateNC(Thread::TSNC_SkipManagedPersonalityRoutine);
38813871
RaiseException(pExInfo->m_ExceptionCode, EXCEPTION_NONCONTINUABLE, pExInfo->m_ptrs.ExceptionRecord->NumberParameters, pExInfo->m_ptrs.ExceptionRecord->ExceptionInformation);
38823872
#else
3873+
LONG disposition = InternalUnhandledExceptionFilter_Worker((EXCEPTION_POINTERS *)&pExInfo->m_ptrs);
38833874
CrashDumpAndTerminateProcess(pExInfo->m_ExceptionCode);
38843875
#endif
38853876
}
@@ -3970,6 +3961,7 @@ extern "C" CLR_BOOL QCALLTYPE SfiNext(StackFrameIterator* pThis, uint* uExCollid
39703961

39713962
if (isPropagatingToNativeCode)
39723963
{
3964+
// Propagating through Reverse pinvoke
39733965
#ifdef HOST_UNIX
39743966
void* callbackCxt = NULL;
39753967
Interop::ManagedToNativeExceptionCallback callback = Interop::GetPropagatingExceptionCallback(
@@ -3983,13 +3975,14 @@ extern "C" CLR_BOOL QCALLTYPE SfiNext(StackFrameIterator* pThis, uint* uExCollid
39833975
pTopExInfo->m_propagateExceptionContext = callbackCxt;
39843976
}
39853977
else
3978+
#endif // HOST_UNIX
39863979
{
39873980
isPropagatingToExternalNativeCode = true;
39883981
}
3989-
#endif // HOST_UNIX
39903982
}
39913983
else
39923984
{
3985+
// Propagating to CallDescrWorkerInternal, filter funclet or CallEHFunclet.
39933986
if (IsCallDescrWorkerInternalReturnAddress(GetIP(pThis->m_crawl.GetRegisterSet()->pCurrentContext)))
39943987
{
39953988
EH_LOG((LL_INFO100, "SfiNext: the native frame is CallDescrWorkerInternal"));
@@ -4000,10 +3993,6 @@ extern "C" CLR_BOOL QCALLTYPE SfiNext(StackFrameIterator* pThis, uint* uExCollid
40003993
EH_LOG((LL_INFO100, "SfiNext: current frame is filter funclet"));
40013994
isPropagatingToNativeCode = TRUE;
40023995
}
4003-
else
4004-
{
4005-
isPropagatingToExternalNativeCode = true;
4006-
}
40073996
}
40083997

40093998
if (isPropagatingToNativeCode)
@@ -4024,18 +4013,21 @@ extern "C" CLR_BOOL QCALLTYPE SfiNext(StackFrameIterator* pThis, uint* uExCollid
40244013

40254014
if (isNotHandledByRuntime && IsExceptionFromManagedCode(pTopExInfo->m_ptrs.ExceptionRecord))
40264015
{
4027-
EH_LOG((LL_INFO100, "SfiNext (pass %d): no more managed frames on the stack, the exception is unhandled", pTopExInfo->m_passNumber));
4016+
EH_LOG((LL_INFO100, "SfiNext (pass %d): no more managed frames on the stack, the exception is not handled by the runtime\n", pTopExInfo->m_passNumber));
40284017
if (pTopExInfo->m_passNumber == 1)
40294018
{
4030-
LONG disposition = InternalUnhandledExceptionFilter_Worker((EXCEPTION_POINTERS *)&pTopExInfo->m_ptrs);
40314019
#ifdef HOST_WINDOWS
4032-
CreateCrashDumpIfEnabled(/* fSOException */ FALSE);
4020+
if (!isPropagatingToExternalNativeCode)
40334021
#endif
4022+
{
4023+
LONG disposition = InternalUnhandledExceptionFilter_Worker((EXCEPTION_POINTERS *)&pTopExInfo->m_ptrs);
4024+
GetThread()->SetThreadStateNC(Thread::TSNC_ProcessedUnhandledException);
4025+
}
40344026
}
40354027
else
40364028
{
40374029
#ifdef HOST_WINDOWS
4038-
GetThread()->SetThreadStateNC(Thread::TSNC_ProcessedUnhandledException);
4030+
GetThread()->SetThreadStateNC(Thread::TSNC_SkipManagedPersonalityRoutine);
40394031
RaiseException(pTopExInfo->m_ExceptionCode, EXCEPTION_NONCONTINUABLE, pTopExInfo->m_ptrs.ExceptionRecord->NumberParameters, pTopExInfo->m_ptrs.ExceptionRecord->ExceptionInformation);
40404032
#else
40414033
CrashDumpAndTerminateProcess(pTopExInfo->m_ExceptionCode);

src/coreclr/vm/threads.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -642,7 +642,7 @@ class Thread
642642
// effort.
643643
//
644644
// Once we are completely independent of the OS UEF, we could remove this.
645-
TSNC_UnhandledException2ndPass = 0x02000000, // The unhandled exception propagation is in the 2nd pass
645+
TSNC_SkipManagedPersonalityRoutine = 0x02000000, // Ignore the ProcessCLRException calls when propagating exception to external native code
646646
TSNC_DebuggerSleepWaitJoin = 0x04000000, // Indicates to the debugger that this thread is in a sleep wait or join state
647647
// This almost mirrors the TS_Interruptible state however that flag can change
648648
// during GC-preemptive mode whereas this one cannot.

0 commit comments

Comments
 (0)