Skip to content

Commit d41eea4

Browse files
kotlarmilosCopilot
andcommitted
[interp] Report async-suspension locals to the debugger via GetAsyncLocals
When a managed async method is suspended at an await, the right-side debugger asks the runtime for the live values of the user's locals and arguments at that suspension point so they can be displayed on the call stack. The JIT path supplies this through ICorJitInfo::reportAsyncDebugInfo, which the runtime stores in the standard DebugInfoStore and DacDbiInterfaceImpl::GetAsyncLocals reads back on the right side. The interpreter compiler never participated in this pipeline, so ICorDebugAsyncFrame::GetArgument and GetLocalVariableEx returned CORDBG_E_IL_VAR_NOT_AVAILABLE for every IL variable on every interpreted async frame. This adds a small parallel pipeline for the interpreter. InterpAsyncSuspendData now carries a per-suspension-point table of {ilVarNum, dataOffset} entries, populated from EmitAsyncSuspensionPoint as it lays out which IL vars are kept live across the suspension. On the DAC side, GetAsyncLocals detects an interpreter method via pMethodDesc->GetInterpreterCode(), treats the incoming state as a byte offset from InterpByteCodeStart (the existing interpreter encoding for INTOP_HANDLE_CONTINUATION_RESUME), decodes the bytecode at startIp + state, expects INTOP_HANDLE_CONTINUATION_RESUME, and surfaces the matching InterpAsyncSuspendData::asyncDebugVars table. The JIT DebugInfoStore path is unchanged. This fixes the following interpreter debugger test failures: - Async.AsyncSimple - Async.AsyncBreakpoint - Async.AsyncBreakpointJmc - Async.AsyncGeneric - Async.AsyncRecursive - Async.AsyncSharedGeneric - Async.AsyncV2CallingAsyncV1Iterator - Async.AsyncGenericStepInto Three sibling tests (Async.AsyncStepIntoThunk, Async.AsyncStepIntoSync, Async.AsyncGenericStepIntoThunk) still fail. They need interp-aware async-thunk-skip support in DebuggerStepper / InterpreterStepHelper::SetupStep, which is independent of the async debug info plumbing addressed here. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 40365cd commit d41eea4

3 files changed

Lines changed: 106 additions & 0 deletions

File tree

src/coreclr/debug/daccess/dacdbiimpl.cpp

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,10 @@
2323

2424
#include "dacdbiimpl.h"
2525

