Skip to content
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
4bbc946
add interpreter tier
matouskozak Nov 21, 2025
53ede58
helper functions for interpreter
matouskozak Nov 21, 2025
0fd2137
interpreter piece of user breakpoints support
matouskozak Nov 21, 2025
4f43f13
execution control for interpreter breakpoints.
matouskozak Nov 21, 2025
64162be
add PATCH_KIND_NATIVE_INTERPRETER
matouskozak Nov 21, 2025
3c43e35
fix interpreter
matouskozak Nov 21, 2025
382a026
hookup interpreter execution control to controller
matouskozak Nov 21, 2025
d642c1e
add newlines
matouskozak Nov 21, 2025
3e38b86
remove duplicate code
matouskozak Nov 21, 2025
96f2aa4
ip adjustment for breakpoint handling
matouskozak Nov 22, 2025
6e07378
simplify m_codeRegionInfo initialization
matouskozak Nov 25, 2025
a14f680
Revert "add interpreter tier"
matouskozak Nov 25, 2025
34d41d1
move PAL_TRY outside of the main interpreter loop
matouskozak Nov 25, 2025
46ce8a2
Apply suggestions from code review
matouskozak Nov 25, 2025
fb1809d
const breakpoint parameters
matouskozak Nov 25, 2025
33e439d
fix conversion issue
matouskozak Nov 25, 2025
5b087e1
exclude browser for now
matouskozak Nov 25, 2025
3afd5ac
validate codeInfo before retrieving jit manager
matouskozak Nov 26, 2025
b7244a1
remove ifdef debug from interperter
matouskozak Nov 27, 2025
57175cd
call `FirstChanceNativeException` from interp
matouskozak Nov 28, 2025
be9ce49
remove special handling around GetIP(context)
matouskozak Nov 28, 2025
c2cf072
simplify by removing PATCH_KIND_NATIVE_INTERPRETER
matouskozak Nov 28, 2025
b92fe7f
whitespace
matouskozak Nov 28, 2025
8ba7347
revert changes that are no longer needed
matouskozak Nov 28, 2025
0975866
add FaultingExceptionFrame for IP adjustment
matouskozak Dec 2, 2025
5fffda4
remove isInterpreterCode argument and use JitManag
matouskozak Dec 2, 2025
025061d
Merge branch 'main' into prototype-interp-user-breakpoints
matouskozak Dec 2, 2025
133d1ce
remove stale comment
matouskozak Dec 2, 2025
063343c
Merge branch 'main' into prototype-interp-user-breakpoints
matouskozak Dec 2, 2025
8610b6e
partially address review comments
matouskozak Dec 4, 2025
0f621e1
Merge branch 'prototype-interp-user-breakpoints' of github.com:matous…
matouskozak Dec 4, 2025
06b760e
assert for missing pExecControl in interp case
matouskozak Dec 4, 2025
4751352
add INTOP_HALT
matouskozak Dec 4, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions src/coreclr/debug/daccess/request.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1287,6 +1287,11 @@ HRESULT ClrDataAccess::GetTieredVersions(
nativeCodeAddrs[count].OptimizationTier = DacpTieredVersionData::OptimizationTier_Optimized;
}

if ((int)(*iter).GetOptimizationTier() == NativeCodeVersion::OptimizationTierInterpreted)
{
nativeCodeAddrs[count].OptimizationTier = DacpTieredVersionData::OptimizationTier_Interpreted;
}

++count;

if (count >= cNativeCodeAddrs)
Expand Down
2 changes: 2 additions & 0 deletions src/coreclr/debug/ee/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ set(CORDBEE_SOURCES_DAC_AND_WKS
debugger.cpp
debuggermessagebox.cpp
debuggermodule.cpp
executioncontrol.cpp
functioninfo.cpp
)

Expand All @@ -20,6 +21,7 @@ set(CORDBEE_HEADERS_DAC_AND_WKS
datatest.h
debugger.h
debugger.inl
executioncontrol.h
)

set(CORDBEE_SOURCES_WKS
Expand Down
50 changes: 45 additions & 5 deletions src/coreclr/debug/ee/controller.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1502,6 +1502,13 @@ bool DebuggerController::ApplyPatch(DebuggerControllerPatch *patch)
return true;
}

