From d0f5121c2bdd14b387556704cc77065d3e0ed363 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 21 May 2026 02:56:40 +0000 Subject: [PATCH 1/4] Implement GetMethodDescParams and GetTypeHandleParams in cDAC with parameterized DacDbiArray allocator Co-authored-by: rcj1 <77995559+rcj1@users.noreply.github.com> --- src/coreclr/debug/daccess/dacdbiimpl.cpp | 43 ++- src/coreclr/debug/daccess/dacdbiimpl.h | 9 +- src/coreclr/debug/di/rspriv.h | 8 + src/coreclr/debug/di/rsthread.cpp | 24 +- src/coreclr/debug/di/rstype.cpp | 5 +- src/coreclr/debug/inc/dacdbiinterface.h | 30 +- src/coreclr/debug/inc/dacdbistructures.h | 2 +- .../vm/datadescriptor/datadescriptor.inc | 1 + .../Contracts/IRuntimeTypeSystem.cs | 1 + .../Constants.cs | 1 + .../Dbi/DacDbiImpl.cs | 281 +++++++++++++++++- .../Dbi/IDacDbiInterface.cs | 6 +- 12 files changed, 351 insertions(+), 60 deletions(-) diff --git a/src/coreclr/debug/daccess/dacdbiimpl.cpp b/src/coreclr/debug/daccess/dacdbiimpl.cpp index 93f85aa3a7e7cf..7895cca29185f5 100644 --- a/src/coreclr/debug/daccess/dacdbiimpl.cpp +++ b/src/coreclr/debug/daccess/dacdbiimpl.cpp @@ -2681,7 +2681,7 @@ HRESULT STDMETHODCALLTYPE DacDbiInterfaceImpl::GetExactTypeHandle(DebuggerIPCE_E // Retrieve the generic type params for a given MethodDesc. This function is specifically // for stackwalking because it requires the generic type token on the stack. -HRESULT STDMETHODCALLTYPE DacDbiInterfaceImpl::GetMethodDescParams(VMPTR_MethodDesc vmMethodDesc, GENERICS_TYPE_TOKEN genericsToken, OUT UINT32 * pcGenericClassTypeParams, OUT TypeParamsList * pGenericTypeParams) +HRESULT STDMETHODCALLTYPE DacDbiInterfaceImpl::EnumerateMethodDescParams(VMPTR_MethodDesc vmMethodDesc, GENERICS_TYPE_TOKEN genericsToken, OUT UINT32 * pcGenericClassTypeParams, FP_TYPEPARAM_CALLBACK fpCallback, CALLBACK_DATA pUserData) { DD_ENTER_MAY_THROW; @@ -2694,7 +2694,7 @@ HRESULT STDMETHODCALLTYPE DacDbiInterfaceImpl::GetMethodDescParams(VMPTR_MethodD ThrowHR(E_INVALIDARG); } - _ASSERTE((pcGenericClassTypeParams != NULL) && (pGenericTypeParams != NULL)); + _ASSERTE((pcGenericClassTypeParams != NULL) && (fpCallback != NULL)); MethodDesc * pMD = vmMethodDesc.GetDacPtr(); @@ -2739,9 +2739,6 @@ HRESULT STDMETHODCALLTYPE DacDbiInterfaceImpl::GetMethodDescParams(VMPTR_MethodD _ASSERTE((classInst.IsEmpty()) == (cGenericClassTypeParams == 0)); _ASSERTE((methodInst.IsEmpty()) == (cGenericMethodTypeParams == 0)); - // allocate memory for the return array - pGenericTypeParams->Alloc(cTotalGenericTypeParams); - for (UINT32 i = 0; i < cTotalGenericTypeParams; i++) { // Retrieve the current type parameter depending on the index. @@ -2755,6 +2752,8 @@ HRESULT STDMETHODCALLTYPE DacDbiInterfaceImpl::GetMethodDescParams(VMPTR_MethodD thCurrent = methodInst[i - cGenericClassTypeParams]; } + DebuggerIPCE_ExpandedTypeData entry; + // There is the possibility that we'll get this far with a dump and not fail, but still // not be able to get full info for a particular param. EX_TRY_ALLOW_DATATARGET_MISSING_MEMORY_WITH_HANDLER @@ -2762,7 +2761,7 @@ HRESULT STDMETHODCALLTYPE DacDbiInterfaceImpl::GetMethodDescParams(VMPTR_MethodD // Fill in the struct using the TypeHandle of the current type parameter if we can. IfFailThrow(TypeHandleToExpandedTypeInfo(NoValueTypeBoxing, (CORDB_ADDRESS)thCurrent.AsTAddr(), - &((*pGenericTypeParams)[i]))); + &entry)); } EX_CATCH_ALLOW_DATATARGET_MISSING_MEMORY_WITH_HANDLER { @@ -2770,9 +2769,11 @@ HRESULT STDMETHODCALLTYPE DacDbiInterfaceImpl::GetMethodDescParams(VMPTR_MethodD TypeHandle thCanon = TypeHandle(g_pCanonMethodTableClass); IfFailThrow(TypeHandleToExpandedTypeInfo(NoValueTypeBoxing, (CORDB_ADDRESS)thCanon.AsTAddr(), - &((*pGenericTypeParams)[i]))); + &entry)); } EX_END_CATCH_ALLOW_DATATARGET_MISSING_MEMORY_WITH_HANDLER + + fpCallback(&entry, pUserData); } } EX_CATCH_HRESULT(hr); @@ -3169,37 +3170,35 @@ HRESULT STDMETHODCALLTYPE DacDbiInterfaceImpl::GetCollectibleTypeStaticAddress(V return hr; } -// DacDbi API: GetTypeHandleParams +// DacDbi API: EnumerateTypeHandleParams // - gets the necessary data for a type handle, i.e. its type parameters, e.g. "String" and "List" from the type handle -// for "Dict>", and sends it back to the right side. -// - pParams is allocated and initialized by this function +// for "Dict>", and sends it back to the right side via the callback. // - This should not fail except for OOM -HRESULT STDMETHODCALLTYPE DacDbiInterfaceImpl::GetTypeHandleParams(VMPTR_TypeHandle vmTypeHandle, OUT TypeParamsList * pParams) +HRESULT STDMETHODCALLTYPE DacDbiInterfaceImpl::EnumerateTypeHandleParams(VMPTR_TypeHandle vmTypeHandle, FP_TYPEPARAM_CALLBACK fpCallback, CALLBACK_DATA pUserData) { DD_ENTER_MAY_THROW HRESULT hr = S_OK; EX_TRY { + _ASSERTE(fpCallback != NULL); TypeHandle typeHandle = TypeHandle::FromPtr(vmTypeHandle.GetDacPtr()); - LOG((LF_CORDB, LL_INFO10000, "D::GTHP: getting type parameters for 0x%08x 0x%0x8.\n", - vmAppDomain.GetDacPtr(), typeHandle.AsPtr())); - + LOG((LF_CORDB, LL_INFO10000, "D::ETHP: enumerating type parameters for 0x%p.\n", typeHandle.AsPtr())); - // Find the class given its type handle. - _ASSERTE(pParams->IsEmpty()); - pParams->Alloc(typeHandle.GetNumGenericArgs()); + unsigned int count = typeHandle.GetNumGenericArgs(); + Instantiation inst = typeHandle.GetInstantiation(); - // collect type information for each type parameter - for (unsigned int i = 0; i < pParams->Count(); ++i) + for (unsigned int i = 0; i < count; ++i) { + DebuggerIPCE_ExpandedTypeData entry; IfFailThrow(TypeHandleToExpandedTypeInfo(NoValueTypeBoxing, - (CORDB_ADDRESS)typeHandle.GetInstantiation()[i].AsTAddr(), - &((*pParams)[i]))); + (CORDB_ADDRESS)inst[i].AsTAddr(), + &entry)); + fpCallback(&entry, pUserData); } - LOG((LF_CORDB, LL_INFO10000, "D::GTHP: sending result")); + LOG((LF_CORDB, LL_INFO10000, "D::ETHP: sending result")); } EX_CATCH_HRESULT(hr); return hr; diff --git a/src/coreclr/debug/daccess/dacdbiimpl.h b/src/coreclr/debug/daccess/dacdbiimpl.h index d521fc9a550aca..e9ce8fae28ba2d 100644 --- a/src/coreclr/debug/daccess/dacdbiimpl.h +++ b/src/coreclr/debug/daccess/dacdbiimpl.h @@ -262,7 +262,7 @@ class DacDbiInterfaceImpl : // Retrieve the generic type params for a given MethodDesc. This function is specifically // for stackwalking because it requires the generic type token on the stack. - HRESULT STDMETHODCALLTYPE GetMethodDescParams(VMPTR_MethodDesc vmMethodDesc, GENERICS_TYPE_TOKEN genericsToken, OUT UINT32 * pcGenericClassTypeParams, OUT TypeParamsList * pGenericTypeParams); + HRESULT STDMETHODCALLTYPE EnumerateMethodDescParams(VMPTR_MethodDesc vmMethodDesc, GENERICS_TYPE_TOKEN genericsToken, OUT UINT32 * pcGenericClassTypeParams, FP_TYPEPARAM_CALLBACK fpCallback, CALLBACK_DATA pUserData); // Get the target field address of a context or thread local static. HRESULT STDMETHODCALLTYPE GetThreadStaticAddress(VMPTR_FieldDesc vmField, VMPTR_Thread vmRuntimeThread, OUT CORDB_ADDRESS * pRetVal); @@ -273,12 +273,9 @@ class DacDbiInterfaceImpl : // Get information about a field added with Edit And Continue. HRESULT STDMETHODCALLTYPE GetEnCHangingFieldInfo(const EnCHangingFieldInfo * pEnCFieldInfo, OUT FieldData * pFieldData, OUT BOOL * pfStatic); - // GetTypeHandleParams gets the necessary data for a type handle, i.e. its - // type parameters, e.g. "String" and "List" from the type handle - // for "Dict>", and sends it back to the right side. - // This should not fail except for OOM + // EnumerateTypeHandleParams gets the necessary data for a type handle, i.e. its type parameters, e.g. "String" and "List" from the type handle for "Dict - HRESULT STDMETHODCALLTYPE GetTypeHandleParams(VMPTR_TypeHandle vmTypeHandle, OUT TypeParamsList * pParams); + HRESULT STDMETHODCALLTYPE EnumerateTypeHandleParams(VMPTR_TypeHandle vmTypeHandle, FP_TYPEPARAM_CALLBACK fpCallback, CALLBACK_DATA pUserData); // DacDbi API: GetSimpleType // gets the metadata token and assembly corresponding to a simple type diff --git a/src/coreclr/debug/di/rspriv.h b/src/coreclr/debug/di/rspriv.h index ebde65295f96e4..9906aa94619bc5 100644 --- a/src/coreclr/debug/di/rspriv.h +++ b/src/coreclr/debug/di/rspriv.h @@ -105,6 +105,14 @@ struct CallbackAccumulator { return reinterpret_cast(pUserData); } + + // Adapter for FP_*_CALLBACK signatures that deliver an item by pointer + // (e.g. FP_TYPEPARAM_CALLBACK). Pass as the callback with &acc as pUserData: + // pDAC->EnumX(args, &CallbackAccumulator::PushCallback, &acc); + static void PushCallback(T * pItem, CALLBACK_DATA pUserData) + { + From(pUserData)->Push(*pItem); + } }; diff --git a/src/coreclr/debug/di/rsthread.cpp b/src/coreclr/debug/di/rsthread.cpp index 55426957217cce..46a8bebc7a1d38 100644 --- a/src/coreclr/debug/di/rsthread.cpp +++ b/src/coreclr/debug/di/rsthread.cpp @@ -7658,14 +7658,16 @@ void CordbJITILFrame::LoadGenericArgs() IDacDbiInterface * pDAC = GetProcess()->GetDAC(); UINT32 cGenericClassTypeParams = 0; - DacDbiArrayList rgGenericTypeParams; + CallbackAccumulator acc; - IfFailThrow(pDAC->GetMethodDescParams(m_nativeFrame->GetNativeCode()->GetVMNativeCodeMethodDescToken(), + IfFailThrow(pDAC->EnumerateMethodDescParams(m_nativeFrame->GetNativeCode()->GetVMNativeCodeMethodDescToken(), m_frameParamsToken, &cGenericClassTypeParams, - &rgGenericTypeParams)); + &CallbackAccumulator::PushCallback, + &acc)); + IfFailThrow(acc.hrError); - UINT32 cTotalGenericTypeParams = rgGenericTypeParams.Count(); + UINT32 cTotalGenericTypeParams = (UINT32)acc.items.Size(); NewInterfaceArrayHolder ppGenericArgs( new CordbType *[cTotalGenericTypeParams](), @@ -7676,7 +7678,7 @@ void CordbJITILFrame::LoadGenericArgs() // creates a CordbType object for the generic argument CordbType *newType; IfFailThrow(CordbType::TypeDataToType(GetCurrentAppDomain(), - &(rgGenericTypeParams[i]), + &(acc.items[i]), &newType)); // We add a ref as the instantiation will be stored away in the @@ -11520,7 +11522,7 @@ void CordbAsyncFrame::LoadGenericArgs() IDacDbiInterface * pDAC = GetProcess()->GetDAC(); UINT32 cGenericClassTypeParams = 0; - DacDbiArrayList rgGenericTypeParams; + CallbackAccumulator acc; UINT32 genericArgIndex; HRESULT result = pDAC->GetGenericArgTokenIndex( @@ -11552,12 +11554,14 @@ void CordbAsyncFrame::LoadGenericArgs() genericTypeParam = resolvedToken; } - IfFailThrow(pDAC->GetMethodDescParams(m_vmMethodDesc, + IfFailThrow(pDAC->EnumerateMethodDescParams(m_vmMethodDesc, genericTypeParam, &cGenericClassTypeParams, - &rgGenericTypeParams)); + &CallbackAccumulator::PushCallback, + &acc)); + IfFailThrow(acc.hrError); - UINT32 cTotalGenericTypeParams = rgGenericTypeParams.Count(); + UINT32 cTotalGenericTypeParams = (UINT32)acc.items.Size(); NewInterfaceArrayHolder ppGenericArgs( new CordbType *[cTotalGenericTypeParams](), @@ -11568,7 +11572,7 @@ void CordbAsyncFrame::LoadGenericArgs() // creates a CordbType object for the generic argument CordbType *newType; IfFailThrow(CordbType::TypeDataToType(m_pAppDomain, - &(rgGenericTypeParams[i]), + &(acc.items[i]), &newType)); // We add a ref as the instantiation will be stored away in the diff --git a/src/coreclr/debug/di/rstype.cpp b/src/coreclr/debug/di/rstype.cpp index 79ea8dabe419c5..4e1b06c4cd6a34 100644 --- a/src/coreclr/debug/di/rstype.cpp +++ b/src/coreclr/debug/di/rstype.cpp @@ -1373,7 +1373,10 @@ HRESULT CordbType::InstantiateFromTypeHandle(CordbAppDomain * pAppDomain, TypeParamsList params; { RSLockHolder lockHolder(pProcess->GetProcessLock()); - IfFailThrow(pProcess->GetDAC()->GetTypeHandleParams(vmTypeHandle, ¶ms)); + CallbackAccumulator acc; + IfFailThrow(pProcess->GetDAC()->EnumerateTypeHandleParams(vmTypeHandle, &CallbackAccumulator::PushCallback, &acc)); + IfFailThrow(acc.hrError); + params.Init(acc.items.Ptr(), (int)acc.items.Size()); } // convert the parameter type information to a list of CordbTypeInstances (one for each parameter) diff --git a/src/coreclr/debug/inc/dacdbiinterface.h b/src/coreclr/debug/inc/dacdbiinterface.h index c4616a7a8a5e74..a6a46dd8febc07 100644 --- a/src/coreclr/debug/inc/dacdbiinterface.h +++ b/src/coreclr/debug/inc/dacdbiinterface.h @@ -1602,6 +1602,11 @@ IDacDbiInterface : public IUnknown ArgInfoList * pArgInfo, VMPTR_TypeHandle * pVmTypeHandle) = 0; + // Callback invoked for each type parameter enumerated by EnumerateMethodDescParams or + // EnumerateTypeHandleParams. The callback must not throw. Implementations typically push + // the value into an accumulator stashed in pUserData. + typedef void (*FP_TYPEPARAM_CALLBACK)(DebuggerIPCE_ExpandedTypeData * pTypeData, CALLBACK_DATA pUserData); + // // Retrieve the generic type params for a given MethodDesc. This function is specifically // for stackwalking because it requires the generic type token on the stack. @@ -1610,19 +1615,20 @@ IDacDbiInterface : public IUnknown // vmMethodDesc - the method in question // genericsToken - the generic type token in the stack frame owned by the method // pcGenericClassTypeParams - [out] - // pGenericTypeParams - [out] + // fpCallback - [in] + // pUserData - [in] // // pcGenericClassTypeParams - out parameter; returns the number of type parameters for the class // containing the method in question; must not be NULL - // pGenericTypeParams - out parameter; returns an array of type parameters and - // the count of the total number of type parameters; must not be NULL + // fpCallback - callback invoked once per type parameter, in order: class type parameters first + // then method type parameters; must not be NULL + // pUserData - opaque user data passed through to the callback // // Notes: - // The memory for the array is allocated by this function on the Dbi heap. - // The caller is responsible for releasing it. + // The callback must not throw. // - virtual HRESULT STDMETHODCALLTYPE GetMethodDescParams(VMPTR_MethodDesc vmMethodDesc, GENERICS_TYPE_TOKEN genericsToken, OUT UINT32 * pcGenericClassTypeParams, OUT TypeParamsList * pGenericTypeParams) = 0; + virtual HRESULT STDMETHODCALLTYPE EnumerateMethodDescParams(VMPTR_MethodDesc vmMethodDesc, GENERICS_TYPE_TOKEN genericsToken, OUT UINT32 * pcGenericClassTypeParams, FP_TYPEPARAM_CALLBACK fpCallback, CALLBACK_DATA pUserData) = 0; // Get the target field address of a thread local static. // Arguments: @@ -1669,20 +1675,16 @@ IDacDbiInterface : public IUnknown virtual HRESULT STDMETHODCALLTYPE GetEnCHangingFieldInfo(const EnCHangingFieldInfo * pEnCFieldInfo, OUT FieldData * pFieldData, OUT BOOL * pfStatic) = 0; - // GetTypeHandleParams gets the necessary data for a type handle, i.e. its + // EnumerateTypeHandleParams gets the necessary data for a type handle, i.e. its // type parameters, e.g. "String" and "List" from the type handle // for "Dict>", and sends it back to the right side. // Arguments: // input: vmTypeHandle - type handle for the type - // output: pParams - list of instances of DebuggerIPCE_ExpandedTypeData, - // one for each type parameter. These will be used on the - // RS to build up an instantiation which will allow - // building an instance of CordbType for the top-level - // type. The memory for this list is allocated on the dbi - // heap in this function. + // fpCallback - callback invoked once per type parameter (must not be NULL) + // pUserData - opaque user data passed through to the callback // This will not fail except for OOM - virtual HRESULT STDMETHODCALLTYPE GetTypeHandleParams(VMPTR_TypeHandle vmTypeHandle, OUT TypeParamsList * pParams) = 0; + virtual HRESULT STDMETHODCALLTYPE EnumerateTypeHandleParams(VMPTR_TypeHandle vmTypeHandle, FP_TYPEPARAM_CALLBACK fpCallback, CALLBACK_DATA pUserData) = 0; // GetSimpleType // gets the metadata token and assembly corresponding to a simple type diff --git a/src/coreclr/debug/inc/dacdbistructures.h b/src/coreclr/debug/inc/dacdbistructures.h index 07637e944b0d28..a7c69a4cfeba02 100644 --- a/src/coreclr/debug/inc/dacdbistructures.h +++ b/src/coreclr/debug/inc/dacdbistructures.h @@ -802,7 +802,7 @@ typedef DacDbiArrayList TypeParamsList; // A struct for passing version information from DBI to DAC. // See code:CordbProcess::CordbProcess#DBIVersionChecking for more information. -const DWORD kCurrentDacDbiProtocolBreakingChangeCounter = 1; +const DWORD kCurrentDacDbiProtocolBreakingChangeCounter = 2; struct DbiVersion { diff --git a/src/coreclr/vm/datadescriptor/datadescriptor.inc b/src/coreclr/vm/datadescriptor/datadescriptor.inc index e5c8a863c4f5cf..0a0c523288de7e 100644 --- a/src/coreclr/vm/datadescriptor/datadescriptor.inc +++ b/src/coreclr/vm/datadescriptor/datadescriptor.inc @@ -1537,6 +1537,7 @@ CDAC_GLOBAL(FieldOffsetBigRVA, T_UINT32, FIELD_OFFSET_BIG_RVA) CDAC_GLOBAL(FieldOffsetDynamicRVA, T_UINT32, FIELD_OFFSET_DYNAMIC_RVA) CDAC_GLOBAL_POINTER(ClrNotificationArguments, &::g_clrNotificationArguments) CDAC_GLOBAL_POINTER(ArrayBoundsZero, cdac_data::ArrayBoundsZero) +CDAC_GLOBAL_POINTER(CanonMethodTable, &::g_pCanonMethodTableClass) CDAC_GLOBAL_POINTER(ContinuationMethodTable, &::g_pContinuationClassIfSubTypeCreated) CDAC_GLOBAL_POINTER(ExceptionMethodTable, &::g_pExceptionClass) CDAC_GLOBAL_POINTER(FreeObjectMethodTable, &::g_pFreeObjectMethodTable) diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IRuntimeTypeSystem.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IRuntimeTypeSystem.cs index b2f109681dfc27..ea60992a213e1a 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IRuntimeTypeSystem.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IRuntimeTypeSystem.cs @@ -160,6 +160,7 @@ public interface IRuntimeTypeSystem : IContract ReadOnlySpan GetInstantiation(TypeHandle typeHandle) => throw new NotImplementedException(); + public bool IsClassInited(TypeHandle typeHandle) => throw new NotImplementedException(); public bool IsInitError(TypeHandle typeHandle) => throw new NotImplementedException(); bool IsGenericTypeDefinition(TypeHandle typeHandle) => throw new NotImplementedException(); diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Constants.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Constants.cs index 808b28a8b44de6..2d9f7a75bbd68e 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Constants.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Constants.cs @@ -32,6 +32,7 @@ public static class Globals public const string RecommendedReaderVersion = nameof(RecommendedReaderVersion); public const string ContinuationMethodTable = nameof(ContinuationMethodTable); + public const string CanonMethodTable = nameof(CanonMethodTable); public const string ExceptionMethodTable = nameof(ExceptionMethodTable); public const string FreeObjectMethodTable = nameof(FreeObjectMethodTable); public const string ObjectMethodTable = nameof(ObjectMethodTable); diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/DacDbiImpl.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/DacDbiImpl.cs index fccf75ac7680f3..caa01a08e27963 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/DacDbiImpl.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/DacDbiImpl.cs @@ -1787,8 +1787,156 @@ public int GetApproxTypeHandle(nint pTypeData, ulong* pRetVal) public int GetExactTypeHandle(nint pTypeData, nint pArgInfo, ulong* pVmTypeHandle) => LegacyFallbackHelper.CanFallback() && _legacy is not null ? _legacy.GetExactTypeHandle(pTypeData, pArgInfo, pVmTypeHandle) : HResults.E_NOTIMPL; - public int GetMethodDescParams(ulong vmMethodDesc, ulong genericsToken, uint* pcGenericClassTypeParams, nint pGenericTypeParams) - => LegacyFallbackHelper.CanFallback() && _legacy is not null ? _legacy.GetMethodDescParams(vmMethodDesc, genericsToken, pcGenericClassTypeParams, pGenericTypeParams) : HResults.E_NOTIMPL; + public int EnumerateMethodDescParams(ulong vmMethodDesc, ulong genericsToken, uint* pcGenericClassTypeParams, + delegate* unmanaged fpCallback, nint pUserData) + { + int hr = HResults.S_OK; +#if DEBUG + List entries = new(); +#endif + uint cClassParams = 0; + try + { + if (vmMethodDesc == 0) + throw new ArgumentException("MethodDesc cannot be null", nameof(vmMethodDesc)); + if (pcGenericClassTypeParams == null) + throw new ArgumentNullException(nameof(pcGenericClassTypeParams)); + if (fpCallback == null) + throw new ArgumentNullException(nameof(fpCallback)); + + IRuntimeTypeSystem rts = _target.Contracts.RuntimeTypeSystem; + Contracts.MethodDescHandle pRepMethod = rts.GetMethodDescHandle(new TargetPointer(vmMethodDesc)); + TypeHandle thRepMt = rts.GetTypeHandle(rts.GetMethodTable(pRepMethod)); + + // Try to resolve exact instantiations using the generics token. Fall back + // to canonical when the token is unavailable, the method isn't shared, or any + // resolution step fails (analogous to native's SanityCheck path). + Contracts.MethodDescHandle pSpecificMethod = pRepMethod; + TypeHandle thSpecificClass = thRepMt; + bool fExact = false; + + GenericContextLoc ctxLoc = rts.GetGenericContextLoc(pRepMethod); + if (ctxLoc == GenericContextLoc.None) + { + fExact = true; + } + else if (genericsToken != 0) + { + try + { + bool hasMethodInst = rts.GetGenericMethodInstantiation(pRepMethod).Length != 0; + if (ctxLoc == GenericContextLoc.InstArg && hasMethodInst) + { + // RequiresInstMethodDescArg: token is a MethodDesc*. + pSpecificMethod = rts.GetMethodDescHandle(new TargetPointer(genericsToken)); + thSpecificClass = rts.GetTypeHandle(rts.GetMethodTable(pSpecificMethod)); + fExact = true; + } + else if (ctxLoc == GenericContextLoc.InstArg) + { + // RequiresInstMethodTableArg: token is a MethodTable*. + thSpecificClass = rts.GetTypeHandle(new TargetPointer(genericsToken)); + fExact = true; + } + else + { + // AcquiresInstMethodTableFromThis: token is some MethodTable*; it may be a + // subclass, so walk the parent chain to find the exact declaring class. + TypeHandle thFromThis = rts.GetTypeHandle(new TargetPointer(genericsToken)); + TypeHandle thMatch = GetMethodTableMatchingParentClass(rts, thFromThis, thRepMt); + if (!thMatch.IsNull) + { + thSpecificClass = thMatch; + fExact = true; + } + } + } + catch (VirtualReadException) + { + // Any failure resolving the exact token: fall back to canonical. + fExact = false; + } + } + + if (!fExact) + { + pSpecificMethod = pRepMethod; + thSpecificClass = thRepMt; + } + + // Project the specific class onto the method's declaring class to get the class instantiation. + TargetPointer specMethodMtPtr = rts.GetMethodTable(pSpecificMethod); + TypeHandle thSpecMethodMt = rts.GetTypeHandle(specMethodMtPtr); + TypeHandle thMatchingParent = GetMethodTableMatchingParentClass(rts, thSpecificClass, thSpecMethodMt); + ReadOnlySpan classInst = thMatchingParent.IsNull + ? ReadOnlySpan.Empty + : rts.GetInstantiation(thMatchingParent); + ReadOnlySpan methodInst = rts.GetGenericMethodInstantiation(pSpecificMethod); + + cClassParams = (uint)classInst.Length; + *pcGenericClassTypeParams = cClassParams; + + // Resolve the System.__Canon TypeHandle for per-parameter fallback. + TargetPointer canonMtPtr = _target.ReadPointer(_target.ReadGlobalPointer(Constants.Globals.CanonMethodTable)); + TypeHandle thCanon = rts.GetTypeHandle(canonMtPtr); + + DebuggerIPCE_ExpandedTypeData entry; + for (int i = 0; i < classInst.Length; i++) + { + FillExpandedTypeDataWithCanonFallback(rts, classInst[i], thCanon, &entry); +#if DEBUG + entries.Add(entry); +#endif + fpCallback(&entry, pUserData); + } + for (int i = 0; i < methodInst.Length; i++) + { + FillExpandedTypeDataWithCanonFallback(rts, methodInst[i], thCanon, &entry); +#if DEBUG + entries.Add(entry); +#endif + fpCallback(&entry, pUserData); + } + } + catch (System.Exception ex) + { + hr = ex.HResult; + } + +#if DEBUG + if (_legacy is not null) + { + DebugExpandedTypeInfo.Clear(); + uint cClassParamsLocal = 0; + delegate* unmanaged debugCallbackPtr = &EnumExpandedTypeInfoCallback; + int hrLocal = _legacy.EnumerateMethodDescParams(vmMethodDesc, genericsToken, &cClassParamsLocal, debugCallbackPtr, 0); + Debug.ValidateHResult(hr, hrLocal); + if (hr == HResults.S_OK) + { + Debug.Assert(cClassParams == cClassParamsLocal, + $"cDAC class params: {cClassParams}, DAC: {cClassParamsLocal}"); + + List legacyEntries = DebugExpandedTypeInfo; + if (!entries.SequenceEqual(legacyEntries)) + { + Debug.Assert(entries.Count == legacyEntries.Count, + $"cDAC param count: {entries.Count}, DAC: {legacyEntries.Count}"); + + int compareCount = Math.Min(entries.Count, legacyEntries.Count); + for (int i = 0; i < compareCount; i++) + { + Debug.Assert(entries[i].Equals(legacyEntries[i]), + $"Type param {i} mismatch{Environment.NewLine}" + + $" cDAC: ({FormatExpandedTypeData(entries[i])}){Environment.NewLine}" + + $" DAC: ({FormatExpandedTypeData(legacyEntries[i])})"); + } + } + } + DebugExpandedTypeInfo.Clear(); + } +#endif + return hr; + } public int GetThreadStaticAddress(ulong vmField, ulong vmRuntimeThread, ulong* pRetVal) { @@ -1853,8 +2001,87 @@ public int GetCollectibleTypeStaticAddress(ulong vmField, ulong* pRetVal) public int GetEnCHangingFieldInfo(nint pEnCFieldInfo, nint pFieldData, Interop.BOOL* pfStatic) => LegacyFallbackHelper.CanFallback() && _legacy is not null ? _legacy.GetEnCHangingFieldInfo(pEnCFieldInfo, pFieldData, pfStatic) : HResults.E_NOTIMPL; - public int GetTypeHandleParams(ulong vmTypeHandle, nint pParams) - => LegacyFallbackHelper.CanFallback() && _legacy is not null ? _legacy.GetTypeHandleParams(vmTypeHandle, pParams) : HResults.E_NOTIMPL; + public int EnumerateTypeHandleParams(ulong vmTypeHandle, + delegate* unmanaged fpCallback, nint pUserData) + { + int hr = HResults.S_OK; +#if DEBUG + List entries = new(); +#endif + try + { + if (fpCallback == null) + throw new ArgumentNullException(nameof(fpCallback)); + + IRuntimeTypeSystem rts = _target.Contracts.RuntimeTypeSystem; + TypeHandle typeHandle = rts.GetTypeHandle(new TargetPointer(vmTypeHandle)); + ReadOnlySpan instantiation = rts.GetInstantiation(typeHandle); + + DebuggerIPCE_ExpandedTypeData entry; + for (int i = 0; i < instantiation.Length; i++) + { + TypeHandleToExpandedTypeInfoImpl(rts, AreValueTypesBoxed.NoValueTypeBoxing, instantiation[i], &entry); + fpCallback(&entry, pUserData); +#if DEBUG + entries.Add(entry); +#endif + } + } + catch (System.Exception ex) + { + hr = ex.HResult; + } + +#if DEBUG + if (_legacy is not null) + { + DebugExpandedTypeInfo.Clear(); + delegate* unmanaged debugCallbackPtr = &EnumExpandedTypeInfoCallback; + int hrLocal = _legacy.EnumerateTypeHandleParams(vmTypeHandle, debugCallbackPtr, 0); + Debug.ValidateHResult(hr, hrLocal); + if (hr == HResults.S_OK) + { + List legacyEntries = DebugExpandedTypeInfo; + if (!entries.SequenceEqual(legacyEntries)) + { + Debug.Assert(entries.Count == legacyEntries.Count, + $"cDAC param count: {entries.Count}, DAC: {legacyEntries.Count}"); + + int compareCount = Math.Min(entries.Count, legacyEntries.Count); + for (int i = 0; i < compareCount; i++) + { + Debug.Assert(entries[i].Equals(legacyEntries[i]), + $"Type param {i} mismatch{Environment.NewLine}" + + $" cDAC: ({FormatExpandedTypeData(entries[i])}){Environment.NewLine}" + + $" DAC: ({FormatExpandedTypeData(legacyEntries[i])})"); + } + } + } + DebugExpandedTypeInfo.Clear(); + } +#endif + return hr; + } + +#if DEBUG + [ThreadStatic] + private static List? _debugExpandedTypeInfo; + + private static List DebugExpandedTypeInfo + => _debugExpandedTypeInfo ??= new(); + + [UnmanagedCallersOnly] + private static void EnumExpandedTypeInfoCallback(DebuggerIPCE_ExpandedTypeData* pTypeData, nint _) + { + DebugExpandedTypeInfo.Add(*pTypeData); + } + + private static string FormatExpandedTypeData(DebuggerIPCE_ExpandedTypeData e) => + $"elementType={e.elementType}, " + + $"token=0x{e.ClassTypeData_metadataToken:x}, " + + $"vmAssembly=0x{e.ClassTypeData_vmAssembly:x}, " + + $"vmTypeHandle=0x{e.ClassTypeData_typeHandle:x}"; +#endif public int GetSimpleType(int simpleType, uint* pMetadataToken, ulong* pVmModule) { @@ -2905,6 +3132,52 @@ public int GetGenericArgTokenIndex(ulong vmMethod, uint* pIndex) return hr; } + // Fills a DebuggerIPCE_ExpandedTypeData entry for a single type parameter, falling back to System.__Canon on failure. + private void FillExpandedTypeDataWithCanonFallback(IRuntimeTypeSystem rts, TypeHandle typeHandle, TypeHandle thCanon, DebuggerIPCE_ExpandedTypeData* pTypeInfo) + { + try + { + TypeHandleToExpandedTypeInfoImpl(rts, AreValueTypesBoxed.NoValueTypeBoxing, typeHandle, pTypeInfo); + } + catch (VirtualReadException) + { + TypeHandleToExpandedTypeInfoImpl(rts, AreValueTypesBoxed.NoValueTypeBoxing, thCanon, pTypeInfo); + } + } + + // True if `a` and `b` share the same non-zero TypeDef RID and Module. + // Mirrors native MethodTable::HasSameTypeDefAs. + private static bool HasSameTypeDefAs(IRuntimeTypeSystem rts, TypeHandle a, TypeHandle b) + { + if (a.Address == b.Address) + return true; + uint ridA = EcmaMetadataUtils.GetRowId(rts.GetTypeDefToken(a)); + uint ridB = EcmaMetadataUtils.GetRowId(rts.GetTypeDefToken(b)); + if (ridA == 0 || ridA != ridB) + return false; + return rts.GetModule(a) == rts.GetModule(b); + } + + // Walks the parent chain of `start` and returns the first MethodTable whose TypeDef matches `parent`, + // or default if no match is found. The walk is bounded by a hard iteration cap to defend against + // cycles observed in corrupt dumps. Mirrors native MethodTable::GetMethodTableMatchingParentClass. + private static TypeHandle GetMethodTableMatchingParentClass(IRuntimeTypeSystem rts, TypeHandle start, TypeHandle parent) + { + TypeHandle current = start; + TargetPointer prev = TargetPointer.Null; + for (int i = 0; i < 1000 && !current.IsNull; i++) + { + if (HasSameTypeDefAs(rts, current, parent)) + return current; + TargetPointer next = rts.GetParentMethodTable(current); + if (next == TargetPointer.Null || next == prev || next == current.Address) + break; + prev = current.Address; + current = rts.GetTypeHandle(next); + } + return default; + } + // Shared core implementation for TypeHandleToExpandedTypeInfo and GetObjectExpandedTypeInfo. private void TypeHandleToExpandedTypeInfoImpl(IRuntimeTypeSystem rts, AreValueTypesBoxed boxed, TypeHandle typeHandle, DebuggerIPCE_ExpandedTypeData* pTypeInfo) { diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/IDacDbiInterface.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/IDacDbiInterface.cs index 05a62ec7909711..42c200c58fea60 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/IDacDbiInterface.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/IDacDbiInterface.cs @@ -553,7 +553,8 @@ public unsafe partial interface IDacDbiInterface int GetExactTypeHandle(nint pTypeData, nint pArgInfo, ulong* pVmTypeHandle); [PreserveSig] - int GetMethodDescParams(ulong vmMethodDesc, ulong genericsToken, uint* pcGenericClassTypeParams, nint pGenericTypeParams); + int EnumerateMethodDescParams(ulong vmMethodDesc, ulong genericsToken, uint* pcGenericClassTypeParams, + delegate* unmanaged fpCallback, nint pUserData); [PreserveSig] int GetThreadStaticAddress(ulong vmField, ulong vmRuntimeThread, ulong* pRetVal); @@ -565,7 +566,8 @@ public unsafe partial interface IDacDbiInterface int GetEnCHangingFieldInfo(nint pEnCFieldInfo, nint pFieldData, Interop.BOOL* pfStatic); [PreserveSig] - int GetTypeHandleParams(ulong vmTypeHandle, nint pParams); + int EnumerateTypeHandleParams(ulong vmTypeHandle, + delegate* unmanaged fpCallback, nint pUserData); [PreserveSig] int GetSimpleType(int simpleType, uint* pMetadataToken, ulong* pVmModule); From 3789f37535b236b22585772c27225d8f6db1f771 Mon Sep 17 00:00:00 2001 From: Rachel Jarvi Date: Fri, 22 May 2026 22:03:45 -0700 Subject: [PATCH 2/4] Apply suggestions from code review Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- src/coreclr/debug/daccess/dacdbiimpl.h | 5 +++-- .../Dbi/DacDbiImpl.cs | 1 + 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/coreclr/debug/daccess/dacdbiimpl.h b/src/coreclr/debug/daccess/dacdbiimpl.h index af956fbb961bb3..6797680418531f 100644 --- a/src/coreclr/debug/daccess/dacdbiimpl.h +++ b/src/coreclr/debug/daccess/dacdbiimpl.h @@ -273,8 +273,9 @@ class DacDbiInterfaceImpl : // Get information about a field added with Edit And Continue. HRESULT STDMETHODCALLTYPE GetEnCHangingFieldInfo(const EnCHangingFieldInfo * pEnCFieldInfo, OUT FieldData * pFieldData, OUT BOOL * pfStatic); - // EnumerateTypeHandleParams gets the necessary data for a type handle, i.e. its type parameters, e.g. "String" and "List" from the type handle for "Dict - + // EnumerateTypeHandleParams gets the necessary data for a type handle, i.e. its type + // parameters, e.g. "String" and "List" from the type handle for + // "Dict>". HRESULT STDMETHODCALLTYPE EnumerateTypeHandleParams(VMPTR_TypeHandle vmTypeHandle, FP_TYPEPARAM_CALLBACK fpCallback, CALLBACK_DATA pUserData); // DacDbi API: GetSimpleType diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/DacDbiImpl.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/DacDbiImpl.cs index c41f0bc30e2acf..6d6ff8261cd2c7 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/DacDbiImpl.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/DacDbiImpl.cs @@ -1886,6 +1886,7 @@ public int EnumerateMethodDescParams(ulong vmMethodDesc, ulong genericsToken, ui throw new ArgumentException("MethodDesc cannot be null", nameof(vmMethodDesc)); if (pcGenericClassTypeParams == null) throw new ArgumentNullException(nameof(pcGenericClassTypeParams)); + *pcGenericClassTypeParams = 0; if (fpCallback == null) throw new ArgumentNullException(nameof(fpCallback)); From e683ef3edc19a265b40039a529d9c66aead879c8 Mon Sep 17 00:00:00 2001 From: Rachel Jarvi Date: Fri, 22 May 2026 22:04:31 -0700 Subject: [PATCH 3/4] Update dacdbistructures.h --- src/coreclr/debug/inc/dacdbistructures.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/coreclr/debug/inc/dacdbistructures.h b/src/coreclr/debug/inc/dacdbistructures.h index a7c69a4cfeba02..07637e944b0d28 100644 --- a/src/coreclr/debug/inc/dacdbistructures.h +++ b/src/coreclr/debug/inc/dacdbistructures.h @@ -802,7 +802,7 @@ typedef DacDbiArrayList TypeParamsList; // A struct for passing version information from DBI to DAC. // See code:CordbProcess::CordbProcess#DBIVersionChecking for more information. -const DWORD kCurrentDacDbiProtocolBreakingChangeCounter = 2; +const DWORD kCurrentDacDbiProtocolBreakingChangeCounter = 1; struct DbiVersion { From a1b7c4bc50295bb7365018431cf19524089700fb Mon Sep 17 00:00:00 2001 From: Rachel Jarvi Date: Mon, 25 May 2026 06:56:38 -0700 Subject: [PATCH 4/4] Remove unnecessary line in IRuntimeTypeSystem.cs --- .../Contracts/IRuntimeTypeSystem.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IRuntimeTypeSystem.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IRuntimeTypeSystem.cs index e70d3cfc8e2d6d..fdcad78ddd3161 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IRuntimeTypeSystem.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IRuntimeTypeSystem.cs @@ -165,7 +165,6 @@ public interface IRuntimeTypeSystem : IContract ReadOnlySpan GetInstantiation(TypeHandle typeHandle) => throw new NotImplementedException(); - public bool IsClassInited(TypeHandle typeHandle) => throw new NotImplementedException(); public bool IsInitError(TypeHandle typeHandle) => throw new NotImplementedException(); bool IsGenericTypeDefinition(TypeHandle typeHandle) => throw new NotImplementedException();