Skip to content

Commit 6b26684

Browse files
authored
Fix VS debugger tests issues (#98783)
* Fix VS debugger tests issues This change fixes three issues I have found in the debugger related code after I was finally able to run the VS debugger tests with .NET 9. These are the fixed issues: * The old EH for hardware exceptions puts incorrectly the exception location adjusted by -1 to the exception stack trace. The new EH didn't have this problem, but VS expects that to be the case, so I have added a compensation for it until the old EH is gone or until VS can be changed to support both cases. * Exception interception was not working correctly for cases when the exception needed to be propagated over native runtime frames. After catching the rethrown SEH / PAL_SEHException and also in ProcessCLRException for Windows, the interception needs to be detected and instead of calling the DispatchManagedException, we need to call the Ex.RhUnwindAndIntercept again. * The first chance exception debugger notification was sometimes sent before even the first stack frame was added to the exception stack trace. That has broken quite a number of VS tests. * Fix missing ifdef to fix x86 build * Fix crossdac build
1 parent dcc66a7 commit 6b26684

File tree

6 files changed

+165
-39
lines changed

6 files changed

+165
-39
lines changed

src/coreclr/vm/excep.cpp

+48-1
Original file line numberDiff line numberDiff line change
@@ -7761,6 +7761,42 @@ void UnwindAndContinueRethrowHelperInsideCatch(Frame* pEntryFrame, Exception* pE
77617761
#endif
77627762
}
77637763

7764+
#ifdef FEATURE_EH_FUNCLETS
7765+
//
7766+
// This function continues exception interception unwind after it crossed native frames using
7767+
// standard EH / SEH.
7768+
//
7769+
VOID DECLSPEC_NORETURN ContinueExceptionInterceptionUnwind()
7770+
{
7771+
STATIC_CONTRACT_THROWS;
7772+
STATIC_CONTRACT_GC_TRIGGERS;
7773+
STATIC_CONTRACT_MODE_ANY;
7774+
7775+
GCX_COOP();
7776+
7777+
Thread *pThread = GetThread();
7778+
ThreadExceptionState* pExState = pThread->GetExceptionState();
7779+
UINT_PTR uInterceptStackFrame = 0;
7780+
7781+
pExState->GetDebuggerState()->GetDebuggerInterceptInfo(NULL, NULL,
7782+
(PBYTE*)&uInterceptStackFrame,
7783+
NULL, NULL);
7784+
7785+
PREPARE_NONVIRTUAL_CALLSITE(METHOD__EH__UNWIND_AND_INTERCEPT);
7786+
DECLARE_ARGHOLDER_ARRAY(args, 2);
7787+
args[ARGNUM_0] = PTR_TO_ARGHOLDER((ExInfo*)pExState->GetCurrentExceptionTracker());
7788+
args[ARGNUM_1] = PTR_TO_ARGHOLDER(uInterceptStackFrame);
7789+
pThread->IncPreventAbort();
7790+
7791+
//Ex.RhUnwindAndIntercept(throwable, &exInfo)
7792+
CRITICAL_CALLSITE;
7793+
CALL_MANAGED_METHOD_NORET(args)
7794+
7795+
UNREACHABLE();
7796+
}
7797+
7798+
#endif // FEATURE_EH_FUNCLETS
7799+
77647800
//
77657801
// This does the work of the Unwind and Continue Hanlder after the catch clause of that handler. The stack has been
77667802
// unwound by the time this is called. Keep that in mind when deciding where to put new code :)
@@ -7784,7 +7820,18 @@ VOID DECLSPEC_NORETURN UnwindAndContinueRethrowHelperAfterCatch(Frame* pEntryFra
77847820
#ifdef FEATURE_EH_FUNCLETS
77857821
if (g_isNewExceptionHandlingEnabled && !nativeRethrow)
77867822
{
7787-
DispatchManagedException(orThrowable);
7823+
Thread *pThread = GetThread();
7824+
ThreadExceptionState* pExState = pThread->GetExceptionState();
7825+
ExInfo *pPrevExInfo = (ExInfo*)pExState->GetCurrentExceptionTracker();
7826+
if (pPrevExInfo != NULL && pPrevExInfo->m_DebuggerExState.GetDebuggerInterceptContext() != NULL)
7827+
{
7828+
ContinueExceptionInterceptionUnwind();
7829+
UNREACHABLE();
7830+
}
7831+
else
7832+
{
7833+
DispatchManagedException(orThrowable);
7834+
}
77887835
}
77897836
else
77907837
#endif // FEATURE_EH_FUNCLETS

src/coreclr/vm/excep.h

+4
Original file line numberDiff line numberDiff line change
@@ -846,6 +846,10 @@ void ResetThreadAbortState(PTR_Thread pThread, CrawlFrame *pCf, StackFrame sfCur
846846

847847
X86_ONLY(EXCEPTION_REGISTRATION_RECORD* GetNextCOMPlusSEHRecord(EXCEPTION_REGISTRATION_RECORD* pRec);)
848848

849+
#ifdef FEATURE_EH_FUNCLETS
850+
VOID DECLSPEC_NORETURN ContinueExceptionInterceptionUnwind();
851+
#endif // FEATURE_EH_FUNCLETS
852+
849853
#endif // !DACCESS_COMPILE
850854

851855
#endif // __excep_h__

src/coreclr/vm/exceptionhandling.cpp

+77-38
Original file line numberDiff line numberDiff line change
@@ -929,8 +929,18 @@ ProcessCLRExceptionNew(IN PEXCEPTION_RECORD pExceptionRecord,
929929
else
930930
{
931931
GCX_COOP();
932-
OBJECTREF oref = ExceptionTracker::CreateThrowable(pExceptionRecord, FALSE);
933-
DispatchManagedException(oref, pContextRecord);
932+
ThreadExceptionState* pExState = pThread->GetExceptionState();
933+
ExInfo *pPrevExInfo = (ExInfo*)pExState->GetCurrentExceptionTracker();
934+
if (pPrevExInfo != NULL && pPrevExInfo->m_DebuggerExState.GetDebuggerInterceptContext() != NULL)
935+
{
936+
ContinueExceptionInterceptionUnwind();
937+
UNREACHABLE();
938+
}
939+
else
940+
{
941+
OBJECTREF oref = ExceptionTracker::CreateThrowable(pExceptionRecord, FALSE);
942+
DispatchManagedException(oref, pContextRecord);
943+
}
934944
}
935945
#endif // !HOST_UNIX
936946
EEPOLICY_HANDLE_FATAL_ERROR_WITH_MESSAGE(COR_E_EXECUTIONENGINE, _T("SEH exception leaked into managed code"));
@@ -4299,9 +4309,12 @@ EXCEPTION_DISPOSITION ClrDebuggerDoUnwindAndIntercept(X86_FIRST_ARG(EXCEPTION_RE
42994309
{
43004310
GCX_COOP();
43014311

4312+
ExInfo* pExInfo = (ExInfo*)pExState->GetCurrentExceptionTracker();
4313+
_ASSERTE(pExInfo != NULL);
4314+
43024315
PREPARE_NONVIRTUAL_CALLSITE(METHOD__EH__UNWIND_AND_INTERCEPT);
43034316
DECLARE_ARGHOLDER_ARRAY(args, 2);
4304-
args[ARGNUM_0] = PTR_TO_ARGHOLDER(pExState->GetCurrentExceptionTracker());
4317+
args[ARGNUM_0] = PTR_TO_ARGHOLDER(pExInfo);
43054318
args[ARGNUM_1] = PTR_TO_ARGHOLDER(uInterceptStackFrame);
43064319
pThread->IncPreventAbort();
43074320

@@ -7532,29 +7545,55 @@ void MarkInlinedCallFrameAsEHHelperCall(Frame* pFrame)
75327545
pInlinedCallFrame->m_Datum = (PTR_NDirectMethodDesc)((TADDR)pInlinedCallFrame->m_Datum | (TADDR)InlinedCallFrameMarker::ExceptionHandlingHelper);
75337546
}
75347547

7548+
static TADDR GetSpForDiagnosticReporting(REGDISPLAY *pRD)
7549+
{
7550+
#ifdef ESTABLISHER_FRAME_ADDRESS_IS_CALLER_SP
7551+
return CallerStackFrame::FromRegDisplay(pRD).SP;
7552+
#else
7553+
return GetSP(pRD->pCurrentContext);
7554+
#endif
7555+
}
7556+
75357557
extern "C" void QCALLTYPE AppendExceptionStackFrame(QCall::ObjectHandleOnStack exceptionObj, SIZE_T ip, SIZE_T sp, int flags, ExInfo *pExInfo)
75367558
{
75377559
QCALL_CONTRACT;
75387560

75397561
BEGIN_QCALL;
7540-
GCX_COOP();
75417562

75427563
Thread* pThread = GET_THREAD();
7543-
Frame* pFrame = pThread->GetFrame();
7544-
MarkInlinedCallFrameAsFuncletCall(pFrame);
75457564

7546-
bool canAllocateMemory = !(exceptionObj.Get() == CLRException::GetPreallocatedOutOfMemoryException()) &&
7547-
!(exceptionObj.Get() == CLRException::GetPreallocatedStackOverflowException());
7565+
{
7566+
GCX_COOP();
7567+
7568+
Frame* pFrame = pThread->GetFrame();
7569+
MarkInlinedCallFrameAsEHHelperCall(pFrame);
75487570

7549-
MethodDesc *pMD = pExInfo->m_frameIter.m_crawl.GetFunction();
7571+
bool canAllocateMemory = !(exceptionObj.Get() == CLRException::GetPreallocatedOutOfMemoryException()) &&
7572+
!(exceptionObj.Get() == CLRException::GetPreallocatedStackOverflowException());
7573+
7574+
MethodDesc *pMD = pExInfo->m_frameIter.m_crawl.GetFunction();
75507575
#if _DEBUG
7551-
EECodeInfo codeInfo(ip);
7552-
_ASSERTE(codeInfo.IsValid());
7553-
_ASSERTE(pMD == codeInfo.GetMethodDesc());
7576+
EECodeInfo codeInfo(ip);
7577+
_ASSERTE(codeInfo.IsValid());
7578+
_ASSERTE(pMD == codeInfo.GetMethodDesc());
75547579
#endif // _DEBUG
75557580

7556-
pExInfo->m_StackTraceInfo.AppendElement(canAllocateMemory, ip, sp, pMD, &pExInfo->m_frameIter.m_crawl);
7557-
pExInfo->m_StackTraceInfo.SaveStackTrace(canAllocateMemory, pExInfo->m_hThrowable, /*bReplaceStack*/FALSE, /*bSkipLastElement*/FALSE);
7581+
// Compensate for a bug in the old EH that doesn't mark faulting instructions as faulting. The VS expects that behavior.
7582+
bool hasFaulted = pExInfo->m_frameIter.m_crawl.HasFaulted();
7583+
if (hasFaulted)
7584+
{
7585+
pExInfo->m_frameIter.m_crawl.hasFaulted = false;
7586+
}
7587+
pExInfo->m_StackTraceInfo.AppendElement(canAllocateMemory, ip, sp, pMD, &pExInfo->m_frameIter.m_crawl);
7588+
pExInfo->m_StackTraceInfo.SaveStackTrace(canAllocateMemory, pExInfo->m_hThrowable, /*bReplaceStack*/FALSE, /*bSkipLastElement*/FALSE);
7589+
pExInfo->m_frameIter.m_crawl.hasFaulted = hasFaulted;
7590+
}
7591+
7592+
// Notify the debugger that we are on the first pass for a managed exception.
7593+
// Note that this callback is made for every managed frame.
7594+
TADDR spForDebugger = GetSpForDiagnosticReporting(pExInfo->m_frameIter.m_crawl.GetRegisterSet());
7595+
EEToDebuggerExceptionInterfaceWrapper::FirstChanceManagedException(pThread, ip, spForDebugger);
7596+
75587597
if (!pExInfo->DeliveredFirstChanceNotification())
75597598
{
75607599
ExceptionNotifications::DeliverFirstChanceNotification();
@@ -7589,15 +7628,6 @@ UINT_PTR GetEstablisherFrame(REGDISPLAY* pvRegDisplay, ExInfo* exInfo)
75897628
#endif
75907629
}
75917630

7592-
static TADDR GetSpForDiagnosticReporting(REGDISPLAY *pRD)
7593-
{
7594-
#ifdef ESTABLISHER_FRAME_ADDRESS_IS_CALLER_SP
7595-
return CallerStackFrame::FromRegDisplay(pRD).SP;
7596-
#else
7597-
return GetSP(pRD->pCurrentContext);
7598-
#endif
7599-
}
7600-
76017631
extern "C" void * QCALLTYPE CallCatchFunclet(QCall::ObjectHandleOnStack exceptionObj, BYTE* pHandlerIP, REGDISPLAY* pvRegDisplay, ExInfo* exInfo)
76027632
{
76037633
QCALL_CONTRACT;
@@ -7663,25 +7693,32 @@ extern "C" void * QCALLTYPE CallCatchFunclet(QCall::ObjectHandleOnStack exceptio
76637693
BOOL fIntercepted = pThread->GetExceptionState()->GetFlags()->DebuggerInterceptInfo();
76647694
if (fIntercepted)
76657695
{
7696+
_ASSERTE(pHandlerIP == NULL);
76667697
// retrieve the interception information
76677698
MethodDesc *pInterceptMD = NULL;
76687699
StackFrame sfInterceptStackFrame;
76697700
UINT_PTR uResumePC = 0;
76707701
ULONG_PTR ulRelOffset;
76717702

76727703
pThread->GetExceptionState()->GetDebuggerState()->GetDebuggerInterceptInfo(&pInterceptMD, NULL, (PBYTE*)&(sfInterceptStackFrame.SP), &ulRelOffset, NULL);
7704+
if (sfInterceptStackFrame.SP == GetSP(pvRegDisplay->pCurrentContext))
7705+
{
7706+
PCODE pStartAddress = pInterceptMD->GetNativeCode();
76737707

7674-
PCODE pStartAddress = pInterceptMD->GetNativeCode();
7675-
7676-
EECodeInfo codeInfo(pStartAddress);
7677-
_ASSERTE(codeInfo.IsValid());
7708+
EECodeInfo codeInfo(pStartAddress);
7709+
_ASSERTE(codeInfo.IsValid());
76787710

7679-
// Note that the value returned for ulRelOffset is actually the offset,
7680-
// so we need to adjust it to get the actual IP.
7681-
_ASSERTE(FitsIn<DWORD>(ulRelOffset));
7682-
uResumePC = codeInfo.GetJitManager()->GetCodeAddressForRelOffset(codeInfo.GetMethodToken(), static_cast<DWORD>(ulRelOffset));
7711+
// Note that the value returned for ulRelOffset is actually the offset,
7712+
// so we need to adjust it to get the actual IP.
7713+
_ASSERTE(FitsIn<DWORD>(ulRelOffset));
7714+
uResumePC = codeInfo.GetJitManager()->GetCodeAddressForRelOffset(codeInfo.GetMethodToken(), static_cast<DWORD>(ulRelOffset));
76837715

7684-
SetIP(pvRegDisplay->pCurrentContext, uResumePC);
7716+
SetIP(pvRegDisplay->pCurrentContext, uResumePC);
7717+
}
7718+
else
7719+
{
7720+
fIntercepted = FALSE;
7721+
}
76857722
}
76867723
#endif // DEBUGGING_SUPPORTED
76877724

@@ -7797,6 +7834,10 @@ extern "C" void QCALLTYPE ResumeAtInterceptionLocation(REGDISPLAY* pvRegDisplay)
77977834
MarkInlinedCallFrameAsFuncletCall(pFrame);
77987835

77997836
UINT_PTR targetSp = GetSP(pvRegDisplay->pCurrentContext);
7837+
ExInfo *pExInfo = (PTR_ExInfo)pThread->GetExceptionState()->GetCurrentExceptionTracker();
7838+
7839+
pExInfo->m_ScannedStackRange.ExtendUpperBound(targetSp);
7840+
78007841
PopExplicitFrames(pThread, (void*)targetSp);
78017842

78027843
// This must be done before we pop the ExInfos.
@@ -8135,10 +8176,6 @@ static void NotifyFunctionEnter(StackFrameIterator *pThis, Thread *pThread, ExIn
81358176
EEToProfilerExceptionInterfaceWrapper::ExceptionSearchFunctionLeave(pExInfo->m_pMDToReportFunctionLeave);
81368177
}
81378178
EEToProfilerExceptionInterfaceWrapper::ExceptionSearchFunctionEnter(pMD);
8138-
// Notify the debugger that we are on the first pass for a managed exception.
8139-
// Note that this callback is made for every managed frame.
8140-
TADDR spForDebugger = GetSpForDiagnosticReporting(pThis->m_crawl.GetRegisterSet());
8141-
EEToDebuggerExceptionInterfaceWrapper::FirstChanceManagedException(pThread, GetControlPC(pThis->m_crawl.GetRegisterSet()), spForDebugger);
81428179
}
81438180
else
81448181
{
@@ -8169,8 +8206,7 @@ extern "C" bool QCALLTYPE SfiInit(StackFrameIterator* pThis, CONTEXT* pStackwalk
81698206
// just clear the thread state.
81708207
pThread->ResetThrowControlForThread();
81718208

8172-
// Skip the SfiInit pinvoke frame
8173-
pFrame = pThread->GetFrame()->PtrNextFrame();
8209+
pFrame = pExInfo->m_pInitialFrame;
81748210

81758211
NotifyExceptionPassStarted(pThis, pThread, pExInfo);
81768212

@@ -8215,6 +8251,7 @@ extern "C" bool QCALLTYPE SfiInit(StackFrameIterator* pThis, CONTEXT* pStackwalk
82158251
!(pExInfo->m_exception == CLRException::GetPreallocatedStackOverflowException());
82168252

82178253
pExInfo->m_StackTraceInfo.AppendElement(canAllocateMemory, NULL, GetRegdisplaySP(pExInfo->m_frameIter.m_crawl.GetRegisterSet()), pMD, &pExInfo->m_frameIter.m_crawl);
8254+
pExInfo->m_StackTraceInfo.SaveStackTrace(canAllocateMemory, pExInfo->m_hThrowable, /*bReplaceStack*/FALSE, /*bSkipLastElement*/FALSE);
82188255

82198256
#if defined(DEBUGGING_SUPPORTED)
82208257
if (NotifyDebuggerOfStub(pThread, pFrame))
@@ -8461,6 +8498,8 @@ extern "C" bool QCALLTYPE SfiNext(StackFrameIterator* pThis, uint* uExCollideCla
84618498
!(pTopExInfo->m_exception == CLRException::GetPreallocatedStackOverflowException());
84628499

84638500
pTopExInfo->m_StackTraceInfo.AppendElement(canAllocateMemory, NULL, GetRegdisplaySP(pTopExInfo->m_frameIter.m_crawl.GetRegisterSet()), pMD, &pTopExInfo->m_frameIter.m_crawl);
8501+
pTopExInfo->m_StackTraceInfo.SaveStackTrace(canAllocateMemory, pTopExInfo->m_hThrowable, /*bReplaceStack*/FALSE, /*bSkipLastElement*/FALSE);
8502+
84648503
#if defined(DEBUGGING_SUPPORTED)
84658504
if (NotifyDebuggerOfStub(pThread, pFrame))
84668505
{

src/coreclr/vm/exinfo.cpp

+29
Original file line numberDiff line numberDiff line change
@@ -330,6 +330,7 @@ ExInfo::ExInfo(Thread *pThread, EXCEPTION_RECORD *pExceptionRecord, CONTEXT *pEx
330330
{
331331
m_StackTraceInfo.AllocateStackTrace();
332332
pThread->GetExceptionState()->m_pCurrentTracker = this;
333+
m_pInitialFrame = pThread->GetFrame();
333334
if (exceptionKind == ExKind::HardwareFault)
334335
{
335336
// Hardware exception handling needs to start on the FaultingExceptionFrame, so we are
@@ -383,8 +384,36 @@ void ExInfo::ReleaseResources()
383384
void ExInfo::PopExInfos(Thread *pThread, void *targetSp)
384385
{
385386
ExInfo *pExInfo = (PTR_ExInfo)pThread->GetExceptionState()->GetCurrentExceptionTracker();
387+
#if defined(DEBUGGING_SUPPORTED)
388+
DWORD_PTR dwInterceptStackFrame = 0;
389+
390+
// This method may be called on an unmanaged thread, in which case no interception can be done.
391+
if (pExInfo)
392+
{
393+
ThreadExceptionState* pExState = pThread->GetExceptionState();
394+
395+
// If the exception is intercepted, then pop trackers according to the stack frame at which
396+
// the exception is intercepted. We must retrieve the frame pointer before we start popping trackers.
397+
if (pExState->GetFlags()->DebuggerInterceptInfo())
398+
{
399+
pExState->GetDebuggerState()->GetDebuggerInterceptInfo(NULL, NULL, (PBYTE*)&dwInterceptStackFrame,
400+
NULL, NULL);
401+
}
402+
}
403+
#endif // DEBUGGING_SUPPORTED
404+
386405
while (pExInfo && pExInfo < (void*)targetSp)
387406
{
407+
#if defined(DEBUGGING_SUPPORTED)
408+
if (g_pDebugInterface != NULL)
409+
{
410+
if (pExInfo->m_ScannedStackRange.GetUpperBound().SP < dwInterceptStackFrame)
411+
{
412+
g_pDebugInterface->DeleteInterceptContext(pExInfo->m_DebuggerExState.GetDebuggerInterceptContext());
413+
}
414+
}
415+
#endif // DEBUGGING_SUPPORTED
416+
388417
pExInfo->ReleaseResources();
389418
pExInfo = (PTR_ExInfo)pExInfo->m_pPrevNestedInfo;
390419
}

src/coreclr/vm/exinfo.h

+2
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,8 @@ struct ExInfo : public ExceptionTrackerBase
269269
// CONTEXT and REGDISPLAY used by the StackFrameIterator for stack walking
270270
CONTEXT m_exContext;
271271
REGDISPLAY m_regDisplay;
272+
// Initial explicit frame for stack walking
273+
Frame *m_pInitialFrame;
272274

273275
#if defined(TARGET_UNIX)
274276
void TakeExceptionPointersOwnership(PAL_SEHException* ex);

src/coreclr/vm/stackwalk.h

+5
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,10 @@ namespace AsmOffsetsAsserts
6868
class AsmOffsets;
6969
};
7070

71+
#ifdef FEATURE_EH_FUNCLETS
72+
extern "C" void QCALLTYPE AppendExceptionStackFrame(QCall::ObjectHandleOnStack exceptionObj, SIZE_T ip, SIZE_T sp, int flags, ExInfo *pExInfo);
73+
#endif
74+
7175
class CrawlFrame
7276
{
7377
public:
@@ -459,6 +463,7 @@ class CrawlFrame
459463
friend class StackFrameIterator;
460464
#ifdef FEATURE_EH_FUNCLETS
461465
friend class ExceptionTracker;
466+
friend void QCALLTYPE AppendExceptionStackFrame(QCall::ObjectHandleOnStack exceptionObj, SIZE_T ip, SIZE_T sp, int flags, ExInfo *pExInfo);
462467
#endif // FEATURE_EH_FUNCLETS
463468

464469
CodeManState codeManState;

0 commit comments

Comments
 (0)