#ifdef FEATURE_INTERPRETER
EECodeInfo codeInfo((PCODE)patch->address);
IJitManager* pJitManager = codeInfo.GetJitManager();
if (pJitManager != NULL && pJitManager == ExecutionManager::GetInterpreterJitManager())
Comment thread
kotlarmilos marked this conversation as resolved.
Outdated
return pJitManager->GetExecutionControl()->ApplyPatch(patch);
#endif // FEATURE_INTERPRETER

#if _DEBUG
VerifyExecutableAddress((BYTE*)patch->address);
#endif
Expand Down Expand Up @@ -1615,6 +1622,13 @@ bool DebuggerController::UnapplyPatch(DebuggerControllerPatch *patch)
return true;
}

#ifdef FEATURE_INTERPRETER
EECodeInfo codeInfo((PCODE)patch->address);
IJitManager* pJitManager = codeInfo.GetJitManager();
if (pJitManager != NULL && pJitManager == ExecutionManager::GetInterpreterJitManager())
return pJitManager->GetExecutionControl()->UnapplyPatch(patch);
#endif // FEATURE_INTERPRETER

LPVOID baseAddress = (LPVOID)(patch->address);

#if !defined(HOST_OSX) || !defined(HOST_ARM64)
Expand Down Expand Up @@ -2595,10 +2609,16 @@ bool DebuggerController::MatchPatch(Thread *thread,
{
LOG((LF_CORDB, LL_INFO100000, "DC::MP: EIP:0x%p\n", GetIP(context)));

// Caller should have already matched our addresses.
if (patch->address != dac_cast<PTR_CORDB_ADDRESS_TYPE>(GetIP(context)))
// For interpreter patches, we can't compare against the context IP because:
// - patch->address is the bytecode address
// - GetIP(context) is the native C++ address inside the interpreter loop
if (patch->kind != PATCH_KIND_NATIVE_INTERPRETER)
{
return false;
// Caller should have already matched our addresses.
if (patch->address != dac_cast<PTR_CORDB_ADDRESS_TYPE>(GetIP(context)))
{
return false;
}
}

// <BUGNUM>RAID 67173 -</BUGNUM> we'll make sure that intermediate patches have NULL
Expand Down Expand Up @@ -3063,6 +3083,7 @@ DPOSS_ACTION DebuggerController::DispatchPatchOrSingleStep(Thread *thread, CONTE
CrstHolderWithState lockController(&g_criticalSection);

TADDR originalAddress = 0;
bool isInterpreterBreakpoint = false;

#ifdef FEATURE_METADATA_UPDATER
DebuggerControllerPatch *dcpEnCOriginal = NULL;
Expand Down Expand Up @@ -3113,6 +3134,17 @@ DPOSS_ACTION DebuggerController::DispatchPatchOrSingleStep(Thread *thread, CONTE

LOG((LF_CORDB|LF_ENC, LL_EVERYTHING, "DC::DPOSS ScanForTriggers called and returned.\n"));

// Check if we're debugging with the interpreter by checking the JIT manager for this address
#ifdef FEATURE_INTERPRETER
{
IJitManager* pJitManager = ExecutionManager::FindJitMan((PCODE)address);
if (pJitManager != NULL && pJitManager == ExecutionManager::GetInterpreterJitManager())
{
isInterpreterBreakpoint = true;
LOG((LF_CORDB, LL_EVERYTHING, "DC::DPOSS Interpreter breakpoint detected at %p\n", address));
}
}
#endif // FEATURE_INTERPRETER

// If we setip, then that will change the address in the context.
// Remeber the old address so that we can compare it to the context's ip and see if it changed.
Expand Down Expand Up @@ -3140,7 +3172,7 @@ DPOSS_ACTION DebuggerController::DispatchPatchOrSingleStep(Thread *thread, CONTE
SENDIPCEVENT_BEGIN(g_pDebugger, thread);

// Now that we've resumed from blocking, check if somebody did a SetIp on us.
bool fIpChanged = (originalAddress != GetIP(context));
bool fIpChanged = isInterpreterBreakpoint ? false : (originalAddress != GetIP(context));

// Send the events outside of the controller lock
bool anyEventsSent = false;
Expand Down Expand Up @@ -3217,7 +3249,7 @@ DPOSS_ACTION DebuggerController::DispatchPatchOrSingleStep(Thread *thread, CONTE
}
#endif

ActivatePatchSkip(thread, dac_cast<PTR_CBYTE>(GetIP(pCtx)), FALSE
ActivatePatchSkip(thread, isInterpreterBreakpoint ? dac_cast<PTR_CBYTE>((CORDB_ADDRESS_TYPE*)originalAddress) : dac_cast<PTR_CBYTE>(GetIP(pCtx)), FALSE
#ifdef OUT_OF_PROCESS_SETTHREADCONTEXT
, pDebuggerSteppingInfo
#endif
Expand Down Expand Up @@ -4565,6 +4597,14 @@ bool DebuggerController::DispatchNativeException(EXCEPTION_RECORD *pException,
switch (dwCode)
{
case EXCEPTION_BREAKPOINT:
// Check if this is an interpreter breakpoint by examining if we have the special exception information
// Interpreter breakpoints provide: [0] = bytecode, [1] = pFrame, [2] = stack
Comment thread
matouskozak marked this conversation as resolved.
Outdated
if (pException->NumberParameters >= 3 && pException->ExceptionInformation[1] != 0)
{
// Store the bytecode pointer for patch lookup
ip = (CORDB_ADDRESS_TYPE*)pException->ExceptionInformation[0];
}

// EIP should be properly set up at this point.
result = DebuggerController::DispatchPatchOrSingleStep(pCurThread,
pContext,
Expand Down
4 changes: 3 additions & 1 deletion src/coreclr/debug/ee/controller.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
#if !defined(DACCESS_COMPILE)

#include "frameinfo.h"
#include "executioncontrol.h"

/* ------------------------------------------------------------------------- *
* Forward declarations
Expand Down Expand Up @@ -363,7 +364,7 @@ typedef DebuggerFunctionKey1 UNALIGNED DebuggerFunctionKey;
//
// NativeUnmanaged: A patch applied to any kind of native code.

enum DebuggerPatchKind { PATCH_KIND_IL_PRIMARY, PATCH_KIND_IL_REPLICA, PATCH_KIND_NATIVE_MANAGED, PATCH_KIND_NATIVE_UNMANAGED };
enum DebuggerPatchKind { PATCH_KIND_IL_PRIMARY, PATCH_KIND_IL_REPLICA, PATCH_KIND_NATIVE_MANAGED, PATCH_KIND_NATIVE_UNMANAGED, PATCH_KIND_NATIVE_INTERPRETER };

// struct DebuggerControllerPatch: An entry in the patch (hash) table,
// this should contain all the info that's needed over the course of a
Expand Down Expand Up @@ -462,6 +463,7 @@ struct DebuggerControllerPatch
BOOL IsILPrimaryPatch();
BOOL IsILReplicaPatch();
DebuggerPatchKind GetKind();
void SetKind(DebuggerPatchKind newKind);

// A patch has DJI if it was created with it or if it has been mapped to a
// function that has been jitted while JIT tracking was on. It does not
Expand Down
7 changes: 6 additions & 1 deletion src/coreclr/debug/ee/controller.inl
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,11 @@ inline DebuggerPatchKind DebuggerControllerPatch::GetKind()
return kind;
}

inline void DebuggerControllerPatch::SetKind(DebuggerPatchKind newKind)
{
kind = newKind;
}

inline BOOL DebuggerControllerPatch::IsILPrimaryPatch()
{
LIMITED_METHOD_CONTRACT;
Expand All @@ -47,7 +52,7 @@ inline BOOL DebuggerControllerPatch::IsManagedPatch()

inline BOOL DebuggerControllerPatch::IsNativePatch()
{
return (kind == PATCH_KIND_NATIVE_MANAGED || kind == PATCH_KIND_NATIVE_UNMANAGED || (IsILReplicaPatch() && !offsetIsIL));
return (kind == PATCH_KIND_NATIVE_MANAGED || kind == PATCH_KIND_NATIVE_UNMANAGED || kind == PATCH_KIND_NATIVE_INTERPRETER ||(IsILReplicaPatch() && !offsetIsIL));
}

inline BOOL DebuggerControllerPatch::IsEnCRemapPatch()
Expand Down
17 changes: 17 additions & 0 deletions src/coreclr/debug/ee/debugger.h
Original file line number Diff line number Diff line change
Expand Up @@ -1358,6 +1358,23 @@ class CodeRegionInfo
(size_t *) &m_sizeOfColdCode);
}

// Initialize for interpreter code - no hot/cold split, just bytecode
void InitializeForInterpreter(PCODE bytecodeAddr, SIZE_T bytecodeSize)
{
CONTRACTL
{
NOTHROW;
GC_NOTRIGGER;
SUPPORTS_DAC;
}
CONTRACTL_END;

m_addrOfHotCode = bytecodeAddr;
m_addrOfColdCode = (PCODE)NULL; // No cold code for interpreter
m_sizeOfHotCode = bytecodeSize;
m_sizeOfColdCode = 0;
}

// Converts an offset within a method to a code address
PCODE OffsetToAddress(SIZE_T offset)
{
Expand Down
73 changes: 73 additions & 0 deletions src/coreclr/debug/ee/executioncontrol.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
//*****************************************************************************
// File: executioncontrol.cpp
//
// Implementation of execution control for interpreter breakpoints.
// WIP
Comment thread
matouskozak marked this conversation as resolved.
Outdated
//
//*****************************************************************************

#include "stdafx.h"
#include "executioncontrol.h"
#include "controller.h"
#include "../../vm/codeman.h"

#ifdef FEATURE_INTERPRETER
#include "../../interpreter/intops.h"
#endif

#if !defined(DACCESS_COMPILE)
#ifdef FEATURE_INTERPRETER

//=============================================================================
// InterpreterExecutionControl - Interpreter bytecode breakpoints
//=============================================================================

InterpreterExecutionControl InterpreterExecutionControl::s_instance;

InterpreterExecutionControl* InterpreterExecutionControl::GetInstance()
{
return &s_instance;
}

bool InterpreterExecutionControl::ApplyPatch(DebuggerControllerPatch* patch)
{
_ASSERTE(patch != NULL);
_ASSERTE(!patch->IsActivated());
_ASSERTE(patch->IsBound());

LOG((LF_CORDB, LL_INFO10000, "InterpreterEC::ApplyPatch %p at bytecode addr %p\n",
patch, patch->address));

patch->SetKind(PATCH_KIND_NATIVE_INTERPRETER);
patch->opcode = *(int32_t*)patch->address;;
Comment thread
matouskozak marked this conversation as resolved.
Outdated
*(uint32_t*)patch->address = INTOP_BREAKPOINT;
Comment thread
matouskozak marked this conversation as resolved.

LOG((LF_CORDB, LL_EVERYTHING, "InterpreterEC::ApplyPatch Breakpoint inserted at %p, saved opcode %x\n",
patch->address, patch->opcode));

return true;
}

bool InterpreterExecutionControl::UnapplyPatch(DebuggerControllerPatch* patch)
{
_ASSERTE(patch != NULL);
_ASSERTE(patch->address != NULL);
_ASSERTE(patch->IsActivated());

LOG((LF_CORDB, LL_INFO1000, "InterpreterEC::UnapplyPatch %p at bytecode addr %p, replacing with original opcode 0x%x\n",
patch, patch->address, patch->opcode));

// Restore the original opcode
*(uint32_t*)patch->address = patch->opcode;
InitializePRD(&(patch->opcode));

LOG((LF_CORDB, LL_EVERYTHING, "InterpreterEC::UnapplyPatch Restored opcode at %p\n",
patch->address));

return true;
}

#endif // FEATURE_INTERPRETER
#endif // !DACCESS_COMPILE
49 changes: 49 additions & 0 deletions src/coreclr/debug/ee/executioncontrol.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
//*****************************************************************************
// File: executioncontrol.h
//
// Abstraction for breakpoint and single-step operations across different
// code execution strategies (JIT, interpreter, R2R).
// TODO: WIP - currently only supports interpreter breakpoint handling.
Comment thread
matouskozak marked this conversation as resolved.
Outdated
//
//*****************************************************************************

#ifndef EXECUTIONCONTROL_H_
#define EXECUTIONCONTROL_H_

struct DebuggerControllerPatch;

#ifdef FEATURE_INTERPRETER


class IExecutionControl
{
public:
virtual ~IExecutionControl() = default;

virtual bool ApplyPatch(DebuggerControllerPatch* patch) = 0;
virtual bool UnapplyPatch(DebuggerControllerPatch* patch) = 0;
};

typedef DPTR(IExecutionControl) PTR_IExecutionControl;

// Interpreter execution control using bytecode patching
class InterpreterExecutionControl : public IExecutionControl
{
public:
static InterpreterExecutionControl* GetInstance();

// Apply a breakpoint patch
virtual bool ApplyPatch(DebuggerControllerPatch* patch) override;

// Remove a breakpoint patch and restore original instruction
virtual bool UnapplyPatch(DebuggerControllerPatch* patch) override;

private:
InterpreterExecutionControl() = default;
static InterpreterExecutionControl s_instance;
};

#endif // FEATURE_INTERPRETER
#endif // EXECUTIONCONTROL_H_
7 changes: 7 additions & 0 deletions src/coreclr/debug/ee/frameinfo.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1715,6 +1715,13 @@ StackWalkAction DebuggerWalkStackProc(CrawlFrame *pCF, void *data)
{
use = false;
}
#ifdef FEATURE_INTERPRETER
// Avoid treating interpreter frame as a managed frame
else if (frame->GetFrameIdentifier() == FrameIdentifier::InterpreterFrame)
{
use = false;
}
#endif // FEATURE_INTERPRETER
else
{
d->info.managed = true;
Expand Down
23 changes: 21 additions & 2 deletions src/coreclr/debug/ee/functioninfo.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1253,9 +1253,28 @@ void DebuggerJitInfo::Init(TADDR newAddress)
this->m_addrOfCode = (ULONG_PTR)PTR_TO_CORDB_ADDRESS((BYTE*) newAddress);
this->m_jitComplete = true;

this->m_codeRegionInfo.InitializeFromStartAddress(PINSTRToPCODE((TADDR)this->m_addrOfCode));
this->m_sizeOfCode = this->m_codeRegionInfo.getSizeOfTotalCode();
if (this->m_nativeCodeVersion.GetOptimizationTier() != NativeCodeVersion::OptimizationTierInterpreted)
Comment thread
matouskozak marked this conversation as resolved.
Outdated
{
this->m_codeRegionInfo.InitializeFromStartAddress(PINSTRToPCODE((TADDR)this->m_addrOfCode));
}
else
{
// For interpreter code, initialize code region info with IL bytecode info
// We only have "hot" code (the bytecode), no cold regions or funclets
MethodDesc* pMD = this->m_nativeCodeVersion.GetMethodDesc();
COR_ILMETHOD* pILHeader = pMD->GetILHeader();
SIZE_T ilCodeSize = 0;

if (pILHeader != NULL)
{
COR_ILMETHOD_DECODER decoder(pILHeader);
ilCodeSize = decoder.GetCodeSize();
}

this->m_codeRegionInfo.InitializeForInterpreter(PINSTRToPCODE((TADDR)this->m_addrOfCode), ilCodeSize);
}

this->m_sizeOfCode = this->m_codeRegionInfo.getSizeOfTotalCode();
this->m_encVersion = this->m_methodInfo->GetCurrentEnCVersion();

#if defined(FEATURE_EH_FUNCLETS)
Expand Down
1 change: 1 addition & 0 deletions src/coreclr/inc/dacprivate.h
Original file line number Diff line number Diff line change
Expand Up @@ -615,6 +615,7 @@ struct MSLAYOUT DacpTieredVersionData
OptimizationTier_OptimizedTier1OSR,
OptimizationTier_QuickJittedInstrumented,
OptimizationTier_OptimizedTier1Instrumented,
OptimizationTier_Interpreted
};

CLRDATA_ADDRESS NativeCodeAddr;
Expand Down
Loading
Loading