From 8b81cc2fe31cb1ee36a861f39053dfa061edb6d4 Mon Sep 17 00:00:00 2001 From: Matous Kozak Date: Mon, 3 Aug 2026 17:36:24 +0200 Subject: [PATCH] Fix interpreter SetIP breakpoint handling Preserve destination breakpoint bypasses across SetIP and emit debugger mappings for catch and filter entries with an exception on the evaluation stack. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 285cb880-7c44-45a2-a8bb-61eb764db71c --- src/coreclr/interpreter/compiler.cpp | 10 ++++++++-- src/coreclr/vm/interpexec.cpp | 22 +++++++++++++++++----- 2 files changed, 25 insertions(+), 7 deletions(-) diff --git a/src/coreclr/interpreter/compiler.cpp b/src/coreclr/interpreter/compiler.cpp index 913f3b697229e9..c0a86685a49223 100644 --- a/src/coreclr/interpreter/compiler.cpp +++ b/src/coreclr/interpreter/compiler.cpp @@ -1072,8 +1072,12 @@ int32_t* InterpCompiler::EmitCodeIns(int32_t *ip, InterpInst *ins, TArraynativeOffset); + bool isEmptyILStack = (ins->flags & INTERP_INST_FLAG_EMPTY_IL_STACK) != 0; + bool isFilterOrCatchEntry = m_pCBB->isFilterOrCatchFuncletEntry && (ilOffset == (uint32_t)m_pCBB->ilOffset); + // Only emit mapping entries at IL offsets where the evaluation stack is empty - if ((ins->flags & INTERP_INST_FLAG_EMPTY_IL_STACK) && + // or at filter and catch entries, where the exception object is on the stack. + if ((isEmptyILStack || isFilterOrCatchEntry) && ((m_ILToNativeMapSize == 0) || (m_pILToNativeMap[m_ILToNativeMapSize - 1].ilOffset != ilOffset))) { // This code assumes that instructions for the same IL offset are emitted in a single run without @@ -1096,7 +1100,9 @@ int32_t* InterpCompiler::EmitCodeIns(int32_t *ip, InterpInst *ins, TArraySetFilterContext(NULL); - // The debugger may have moved execution via SetIP. If so, drop the bypass - // (it was set up for the original IP) and resume at the new context via - // ResumeAfterCatchException. + // The debugger may have moved execution via SetIP. Preserve a bypass + // created for the destination before resuming at the new context. if ((GetIP(&ctx) != (PCODE)ip) || (GetSP(&ctx) != (DWORD64)pFrame)) { + if (GetIP(&ctx) == (PCODE)savedBypassAddress) + { + pThreadContext->m_bypassAddress = savedBypassAddress; + pThreadContext->m_bypassOpcode = savedBypassOpcode; + } + ThrowResumeAfterCatchException(GetSP(&ctx), GetIP(&ctx)); } @@ -1500,10 +1505,17 @@ void InterpExecMethod(InterpreterFrame *pInterpreterFrame, InterpMethodContextFr INTOP_CASE(INTOP_BREAKPOINT) { pFrame->ip = ip; - LOG((LF_CORDB, LL_INFO10000, "InterpExecMethod: Hit breakpoint at IP %p\n", ip)); - InterpBreakpoint(ip, pFrame, stack, pInterpreterFrame); int32_t bypassOpcode = 0; + if (pThreadContext->HasBypass(ip, &bypassOpcode)) + { + LOG((LF_CORDB, LL_INFO10000, "InterpExecMethod: Pre-callback bypass at IP %p with opcode 0x%x\n", ip, bypassOpcode)); + pThreadContext->ClearBypass(); + INTOP_DISPATCH(bypassOpcode); + } + + LOG((LF_CORDB, LL_INFO10000, "InterpExecMethod: Hit breakpoint at IP %p\n", ip)); + InterpBreakpoint(ip, pFrame, stack, pInterpreterFrame); // After debugger callback, check if bypass was set on the thread context if (pThreadContext->HasBypass(ip, &bypassOpcode))