From 5e6b91293a20645612e28073d42ec5666eddcc93 Mon Sep 17 00:00:00 2001 From: Jan Vorlicek Date: Fri, 17 May 2024 20:55:15 +0200 Subject: [PATCH 1/2] Fix VS debugger issues with funceval and secondary threads The VS team has recently reported two issues with the new exception handling in Visual Studio debugger. The first issue was that an unhandled exception on a secondary managed thread wasn't showing any stack trace when the exception occured and the debugger has broken in. The other issue was that when an exception occured during a funceval invoked from the immediate window, the debugger would not highlight the source line where the exception occured and it would not display a dialog with the exception details. Both problems were caused by the same underlying problem. In both cases, the "catch handler found" notification was to be sent at a point when the managed stack frames were already gone - in native code in catch / filter. In the funceval case, there was even a check that prevented sending the notification at all when there was no exception info present. The fix is to move the notification to the point where the managed stack frames are still present - when we detect in the EH code that there is no managed frame left and either the DebuggerU2MCatchHandler frame or FuncEvalFrame is the explicit frame we've encountered as the next one to process. The FuncEvalFrame case is a bit more involved, as we always push ProtectValueClassFrame after the FuncEvalFrame, so we need to skip that one in the check. The debugger actually needs to get a pointer to the FuncEvalFrame in the notification to do the right thing. Close #102178 and #101729 --- src/coreclr/vm/excep.cpp | 4 ++-- src/coreclr/vm/exceptionhandling.cpp | 20 ++++++++++++++++++++ 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/src/coreclr/vm/excep.cpp b/src/coreclr/vm/excep.cpp index 795ba8fde6c946..cb1fe1e30a4495 100644 --- a/src/coreclr/vm/excep.cpp +++ b/src/coreclr/vm/excep.cpp @@ -7901,10 +7901,10 @@ LONG NotifyOfCHFFilterWrapper( (pThread->GetExceptionState()->GetContextRecord() == NULL) || (GetSP(pThread->GetExceptionState()->GetContextRecord()) != GetSP(pExceptionInfo->ContextRecord) ) ) { - LOG((LF_EH, LL_INFO1000, "NotifyOfCHFFilterWrapper: not sending notices. pThread: %0x8", pThread)); + LOG((LF_EH, LL_INFO1000, "NotifyOfCHFFilterWrapper: not sending notices. pThread: %p", pThread)); if (pThread) { - LOG((LF_EH, LL_INFO1000, ", Thread SP: %0x8, Exception SP: %08x", + LOG((LF_EH, LL_INFO1000, ", Thread SP: %p, Exception SP: %p", pThread->GetExceptionState()->GetContextRecord() ? GetSP(pThread->GetExceptionState()->GetContextRecord()) : NULL, pExceptionInfo->ContextRecord ? GetSP(pExceptionInfo->ContextRecord) : NULL )); } diff --git a/src/coreclr/vm/exceptionhandling.cpp b/src/coreclr/vm/exceptionhandling.cpp index db648d88496f62..a16728bb382839 100644 --- a/src/coreclr/vm/exceptionhandling.cpp +++ b/src/coreclr/vm/exceptionhandling.cpp @@ -8217,6 +8217,26 @@ static void NotifyExceptionPassStarted(StackFrameIterator *pThis, Thread *pThrea pExInfo->m_ExceptionFlags.SetUnwindHasStarted(); EEToDebuggerExceptionInterfaceWrapper::ManagedExceptionUnwindBegin(pThread); } + else + { + // The debugger explicitly checks that the notification refers to a FuncEvalFrame in case an exception becomes unhandled in a func eval. + // We need to do the notification here before we start propagating the exception through native frames, since that will remove + // all managed frames from the stack and the debugger would not see the failure location. + if (pThis->GetFrameState() == StackFrameIterator::SFITER_FRAME_FUNCTION) + { + Frame* pFrame = pThis->m_crawl.GetFrame(); + // If the frame is ProtectValueClassFrame, move to the next one as we want to report the FuncEvalFrame + if (pFrame->GetVTablePtr() == ProtectValueClassFrame::GetMethodFrameVPtr()) + { + pFrame = pFrame->PtrNextFrame(); + _ASSERTE((pFrame != FRAME_TOP) && (pFrame->GetVTablePtr() == FuncEvalFrame::GetMethodFrameVPtr())); + } + if ((pFrame->GetVTablePtr() == FuncEvalFrame::GetMethodFrameVPtr()) || (pFrame->GetVTablePtr() == DebuggerU2MCatchHandlerFrame::GetMethodFrameVPtr())) + { + EEToDebuggerExceptionInterfaceWrapper::NotifyOfCHFFilter((EXCEPTION_POINTERS *)&pExInfo->m_ptrs, pFrame); + } + } + } } } From ecc0bf27ce683180360e45631c2745cf6ad2be36 Mon Sep 17 00:00:00 2001 From: Jan Vorlicek Date: Tue, 21 May 2024 10:38:59 +0200 Subject: [PATCH 2/2] Fix too strong assert The ProtectValueClassFrame can also occur without FuncEvalFrame in the reflection invocation. --- src/coreclr/vm/exceptionhandling.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/coreclr/vm/exceptionhandling.cpp b/src/coreclr/vm/exceptionhandling.cpp index a16728bb382839..6033dd4ad02f29 100644 --- a/src/coreclr/vm/exceptionhandling.cpp +++ b/src/coreclr/vm/exceptionhandling.cpp @@ -8229,7 +8229,7 @@ static void NotifyExceptionPassStarted(StackFrameIterator *pThis, Thread *pThrea if (pFrame->GetVTablePtr() == ProtectValueClassFrame::GetMethodFrameVPtr()) { pFrame = pFrame->PtrNextFrame(); - _ASSERTE((pFrame != FRAME_TOP) && (pFrame->GetVTablePtr() == FuncEvalFrame::GetMethodFrameVPtr())); + _ASSERTE(pFrame != FRAME_TOP); } if ((pFrame->GetVTablePtr() == FuncEvalFrame::GetMethodFrameVPtr()) || (pFrame->GetVTablePtr() == DebuggerU2MCatchHandlerFrame::GetMethodFrameVPtr())) {