From 909edec915d0771bbd7513d47f0af815f06097f9 Mon Sep 17 00:00:00 2001 From: Tom McDonald Date: Thu, 9 Apr 2026 12:10:01 -0400 Subject: [PATCH 1/3] Fix x86 runtime async frame pointer mismatch in GetSpForDiagnosticReporting Adjust GetSpForDiagnosticReporting to correctly handle runtime async variant method stack layout on x86. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/coreclr/vm/exceptionhandling.cpp | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/src/coreclr/vm/exceptionhandling.cpp b/src/coreclr/vm/exceptionhandling.cpp index a1d2b1dd7fb1bb..ed722a970a2c6a 100644 --- a/src/coreclr/vm/exceptionhandling.cpp +++ b/src/coreclr/vm/exceptionhandling.cpp @@ -2947,13 +2947,21 @@ void MarkInlinedCallFrameAsEHHelperCall(Frame* pFrame) pInlinedCallFrame->m_Datum = (PTR_PInvokeMethodDesc)((TADDR)pInlinedCallFrame->m_Datum | (TADDR)InlinedCallFrameMarker::ExceptionHandlingHelper); } -static TADDR GetSpForDiagnosticReporting(REGDISPLAY *pRD) +static TADDR GetSpForDiagnosticReporting(REGDISPLAY *pRD, MethodDesc *pMD = NULL) { #ifdef ESTABLISHER_FRAME_ADDRESS_IS_CALLER_SP TADDR sp = CallerStackFrame::FromRegDisplay(pRD).SP; #if defined(TARGET_X86) - sp -= sizeof(TADDR); // For X86 we want the address 1 pointer into the callee. -#endif // defined(TARGET_X86) + sp -= sizeof(TADDR); + // On x86, runtime async methods have stack parameters that cause CallerSP + // to sit above the parameter area. The DBI uses PCTAddr as the frame + // pointer, which is at the return address (below the parameters). + // Subtract an extra sizeof(TADDR) to account for the stack parameter. + if (pMD != NULL && pMD->IsAsyncMethod()) + { + sp -= sizeof(TADDR); + } +#endif return sp; #else return GetSP(pRD->pCurrentContext); @@ -2989,7 +2997,7 @@ extern "C" void QCALLTYPE AppendExceptionStackFrame(QCall::ObjectHandleOnStack e // Notify the debugger that we are on the first pass for a managed exception. // Note that this callback is made for every managed frame. - TADDR spForDebugger = GetSpForDiagnosticReporting(pExInfo->m_frameIter.m_crawl.GetRegisterSet()); + TADDR spForDebugger = GetSpForDiagnosticReporting(pExInfo->m_frameIter.m_crawl.GetRegisterSet(), pExInfo->m_frameIter.m_crawl.GetFunction()); EEToDebuggerExceptionInterfaceWrapper::FirstChanceManagedException(pThread, ip, spForDebugger); if (!pExInfo->DeliveredFirstChanceNotification()) @@ -3127,7 +3135,7 @@ void CallCatchFunclet(OBJECTREF throwable, BYTE* pHandlerIP, REGDISPLAY* pvRegDi MethodDesc *pMD = exInfo->m_frameIter.m_crawl.GetFunction(); // Profiler, debugger and ETW events - TADDR spForDebugger = GetSpForDiagnosticReporting(pvRegDisplay); + TADDR spForDebugger = GetSpForDiagnosticReporting(pvRegDisplay, pMD); exInfo->MakeCallbacksRelatedToHandler(true, pThread, pMD, &exInfo->m_ClauseForCatch, (DWORD_PTR)pHandlerIP, spForDebugger); EH_LOG((LL_INFO100, "Calling catch funclet at %p\n", pHandlerIP)); @@ -3341,7 +3349,7 @@ void CallFinallyFunclet(BYTE* pHandlerIP, REGDISPLAY* pvRegDisplay, ExInfo* exIn MethodDesc *pMD = exInfo->m_frameIter.m_crawl.GetFunction(); // Profiler, debugger and ETW events - TADDR spForDebugger = GetSpForDiagnosticReporting(pvRegDisplay); + TADDR spForDebugger = GetSpForDiagnosticReporting(pvRegDisplay, pMD); exInfo->MakeCallbacksRelatedToHandler(true, pThread, pMD, &exInfo->m_CurrentClause, (DWORD_PTR)pHandlerIP, spForDebugger); EH_LOG((LL_INFO100, "Calling finally funclet at %p\n", pHandlerIP)); @@ -3375,7 +3383,7 @@ extern "C" CLR_BOOL QCALLTYPE CallFilterFunclet(QCall::ObjectHandleOnStack excep pExInfo->m_csfEnclosingClause = CallerStackFrame::FromRegDisplay(pExInfo->m_frameIter.m_crawl.GetRegisterSet()); MethodDesc *pMD = pExInfo->m_frameIter.m_crawl.GetFunction(); // Profiler, debugger and ETW events - TADDR spForDebugger = GetSpForDiagnosticReporting(pvRegDisplay); + TADDR spForDebugger = GetSpForDiagnosticReporting(pvRegDisplay, pMD); pExInfo->MakeCallbacksRelatedToHandler(true, pThread, pMD, &pExInfo->m_CurrentClause, (DWORD_PTR)pFilterIP, spForDebugger); EH_LOG((LL_INFO100, "Calling filter funclet at %p\n", pFilterIP)); @@ -3654,7 +3662,7 @@ static void NotifyExceptionPassStarted(StackFrameIterator *pThis, Thread *pThrea // We don't need to do anything special for continuable exceptions after calling // this callback. We are going to start unwinding anyway. PCODE uMethodStartPC = pExInfo->m_frameIter.m_crawl.GetCodeInfo()->GetStartAddress(); - TADDR spForDebugger = GetSpForDiagnosticReporting(pRD); + TADDR spForDebugger = GetSpForDiagnosticReporting(pRD, pMD); EEToDebuggerExceptionInterfaceWrapper::FirstChanceManagedExceptionCatcherFound(pThread, pMD, (TADDR) uMethodStartPC, spForDebugger, &pExInfo->m_ClauseForCatch); } From b6c452571782c7dd811ab568bb80691e052e23ac Mon Sep 17 00:00:00 2001 From: Tom McDonald Date: Fri, 10 Apr 2026 20:44:31 -0400 Subject: [PATCH 2/3] Revert "Fix x86 runtime async frame pointer mismatch in GetSpForDiagnosticReporting" This reverts commit 909edec915d0771bbd7513d47f0af815f06097f9. --- src/coreclr/vm/exceptionhandling.cpp | 24 ++++++++---------------- 1 file changed, 8 insertions(+), 16 deletions(-) diff --git a/src/coreclr/vm/exceptionhandling.cpp b/src/coreclr/vm/exceptionhandling.cpp index ed722a970a2c6a..a1d2b1dd7fb1bb 100644 --- a/src/coreclr/vm/exceptionhandling.cpp +++ b/src/coreclr/vm/exceptionhandling.cpp @@ -2947,21 +2947,13 @@ void MarkInlinedCallFrameAsEHHelperCall(Frame* pFrame) pInlinedCallFrame->m_Datum = (PTR_PInvokeMethodDesc)((TADDR)pInlinedCallFrame->m_Datum | (TADDR)InlinedCallFrameMarker::ExceptionHandlingHelper); } -static TADDR GetSpForDiagnosticReporting(REGDISPLAY *pRD, MethodDesc *pMD = NULL) +static TADDR GetSpForDiagnosticReporting(REGDISPLAY *pRD) { #ifdef ESTABLISHER_FRAME_ADDRESS_IS_CALLER_SP TADDR sp = CallerStackFrame::FromRegDisplay(pRD).SP; #if defined(TARGET_X86) - sp -= sizeof(TADDR); - // On x86, runtime async methods have stack parameters that cause CallerSP - // to sit above the parameter area. The DBI uses PCTAddr as the frame - // pointer, which is at the return address (below the parameters). - // Subtract an extra sizeof(TADDR) to account for the stack parameter. - if (pMD != NULL && pMD->IsAsyncMethod()) - { - sp -= sizeof(TADDR); - } -#endif + sp -= sizeof(TADDR); // For X86 we want the address 1 pointer into the callee. +#endif // defined(TARGET_X86) return sp; #else return GetSP(pRD->pCurrentContext); @@ -2997,7 +2989,7 @@ extern "C" void QCALLTYPE AppendExceptionStackFrame(QCall::ObjectHandleOnStack e // Notify the debugger that we are on the first pass for a managed exception. // Note that this callback is made for every managed frame. - TADDR spForDebugger = GetSpForDiagnosticReporting(pExInfo->m_frameIter.m_crawl.GetRegisterSet(), pExInfo->m_frameIter.m_crawl.GetFunction()); + TADDR spForDebugger = GetSpForDiagnosticReporting(pExInfo->m_frameIter.m_crawl.GetRegisterSet()); EEToDebuggerExceptionInterfaceWrapper::FirstChanceManagedException(pThread, ip, spForDebugger); if (!pExInfo->DeliveredFirstChanceNotification()) @@ -3135,7 +3127,7 @@ void CallCatchFunclet(OBJECTREF throwable, BYTE* pHandlerIP, REGDISPLAY* pvRegDi MethodDesc *pMD = exInfo->m_frameIter.m_crawl.GetFunction(); // Profiler, debugger and ETW events - TADDR spForDebugger = GetSpForDiagnosticReporting(pvRegDisplay, pMD); + TADDR spForDebugger = GetSpForDiagnosticReporting(pvRegDisplay); exInfo->MakeCallbacksRelatedToHandler(true, pThread, pMD, &exInfo->m_ClauseForCatch, (DWORD_PTR)pHandlerIP, spForDebugger); EH_LOG((LL_INFO100, "Calling catch funclet at %p\n", pHandlerIP)); @@ -3349,7 +3341,7 @@ void CallFinallyFunclet(BYTE* pHandlerIP, REGDISPLAY* pvRegDisplay, ExInfo* exIn MethodDesc *pMD = exInfo->m_frameIter.m_crawl.GetFunction(); // Profiler, debugger and ETW events - TADDR spForDebugger = GetSpForDiagnosticReporting(pvRegDisplay, pMD); + TADDR spForDebugger = GetSpForDiagnosticReporting(pvRegDisplay); exInfo->MakeCallbacksRelatedToHandler(true, pThread, pMD, &exInfo->m_CurrentClause, (DWORD_PTR)pHandlerIP, spForDebugger); EH_LOG((LL_INFO100, "Calling finally funclet at %p\n", pHandlerIP)); @@ -3383,7 +3375,7 @@ extern "C" CLR_BOOL QCALLTYPE CallFilterFunclet(QCall::ObjectHandleOnStack excep pExInfo->m_csfEnclosingClause = CallerStackFrame::FromRegDisplay(pExInfo->m_frameIter.m_crawl.GetRegisterSet()); MethodDesc *pMD = pExInfo->m_frameIter.m_crawl.GetFunction(); // Profiler, debugger and ETW events - TADDR spForDebugger = GetSpForDiagnosticReporting(pvRegDisplay, pMD); + TADDR spForDebugger = GetSpForDiagnosticReporting(pvRegDisplay); pExInfo->MakeCallbacksRelatedToHandler(true, pThread, pMD, &pExInfo->m_CurrentClause, (DWORD_PTR)pFilterIP, spForDebugger); EH_LOG((LL_INFO100, "Calling filter funclet at %p\n", pFilterIP)); @@ -3662,7 +3654,7 @@ static void NotifyExceptionPassStarted(StackFrameIterator *pThis, Thread *pThrea // We don't need to do anything special for continuable exceptions after calling // this callback. We are going to start unwinding anyway. PCODE uMethodStartPC = pExInfo->m_frameIter.m_crawl.GetCodeInfo()->GetStartAddress(); - TADDR spForDebugger = GetSpForDiagnosticReporting(pRD, pMD); + TADDR spForDebugger = GetSpForDiagnosticReporting(pRD); EEToDebuggerExceptionInterfaceWrapper::FirstChanceManagedExceptionCatcherFound(pThread, pMD, (TADDR) uMethodStartPC, spForDebugger, &pExInfo->m_ClauseForCatch); } From a8062139134ec8d4ba254c2112c57c08e5e139b6 Mon Sep 17 00:00:00 2001 From: Tom McDonald Date: Fri, 10 Apr 2026 21:10:00 -0400 Subject: [PATCH 3/3] Fix x86 FindFrame mismatch for methods with stack parameters On x86, the runtime sends CallerSP - sizeof(TADDR) as the frame pointer for exception notifications (see GetSpForDiagnosticReporting). Since this does not account for the stack parameter size, the DBI's IsContainedInFrame now queries the DAC for stackParamSize and adjusts the frame pointer before matching. Also switches FindFrame to use TARGET_X86 instead of HOST_64BIT for the ifdef guard, using exact FramePointer comparison on all non-x86 platforms. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/coreclr/debug/di/rsthread.cpp | 31 ++++++++++++++++++++++++++----- 1 file changed, 26 insertions(+), 5 deletions(-) diff --git a/src/coreclr/debug/di/rsthread.cpp b/src/coreclr/debug/di/rsthread.cpp index f32c499371a44f..3fa699c5556cae 100644 --- a/src/coreclr/debug/di/rsthread.cpp +++ b/src/coreclr/debug/di/rsthread.cpp @@ -1412,13 +1412,13 @@ HRESULT CordbThread::FindFrame(ICorDebugFrame ** ppFrame, FramePointer fp) ICorDebugFrame * pIFrame = pSSW->GetFrame(i); CordbFrame * pCFrame = CordbFrame::GetCordbFrameFromInterface(pIFrame); -#if defined(HOST_64BIT) - // On 64-bit we can simply compare the FramePointer. +#if !defined(TARGET_X86) + // Compare the FramePointer to determine if the frame matches if (pCFrame->GetFramePointer() == fp) -#else // !HOST_64BIT - // On other platforms, we need to do a more elaborate check. +#else + // On x86 we need to do a more elaborate check. The reason is that on x86, the FramePointer is always the same as the value of EBP, so we can just check if the input FramePointer is contained in the frame. However, on other platforms, the FramePointer may not be the same as the value of RSP, so we need to check if the input FramePointer is the same as the one of the frame. if (pCFrame->IsContainedInFrame(fp)) -#endif // HOST_64BIT +#endif { *ppFrame = pIFrame; (*ppFrame)->AddRef(); @@ -4773,6 +4773,27 @@ bool CordbFrame::IsContainedInFrame(FramePointer fp) CORDB_ADDRESS sp = PTR_TO_CORDB_ADDRESS(fp.GetSPValue()); +#if defined(TARGET_X86) + // On x86, the runtime sends CallerSP - sizeof(TADDR) as the frame pointer + // for exception notifications (see GetSpForDiagnosticReporting). Since this + // does not account for the stack parameter size, we adjust for it here. + if (sp > stackEnd) + { + CordbNativeFrame * pNativeFrame = GetAsNativeFrame(); + if (pNativeFrame != NULL) + { + CORDB_ADDRESS codeAddr = pNativeFrame->GetNativeCode()->GetAddress(); + ULONG32 stackParamSize = 0; + IDacDbiInterface * pDAC = GetProcess()->GetDAC(); + if (SUCCEEDED(pDAC->GetStackParameterSize(codeAddr + m_ip, &stackParamSize)) + && stackParamSize > 0) + { + sp -= stackParamSize; + } + } + } +#endif // TARGET_X86 + if ((stackStart <= sp) && (sp <= stackEnd)) { return true;