26+
#ifdef FEATURE_INTERPRETER
27+
#include <interpretershared.h>
28+
#endif // FEATURE_INTERPRETER
29+
2630
#ifdef FEATURE_COMINTEROP
2731
#include "runtimecallablewrapper.h"
2832
#include "comcallablewrapper.h"
@@ -7771,6 +7775,50 @@ HRESULT STDMETHODCALLTYPE DacDbiInterfaceImpl::GetAsyncLocals(VMPTR_MethodDesc v
77717775
{
77727776
return hr;
77737777
}
7778+
7779+
#ifdef FEATURE_INTERPRETER
7780+
PTR_InterpByteCodeStart pByteCodeStart = pMethodDesc->GetInterpreterCode();
7781+
if (pByteCodeStart != NULL)
7782+
{
7783+
PTR_InterpMethod pInterpMethod = pByteCodeStart->Method;
7784+
if (pInterpMethod != NULL)
7785+
{
7786+
// ip address of the suspension RESUME opcode = startIp_addr + state.
7787+
TADDR ipAddr = dac_cast<TADDR>(pByteCodeStart) + state;
7788+
TADDR bytecodeStart = dac_cast<TADDR>(pByteCodeStart) + sizeof(InterpByteCodeStart);
7789+
TADDR bytecodeEnd = bytecodeStart + (TADDR)pInterpMethod->codeSize * sizeof(int32_t);
7790+
if (ipAddr >= bytecodeStart && (ipAddr + 2 * sizeof(int32_t)) <= bytecodeEnd)
7791+
{
7792+
DPTR(int32_t) pIp = dac_cast<DPTR(int32_t)>(ipAddr);
7793+
int32_t opcode = pIp[0];
7794+
if (opcode == INTOP_HANDLE_CONTINUATION_RESUME)
7795+
{
7796+
int32_t dataItemIndex = pIp[1];
7797+
// pDataItems is an array of void*, where the entry is the InterpAsyncSuspendData*.
7798+
DPTR(TADDR) pDataItems = dac_cast<DPTR(TADDR)>(dac_cast<TADDR>(pInterpMethod->pDataItems));
7799+
TADDR suspendDataAddr = (pDataItems != NULL) ? pDataItems[dataItemIndex] : 0;
7800+
if (suspendDataAddr != 0)
7801+
{
7802+
DPTR(InterpAsyncSuspendData) pSuspendData = dac_cast<DPTR(InterpAsyncSuspendData)>(suspendDataAddr);
7803+
int32_t numVars = pSuspendData->numAsyncDebugVars;
7804+
DPTR(InterpAsyncDebugVar) pVars = dac_cast<DPTR(InterpAsyncDebugVar)>(dac_cast<TADDR>(pSuspendData->asyncDebugVars));
7805+
if (numVars > 0 && pVars != NULL)
7806+
{
7807+
pAsyncLocals->Alloc(numVars);
7808+
for (int32_t i = 0; i < numVars; i++)
7809+
{
7810+
(*pAsyncLocals)[i].offset = pVars[i].dataOffset;
7811+
(*pAsyncLocals)[i].ilVarNum = pVars[i].ilVarNum;
7812+
}
7813+
}
7814+
}
7815+
}
7816+
}
7817+
}
7818+
return hr;
7819+
}
7820+
#endif // FEATURE_INTERPRETER
7821+
77747822
TADDR nativeCodeStartAddr;
77757823
if (codeAddr != (TADDR)NULL)
77767824
{

src/coreclr/interpreter/compiler.cpp

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1909,6 +1909,17 @@ void InterpCompiler::PrepareInterpMethod()
19091909
count++; // Include the terminator entry
19101910
m_methodDataBuilder.AllocateIntervalMap(count);
19111911
}
1912+
1913+
// Reserve IntervalMaps space for the per-suspension async debug-var array.
1914+
for (int32_t i = 0; i < m_asyncSuspendDataItems.GetSize(); i++)
1915+
{
1916+
InterpAsyncSuspendData* sd = m_asyncSuspendDataItems.Get(i);
1917+
if (sd->numAsyncDebugVars > 0)
1918+
{
1919+
m_methodDataBuilder.AllocateInSection(InterpMethodDataSection::IntervalMaps,
1920+
sd->numAsyncDebugVars * (uint32_t)sizeof(InterpAsyncDebugVar));
1921+
}
1922+
}
19121923
}
19131924

19141925
int32_t* InterpCompiler::GetCode(int32_t *pCodeSize)
@@ -2028,6 +2039,23 @@ InterpMethod* InterpCompiler::FinalizeMethodData(void* baseAddressRW, void* base
20282039
dstDataRW->zeroedLocalsIntervals = dstMapRX;
20292040
currentIntervalMapOffset += mapSize;
20302041
}
2042+
2043+
// Copy async debug-var array into IntervalMaps section and fix up pointer
2044+
if (srcData->numAsyncDebugVars > 0 && srcData->asyncDebugVars != nullptr)
2045+
{
2046+
uint32_t dbgSize = (uint32_t)srcData->numAsyncDebugVars * (uint32_t)sizeof(InterpAsyncDebugVar);
2047+
assert(currentIntervalMapOffset + dbgSize <= intervalMapsSectionEnd);
2048+
2049+
InterpAsyncDebugVar* dstDbgRW = (InterpAsyncDebugVar*)(rwBase + currentIntervalMapOffset);
2050+
InterpAsyncDebugVar* dstDbgRX = (InterpAsyncDebugVar*)(rxBase + currentIntervalMapOffset);
2051+
memcpy(dstDbgRW, srcData->asyncDebugVars, dbgSize);
2052+
dstDataRW->asyncDebugVars = dstDbgRX;
2053+
currentIntervalMapOffset += dbgSize;
2054+
}
2055+
else
2056+
{
2057+
dstDataRW->asyncDebugVars = nullptr;
2058+
}
20312059

