[cdac] Add IManagedTypeSource contract for FQN based type access#127310
[cdac] Add IManagedTypeSource contract for FQN based type access#127310max-charlamb wants to merge 6 commits into
Conversation
|
Tagging subscribers to this area: @steveisok, @tommcdon, @dotnet/dotnet-diag |
| | `Loader` | | ||
| | `ConditionalWeakTable` | | ||
|
|
||
| Managed types used: |
There was a problem hiding this comment.
I do not think it is useful to make a distinction between managed types and unmanaged types. We have equivalent C# and C++ definitions for number of types, and the balance has been shifting towards doing more on the C# side.
For example, take MethodTable. MethodTable has equivalent C++ and C# implementations. In regular CoreCLR, C++ implementation of a MethodTable is a superset of the C# one for now. In NativeAOT, C# implementation is a superset of the C++ one. It is hard to tell whether MethodTable is a managed type. We may want to assume that there is going to be a C# implementation of everything used by cdac in the fullness of time.
I think we should have an abstraction for accessing layout information. We have 3 potential sources of layout information: precomputed contract json, C/C++ symbols, and ECMA metadata. The implementation of the abstraction should pick the most suitable source, e.g. try contract json first, if the layout information is not available in the contract json, try the other sources.
There was a problem hiding this comment.
We may want to assume that there is going to be a C# implementation of everything used by cdac in the fullness of time.
We'll need to make sure we are handling bootstrapping. If the algorithms to fetch managed type field offsets are documented as contracts then either those contracts can't rely on any external layout info themselves or we need to ensure all the layouts they do use have definitions outside of ECMA metadata.
There was a problem hiding this comment.
Right, I think about the cdac json field layouts as a cache that breaks undesirable dependencies. The bootstrapping problem is a manifestation of such undesirable dependency. Another example are minidumps; we do not necessarily want minidumps to include everything needed to fetch managed layouts for types required to analyze the minidumps.
| # Contract ManagedTypeLayout | ||
|
|
||
| This contract centralizes the lookup of managed-type (CoreLib) layout information — | ||
| instance field offsets and static field addresses — that was previously duplicated |
There was a problem hiding this comment.
that was previously duplicated across individual consumers
This should be included in PR description that explains what changed.
It should not be included in the docs. The docs should be about the final design.
260ad69 to
f78d3a7
Compare
Moving the remaining types to a uniform plan in a follow up is fine. I would like to see an example of a delta between layout of given managed type being cached in the cdac json vs. not. Would you mind sharing what it would take to add |
c7883dc to
62223d6
Compare
|
62223d6 to
09ca47f
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 19 out of 19 changed files in this pull request and generated 3 comments.
Comments suppressed due to low confidence (1)
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IRuntimeTypeSystem.cs:186
IRuntimeTypeSystemis a public interface; removing members likeGetTypeByNameAndModule/GetCoreLibFieldDescAndDefis a binary/source breaking change for any external consumers compiled against the previous version. If this assembly is intended to have a stable public surface, consider keeping the methods (e.g., obsolete them) or ensure the breaking-change/API-review process is followed for their removal.
// For arrays, reference types, and TypeDescs, behaves identically to GetSignatureCorElementType.
CorElementType GetInternalCorElementType(TypeHandle typeHandle) => throw new NotImplementedException();
// return true if the TypeHandle represents an enum type.
bool IsEnum(TypeHandle typeHandle) => throw new NotImplementedException();
// return true if the TypeHandle represents an array, and set the rank to either 0 (if the type is not an array), or the rank number if it is.
bool IsArray(TypeHandle typeHandle, out uint rank) => throw new NotImplementedException();
TypeHandle GetTypeParam(TypeHandle typeHandle) => throw new NotImplementedException();
TypeHandle GetConstructedType(TypeHandle typeHandle, CorElementType corElementType, int rank, ImmutableArray<TypeHandle> typeArguments) => throw new NotImplementedException();
TypeHandle GetPrimitiveType(CorElementType typeCode) => throw new NotImplementedException();
bool IsGenericVariable(TypeHandle typeHandle, out TargetPointer module, out uint token) => throw new NotImplementedException();
bool IsFunctionPointer(TypeHandle typeHandle, out ReadOnlySpan<TypeHandle> retAndArgTypes, out byte callConv) => throw new NotImplementedException();
bool IsPointer(TypeHandle typeHandle) => throw new NotImplementedException();
// Returns null if the TypeHandle is not a class/struct/generic variable
#endregion TypeHandle inspection APIs
… null handling - Lock.cs: read _owningThreadId as int (matches CoreLib declaration) so TargetFieldExtensions.AssertPrimitiveType debug validation passes; cast to uint at the SyncBlock_1 API boundary where the public TryGetLockInfo signature returns uint. - ComWrappers.cs: use TryGetStaticFieldAddress and return TargetPointer.Null when the ComWrappers statics base isn't allocated, so callers (GetMOWs / GetComWrappersRCWForObject) report 'no data' via the existing null-check path instead of throwing. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 24 out of 24 changed files in this pull request and generated 2 comments.
Comments suppressed due to low confidence (1)
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IRuntimeTypeSystem.cs:185
- This PR removes members from the public IRuntimeTypeSystem contract (e.g. GetTypeByNameAndModule / GetCoreLibFieldDescAndDef). Removing public interface members is a breaking change for any out-of-tree consumers/implementers. If this surface is intended to remain public, consider keeping these as compatibility shims (possibly [Obsolete]) that delegate to ManagedTypeSource, or otherwise confirm that this assembly/contracts are not part of a supported public API and that breaking removals are acceptable.
// return true if the TypeHandle represents an enum type.
bool IsEnum(TypeHandle typeHandle) => throw new NotImplementedException();
// return true if the TypeHandle represents an array, and set the rank to either 0 (if the type is not an array), or the rank number if it is.
bool IsArray(TypeHandle typeHandle, out uint rank) => throw new NotImplementedException();
TypeHandle GetTypeParam(TypeHandle typeHandle) => throw new NotImplementedException();
TypeHandle GetConstructedType(TypeHandle typeHandle, CorElementType corElementType, int rank, ImmutableArray<TypeHandle> typeArguments) => throw new NotImplementedException();
TypeHandle GetPrimitiveType(CorElementType typeCode) => throw new NotImplementedException();
bool IsGenericVariable(TypeHandle typeHandle, out TargetPointer module, out uint token) => throw new NotImplementedException();
bool IsFunctionPointer(TypeHandle typeHandle, out ReadOnlySpan<TypeHandle> retAndArgTypes, out byte callConv) => throw new NotImplementedException();
bool IsPointer(TypeHandle typeHandle) => throw new NotImplementedException();
// Returns null if the TypeHandle is not a class/struct/generic variable
#endregion TypeHandle inspection APIs
The IManagedTypeSource contract was registered in CoreCLRContracts.cs but missing from the CDAC_GLOBAL_CONTRACT list in datadescriptor.inc, so it was not advertised by the runtime descriptor. Add the entry so the contract is discoverable. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Adds ManagedTypeSource.md documenting the new contract for resolving managed-type layout by fully-qualified name. Updates ConditionalWeakTable.md, SyncBlock.md, and ComWrappers.md to use ManagedTypeSource for field-offset resolution instead of RuntimeTypeSystem.GetCoreLibFieldDescAndDef + GetFieldDescOffset (or ad-hoc helpers), and standardizes the contract-doc section headings and the 'Managed types used' table format. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
12f47f5 to
f22b14e
Compare
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 24 out of 24 changed files in this pull request and generated 3 comments.
Comments suppressed due to low confidence (1)
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IRuntimeTypeSystem.cs:186
- This PR removes public members from IRuntimeTypeSystem (e.g., name-based type/field lookup). Since these contract interfaces are public, this is a breaking change for any external consumers and should go through the same API approval/versioning process as other public API changes (or the interface should be made internal if it’s not intended for external use).
// return true if the TypeHandle represents an enum type.
bool IsEnum(TypeHandle typeHandle) => throw new NotImplementedException();
// return true if the TypeHandle represents an array, and set the rank to either 0 (if the type is not an array), or the rank number if it is.
bool IsArray(TypeHandle typeHandle, out uint rank) => throw new NotImplementedException();
TypeHandle GetTypeParam(TypeHandle typeHandle) => throw new NotImplementedException();
TypeHandle GetConstructedType(TypeHandle typeHandle, CorElementType corElementType, int rank, ImmutableArray<TypeHandle> typeArguments) => throw new NotImplementedException();
TypeHandle GetPrimitiveType(CorElementType typeCode) => throw new NotImplementedException();
bool IsGenericVariable(TypeHandle typeHandle, out TargetPointer module, out uint token) => throw new NotImplementedException();
bool IsFunctionPointer(TypeHandle typeHandle, out ReadOnlySpan<TypeHandle> retAndArgTypes, out byte callConv) => throw new NotImplementedException();
bool IsPointer(TypeHandle typeHandle) => throw new NotImplementedException();
// Returns null if the TypeHandle is not a class/struct/generic variable
#endregion TypeHandle inspection APIs
| /// <summary> | ||
| /// Resolves layout information for managed CLR types by fully-qualified name. | ||
| /// </summary> | ||
| public interface IManagedTypeSource : IContract | ||
| { | ||
| static string IContract.Name { get; } = nameof(ManagedTypeSource); |
| CorElementType.U4 => "uint32", | ||
| CorElementType.I8 => "int64", | ||
| CorElementType.U8 => "uint64", | ||
| CorElementType.I => "nint", | ||
| CorElementType.U => "nuint", | ||
| CorElementType.String |
| TargetPointer lockObject = target.ReadPointer(syncBlock + /* SyncBlock::Lock offset */); | ||
|
|
||
| if (lockObject != TargetPointer.Null) | ||
| { | ||
| // Resolve System.Threading.Lock in System.Private.CoreLib by name using RuntimeTypeSystem contract, LockName and LockNamespace. | ||
| uint state = ReadUintField(/* Lock type */, "LockStateName", /* RuntimeTypeSystem contract */, /* MetadataReader for SPC */, lockObject); | ||
| // Resolve the layout of System.Threading.Lock via ManagedTypeSource. | ||
| Target.TypeInfo lockType = target.Contracts.ManagedTypeSource.GetTypeInfo("System.Threading.Lock"); | ||
| uint state = target.Read<uint>(lockObject + (uint)lockType.Fields["_state"].Offset); | ||
| bool monitorHeld = (state & 1) != 0; | ||
| if (monitorHeld) | ||
| { | ||
| owningThreadId = ReadUintField(/* Lock type */, "LockOwningThreadIdName", /* contracts */, lockObject); | ||
| recursion = ReadUintField(/* Lock type */, "LockRecursionCountName", /* contracts */, lockObject); | ||
| owningThreadId = target.Read<uint>(lockObject + (uint)lockType.Fields["_owningThreadId"].Offset); | ||
| recursion = target.Read<uint>(lockObject + (uint)lockType.Fields["_recursionCount"].Offset); | ||
| } |
Note
This PR description was drafted with assistance from GitHub Copilot.
Summary
Adds a new cDAC contract,
IManagedTypeSource, that resolves managed type information (instance field offsets,TypeHandles, static and thread-static field addresses) from standard debug data by name instead of from hardcoded native data descriptor entries.Following the existing
ConditionalWeakTableprecedent, all managed type access in the cDAC now flows through this single contract. Callers identify a type once by its fully-qualified name; the contract resolves the metadata, locates the runtimeTypeHandle, and exposes:GetTypeInfo(fqn)— instance-field layout (offsets pre-shifted bysizeof(Object)for the standardaddress + field.Offsetreference-type idiom)GetTypeHandle(fqn)— the runtimeTypeHandle(the struct, not a raw pointer)GetStaticFieldAddress(fqn, fieldName)— address of a non-thread static field, gated on its statics base being allocatedGetThreadStaticFieldAddress(fqn, fieldName, thread)— address of a thread-static field for a specific thread, gated on the per-thread base being allocatedEach API has a
Try…overload returningfalsewhen the type/field is unavailable.Contract:
ManagedTypeSource, versionc1.FQN centralization
All fully-qualified type-name strings now live in per-type
Data.Managed.*classes (Lock,List,ConditionalWeakTable,ConditionalWeakTableContainer,ConditionalWeakTableEntry,ComWrappers,NativeObjectWrapper). These classes implementIData<TSelf>and expose typed accessors (e.g.,Data.Managed.ComWrappers.AllManagedObjectWrapperTable(target)) so consumers in theContracts/layer never reference FQN strings or field names directly.Migrated consumers
ConditionalWeakTable_1(the original metadata-based reader; now goes through the centralized contract)SyncBlock_1(System.Threading.Lock)ComWrappers_1— both thes_allManagedObjectWrapperTable/s_nativeObjectWrapperTablestatics and theNativeObjectWrapperTypeHandlecomparisonObject_1/Data.String,Data.Exception,Data.NativeObjectWrapperObject,Data.ManagedObjectWrapperHolderObjectAsyncContinuationDumpTestsuses the new thread-static API forAsyncDispatcherInfo.t_currentRTS API removal
Now that
IManagedTypeSourceowns name-based lookup, the followingIRuntimeTypeSystemAPIs (and their internal helpers / caches) have been removed:GetTypeByNameAndModuleGetCoreLibFieldDescAndDefIRuntimeTypeSystemno longer accepts type or field names; it operates purely onTypeHandle/FieldDescpointers.Notes
Data.String.StringLengthis nowintto match the managed field type.SyncBlock_1readsLock._owningThreadIdasint(matches the managed field) and casts touintfor the existing API surface.ManagedTypeSource_1matches type names by reconstructingNamespace + "." + NameperTypeDef, so a dot within a namespace or type-name segment does not affect resolution. Nested types use+separators (Outer+Inner).datadescriptor.incand adds theManagedTypeSourcecontract registration.Documentation
docs/design/datacontracts/ManagedTypeSource.mdand the affected contract docs are still in progress and will be updated in a follow-up commit before merge.Validation