[cDAC] implement GetExactTypeHandle API#128616
Draft
Copilot wants to merge 1 commit into
Draft
Conversation
Co-authored-by: rcj1 <77995559+rcj1@users.noreply.github.com>
This was referenced May 27, 2026
Open
Contributor
|
Tagging subscribers to this area: @steveisok, @tommcdon, @dotnet/dotnet-diag |
Contributor
There was a problem hiding this comment.
Pull request overview
This PR extends the cDAC / DataContractReader type-system and legacy DBI interop to support exact type-handle resolution (including function pointer types) and to read loader-allocator collectibility metadata directly from the runtime data descriptor. It also enables DAC-side function-pointer type comparisons by making CoreCLR’s EETypeHashTable::CompareFnPtrType DAC-compatible.
Changes:
- Implemented
IDacDbiInterface.GetExactTypeHandlein the managed Legacy DBI path, including constructed-type resolution (array/ptr/byref/generic instantiation/fnptr). - Extended
IRuntimeTypeSystem.GetConstructedTypewith acallConvdiscriminator and added function-pointer loader-module selection logic (mirroring CoreCLR’s native algorithm). - Exposed and consumed
LoaderAllocator.IsCollectibleandLoaderAllocator.CreationNumbervia cDAC data descriptors; adjusted CoreCLR DAC paths to read FnPtr ret/arg arrays via DAC-safe field access.
Show a summary per file
| File | Description |
|---|---|
| src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/IDacDbiInterface.cs | Adds ArgInfoList and updates GetExactTypeHandle signature to typed pointers. |
| src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/DacDbiImpl.cs | Implements GetExactTypeHandle and helpers for exact constructed-type resolution in managed cDAC. |
| src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/LoaderAllocator.cs | Reads IsCollectible and CreationNumber unconditionally from the data descriptor. |
| src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeTypeSystem_1.cs | Adds callConv to constructed-type caching and implements FnPtr matching + loader-module selection. |
| src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IRuntimeTypeSystem.cs | Extends GetConstructedType signature with optional callConv. |
| src/coreclr/vm/typehash.cpp | Removes DACCESS_COMPILE guard to enable FnPtr comparisons under DAC builds. |
| src/coreclr/vm/typedesc.h | Makes FnPtr ret/arg pointer retrieval use DAC-safe member-address access. |
| src/coreclr/vm/loaderallocator.hpp | Exposes m_IsCollectible and m_nLoaderAllocator offsets in cdac_data<LoaderAllocator>. |
| src/coreclr/vm/datadescriptor/datadescriptor.inc | Adds LoaderAllocator.IsCollectible and LoaderAllocator.CreationNumber fields to the descriptor. |
| docs/design/datacontracts/RuntimeTypeSystem.md | Documents the updated constructed-type API and the new loader-allocator fields/algorithm. |
Copilot's findings
Comments suppressed due to low confidence (2)
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeTypeSystem_1.cs:1111
- GetConstructedType looks up the cache using a TypeKey that includes callConv, but some successful paths add to the cache using a TypeKey ctor overload that omits callConv (defaulting to 0). If a non-zero callConv is ever passed for non-FnPtr element types (even accidentally), caching becomes inconsistent. Consider normalizing callConv to 0 unless corElementType == FnPtr, and always using the same TypeKey construction (including callConv) for both lookup and insertion.
TypeHandle IRuntimeTypeSystem.GetConstructedType(TypeHandle typeHandle, CorElementType corElementType, int rank, ImmutableArray<TypeHandle> typeArguments, byte callConv)
{
if (typeHandle.Address == TargetPointer.Null && corElementType != CorElementType.FnPtr)
return new TypeHandle(TargetPointer.Null);
if (_typeHandles.TryGetValue(new TypeKey(typeHandle, corElementType, rank, typeArguments, callConv), out TypeHandle existing))
return existing;
ILoader loaderContract = _target.Contracts.Loader;
TargetPointer loaderModule;
if (corElementType == CorElementType.FnPtr)
loaderModule = FindFnPtrLoaderModule(typeArguments);
else
loaderModule = GetLoaderModule(typeHandle);
ModuleHandle moduleHandle = loaderContract.GetModuleHandleFromModulePtr(loaderModule);
TypeHandle potentialMatch;
foreach (TargetPointer ptr in loaderContract.GetAvailableTypeParams(moduleHandle))
{
potentialMatch = GetTypeHandle(ptr);
if (corElementType == CorElementType.GenericInst)
{
if (GenericInstantiationMatch(typeHandle, potentialMatch, typeArguments) && IsLoaded(potentialMatch))
{
_ = _typeHandles.TryAdd(new TypeKey(typeHandle, corElementType, rank, typeArguments), potentialMatch);
return potentialMatch;
}
}
else if (corElementType == CorElementType.FnPtr)
{
if (FnPtrMatch(potentialMatch, typeArguments, callConv) && IsLoaded(potentialMatch))
{
_ = _typeHandles.TryAdd(new TypeKey(typeHandle, corElementType, rank, typeArguments, callConv), potentialMatch);
return potentialMatch;
}
}
else if (ArrayPtrMatch(typeHandle, corElementType, rank, potentialMatch) && IsLoaded(potentialMatch))
{
_ = _typeHandles.TryAdd(new TypeKey(typeHandle, corElementType, rank, typeArguments), potentialMatch);
return potentialMatch;
src/coreclr/vm/typehash.cpp:446
- CompareFnPtrType now compiles under DACCESS_COMPILE, but it assigns GetRetAndArgTypesPointer() (PTR_TypeHandle/DPTR) into a raw TypeHandle* and then indexes it. Under DAC this is unsafe because DPTR->T* marshals a single element; indexing will read past the marshaled buffer and can produce incorrect comparisons or memory errors. Keep it as PTR_TypeHandle/ArrayDPTR and use DAC-safe indexing, or explicitly marshal (numArgs+1) elements into a local buffer before comparing.
if (!t.IsFnPtrType())
return FALSE;
FnPtrTypeDesc* pTD = t.AsFnPtrType();
if (pTD->GetNumArgs() != numArgs || pTD->GetCallConv() != callConv)
return FALSE;
// Now check the return and argument types. Some type arguments might be encoded.
TypeHandle *retAndArgTypes2 = pTD->GetRetAndArgTypesPointer();
for (DWORD i = 0; i <= numArgs; i++)
{
if (retAndArgTypes2[i] != retAndArgTypes[i])
{
- Files reviewed: 10/10 changed files
- Comments generated: 7
Comment on lines
+1873
to
+1884
| public int GetExactTypeHandle(DebuggerIPCE_ExpandedTypeData* pTypeData, ArgInfoList* pArgInfo, ulong* pVmTypeHandle) | ||
| { | ||
| if (pVmTypeHandle == null) | ||
| return HResults.E_POINTER; | ||
| *pVmTypeHandle = 0; | ||
| int hr = HResults.S_OK; | ||
| try | ||
| { | ||
| IRuntimeTypeSystem rts = _target.Contracts.RuntimeTypeSystem; | ||
| TypeHandle th = default; | ||
| CorElementType et = (CorElementType)ReadLittleEndian(pTypeData->elementType); | ||
| switch (et) |
Comment on lines
+1873
to
+1903
| public int GetExactTypeHandle(DebuggerIPCE_ExpandedTypeData* pTypeData, ArgInfoList* pArgInfo, ulong* pVmTypeHandle) | ||
| { | ||
| if (pVmTypeHandle == null) | ||
| return HResults.E_POINTER; | ||
| *pVmTypeHandle = 0; | ||
| int hr = HResults.S_OK; | ||
| try | ||
| { | ||
| IRuntimeTypeSystem rts = _target.Contracts.RuntimeTypeSystem; | ||
| TypeHandle th = default; | ||
| CorElementType et = (CorElementType)ReadLittleEndian(pTypeData->elementType); | ||
| switch (et) | ||
| { | ||
| case CorElementType.Array: | ||
| case CorElementType.SzArray: | ||
| th = GetExactArrayTypeHandle(rts, pTypeData, pArgInfo); | ||
| break; | ||
| case CorElementType.Ptr: | ||
| case CorElementType.Byref: | ||
| th = GetExactPtrOrByRefTypeHandle(rts, pTypeData, pArgInfo); | ||
| break; | ||
| case CorElementType.Class: | ||
| case CorElementType.ValueType: | ||
| th = GetExactClassTypeHandle(rts, pTypeData, pArgInfo); | ||
| break; | ||
| case CorElementType.FnPtr: | ||
| th = GetExactFnPtrTypeHandle(rts, pArgInfo); | ||
| break; | ||
| default: | ||
| th = rts.GetPrimitiveType(et); | ||
| break; |
Comment on lines
90
to
107
| @@ -101,7 +103,7 @@ public bool Equals(TypeKey other) | |||
|
|
|||
| public override int GetHashCode() | |||
| { | |||
| int hash = HashCode.Combine(TypeHandle.GetHashCode(), (int)ElementType, Rank); | |||
| int hash = HashCode.Combine(TypeHandle.GetHashCode(), (int)ElementType, Rank, (int)CallConv); | |||
| foreach (TypeHandle th in TypeArgs) | |||
Comment on lines
+1117
to
+1163
| // Mirrors the native algorithm | ||
| // ClassLoader::ComputeLoaderModuleForFunctionPointer: the loader module is the collectible | ||
| // ret/arg type's loader module with the largest LoaderAllocator creation number, or CoreLib's | ||
| // loader module if none of the ret/arg types are collectible. | ||
| private TargetPointer FindFnPtrLoaderModule(ImmutableArray<TypeHandle> retAndArgTypes) | ||
| { | ||
| ILoader loaderContract = _target.Contracts.Loader; | ||
|
|
||
| TargetPointer loaderModulePtr = TargetPointer.Null; | ||
| ulong latestFoundNumber = 0; | ||
| bool anyCollectible = false; | ||
|
|
||
| foreach (TypeHandle arg in retAndArgTypes) | ||
| { | ||
| if (arg.Address == TargetPointer.Null) | ||
| continue; | ||
|
|
||
| TargetPointer argModulePtr = GetLoaderModule(arg); | ||
| if (argModulePtr == TargetPointer.Null) | ||
| continue; | ||
|
|
||
| ModuleHandle argModuleHandle = loaderContract.GetModuleHandleFromModulePtr(argModulePtr); | ||
| TargetPointer argLoaderAllocator = loaderContract.GetLoaderAllocator(argModuleHandle); | ||
| if (argLoaderAllocator == TargetPointer.Null) | ||
| continue; | ||
|
|
||
| Data.LoaderAllocator loaderAllocator = _target.ProcessedData.GetOrAdd<Data.LoaderAllocator>(argLoaderAllocator); | ||
| if (!loaderAllocator.IsCollectible) | ||
| continue; | ||
|
|
||
| if (!anyCollectible || loaderAllocator.CreationNumber > latestFoundNumber) | ||
| { | ||
| anyCollectible = true; | ||
| latestFoundNumber = loaderAllocator.CreationNumber; | ||
| loaderModulePtr = argModulePtr; | ||
| } | ||
| } | ||
|
|
||
| if (!anyCollectible) | ||
| { | ||
| TargetPointer systemAssembly = loaderContract.GetSystemAssembly(); | ||
| ModuleHandle coreLibModuleHandle = loaderContract.GetModuleHandleFromAssemblyPtr(systemAssembly); | ||
| loaderModulePtr = loaderContract.GetModule(coreLibModuleHandle); | ||
| } | ||
|
|
||
| return loaderModulePtr; | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Implements the cDAC
GetExactTypeHandleAPI, including the function-pointer branch. Extends GetConstructedType for function pointers, albeit only for the managed calling convention (equivalent with native).