20322060
currentAsyncOffset += sizeof(InterpAsyncSuspendData);
20332061
}
@@ -5783,6 +5811,7 @@ void InterpCompiler::EmitSuspend(const CORINFO_CALL_INFO &callInfo, Continuation
57835811

57845812
TArray<int32_t, MemPoolAllocator> liveVars(GetMemPoolAllocator(IMK_AsyncSuspend));
57855813
TArray<int32_t, MemPoolAllocator> varsToZero(GetMemPoolAllocator(IMK_AsyncSuspend));
5814+
TArray<InterpAsyncDebugVar, MemPoolAllocator> debugVars(GetMemPoolAllocator(IMK_AsyncSuspend));
57865815

57875816
// Step 2: Handle live stack vars (excluding return value)
57885817
int32_t stackDepth = (int32_t)(m_pStackPointer - m_pStackBase);
@@ -5935,6 +5964,14 @@ void InterpCompiler::EmitSuspend(const CORINFO_CALL_INFO &callInfo, Continuation
59355964

59365965
INTERP_DUMP("Allocate var %d at data offset %d (size %d) (offsetFromStartOfReturnValue = %d)\n", var, currentOffset, size, currentOffset - returnValueDataStartOffset);
59375966

5967+
if (var >= 0 && var < m_numILVars && var != returnValueVar)
5968+
{
5969+
InterpAsyncDebugVar dbgVar;
5970+
dbgVar.ilVarNum = (uint32_t)var;
5971+
dbgVar.dataOffset = (uint32_t)(currentOffset + OFFSETOF__CORINFO_Continuation__data);
5972+
debugVars.Add(dbgVar);
5973+
}
5974+
59385975
if (interpType == InterpTypeO)
59395976
{
59405977
// Mark as GC reference
@@ -6015,6 +6052,18 @@ void InterpCompiler::EmitSuspend(const CORINFO_CALL_INFO &callInfo, Continuation
60156052
suspendData->resumeInfo.DiagnosticIP = (size_t)NULL;
60166053
suspendData->methodStartIP = 0; // This is filled in by logic later in emission once we know the final address of the method
60176054
suspendData->continuationArgOffset = m_pVars[m_continuationArgIndex].offset;
6055+
6056+
suspendData->numAsyncDebugVars = debugVars.GetSize();
6057+
if (suspendData->numAsyncDebugVars > 0)
6058+
{
6059+
size_t dbgBytes = suspendData->numAsyncDebugVars * sizeof(InterpAsyncDebugVar);
6060+
suspendData->asyncDebugVars = (InterpAsyncDebugVar*)AllocMethodData(dbgBytes);
6061+
memcpy(suspendData->asyncDebugVars, debugVars.GetUnderlyingArray(), dbgBytes);
6062+
}
6063+
else
6064+
{
6065+
suspendData->asyncDebugVars = NULL;
6066+
}
60186067
suspendData->asyncMethodReturnType = NULL;
60196068
switch (m_methodInfo->args.retType)
60206069
{

src/coreclr/interpreter/inc/interpretershared.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,12 @@ struct InterpIntervalMapEntry
199199
uint32_t countBytes = 0; // If count is 0 then this is the end marker.
200200
};
201201

202+
struct InterpAsyncDebugVar
203+
{
204+
uint32_t ilVarNum;
205+
uint32_t dataOffset;
206+
};
207+
202208
struct InterpAsyncSuspendData
203209
{
204210
CORINFO_AsyncResumeInfo resumeInfo;
@@ -218,6 +224,9 @@ struct InterpAsyncSuspendData
218224
COMPILER_SHARED_TYPE(CORINFO_METHOD_HANDLE, DPTR(MethodDesc), captureSyncContextMethod);
219225
COMPILER_SHARED_TYPE(CORINFO_METHOD_HANDLE, DPTR(MethodDesc), restoreExecutionContextMethod);
220226
COMPILER_SHARED_TYPE(CORINFO_METHOD_HANDLE, DPTR(MethodDesc), restoreContextsOnSuspensionMethod);
227+
228+
int32_t numAsyncDebugVars;
229+
InterpAsyncDebugVar* asyncDebugVars;
221230
};
222231

223232
#endif

0 commit comments

Comments
 (0)