diff --git a/src/coreclr/debug/daccess/dacdbiimpl.cpp b/src/coreclr/debug/daccess/dacdbiimpl.cpp index 55bba0be3f4f99..adf00d365c48f9 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 47fde49bb388dc..6797680418531f 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,10 @@ 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 - - HRESULT STDMETHODCALLTYPE GetTypeHandleParams(VMPTR_TypeHandle vmTypeHandle, OUT TypeParamsList * pParams); + // 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 // 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 b0d8d6be56e4db..fc8dbb8e0706e6 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 00575263a38666..18acfcedda881c 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/vm/datadescriptor/datadescriptor.inc b/src/coreclr/vm/datadescriptor/datadescriptor.inc index d8b0ae58896a83..8d18de704bd34b 100644 --- a/src/coreclr/vm/datadescriptor/datadescriptor.inc +++ b/src/coreclr/vm/datadescriptor/datadescriptor.inc @@ -1540,6 +1540,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(ContinuationSingletonEEClass, &::g_singletonContinuationEEClass) CDAC_GLOBAL_POINTER(ExceptionMethodTable, &::g_pExceptionClass) 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 fdcad78ddd3161..e70d3cfc8e2d6d 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,6 +165,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 8b6ad67ef388fb..22f5e22764949c 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 ContinuationSingletonEEClass = nameof(ContinuationSingletonEEClass); public const string ExceptionMethodTable = nameof(ExceptionMethodTable); public const string FreeObjectMethodTable = nameof(FreeObjectMethodTable); 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 61d77ad15202ed..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 @@ -1872,8 +1872,157 @@ 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)); + *pcGenericClassTypeParams = 0; + 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) { @@ -1938,8 +2087,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) { @@ -3186,6 +3414,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 c9b59b7d067e80..09fc4cd7ec30d4 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);