Skip to content

[cdac] Add IManagedTypeSource contract for FQN based type access#127310

Open
max-charlamb wants to merge 6 commits into
dotnet:mainfrom
max-charlamb:dev/max-charlamb/cdac-managed-type-layout-contract
Open

[cdac] Add IManagedTypeSource contract for FQN based type access#127310
max-charlamb wants to merge 6 commits into
dotnet:mainfrom
max-charlamb:dev/max-charlamb/cdac-managed-type-layout-contract

Conversation

@max-charlamb
Copy link
Copy Markdown
Member

@max-charlamb max-charlamb commented Apr 23, 2026

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 ConditionalWeakTable precedent, 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 runtime TypeHandle, and exposes:

  • GetTypeInfo(fqn) — instance-field layout (offsets pre-shifted by sizeof(Object) for the standard address + field.Offset reference-type idiom)
  • GetTypeHandle(fqn) — the runtime TypeHandle (the struct, not a raw pointer)
  • GetStaticFieldAddress(fqn, fieldName) — address of a non-thread static field, gated on its statics base being allocated
  • GetThreadStaticFieldAddress(fqn, fieldName, thread) — address of a thread-static field for a specific thread, gated on the per-thread base being allocated

Each API has a Try… overload returning false when the type/field is unavailable.

Contract: ManagedTypeSource, version c1.

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 implement IData<TSelf> and expose typed accessors (e.g., Data.Managed.ComWrappers.AllManagedObjectWrapperTable(target)) so consumers in the Contracts/ 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 the s_allManagedObjectWrapperTable / s_nativeObjectWrapperTable statics and the NativeObjectWrapper TypeHandle comparison
  • Object_1 / Data.String, Data.Exception, Data.NativeObjectWrapperObject, Data.ManagedObjectWrapperHolderObject
  • AsyncContinuationDumpTests uses the new thread-static API for AsyncDispatcherInfo.t_current

RTS API removal

Now that IManagedTypeSource owns name-based lookup, the following IRuntimeTypeSystem APIs (and their internal helpers / caches) have been removed:

  • GetTypeByNameAndModule
  • GetCoreLibFieldDescAndDef

IRuntimeTypeSystem no longer accepts type or field names; it operates purely on TypeHandle / FieldDesc pointers.

Notes

  • Data.String.StringLength is now int to match the managed field type.
  • SyncBlock_1 reads Lock._owningThreadId as int (matches the managed field) and casts to uint for the existing API surface.
  • The metadata walk in ManagedTypeSource_1 matches type names by reconstructing Namespace + "." + Name per TypeDef, so a dot within a namespace or type-name segment does not affect resolution. Nested types use + separators (Outer+Inner).
  • Removes the corresponding managed-type entries from datadescriptor.inc and adds the ManagedTypeSource contract registration.

Documentation

docs/design/datacontracts/ManagedTypeSource.md and the affected contract docs are still in progress and will be updated in a follow-up commit before merge.

Validation

  • cDAC unit tests: 1977/1977 passing

This comment was marked as outdated.

@dotnet-policy-service
Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @steveisok, @tommcdon, @dotnet/dotnet-diag
See info in area-owners.md if you want to be subscribed.

Copilot AI review requested due to automatic review settings April 24, 2026 03:05
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 29 out of 29 changed files in this pull request and generated 2 comments.

Copilot AI review requested due to automatic review settings April 24, 2026 03:25
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 29 out of 29 changed files in this pull request and generated 2 comments.

Copilot AI review requested due to automatic review settings April 24, 2026 03:46
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 31 out of 31 changed files in this pull request and generated 1 comment.

| `Loader` |
| `ConditionalWeakTable` |

Managed types used:
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot AI review requested due to automatic review settings April 30, 2026 19:47
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 30 out of 30 changed files in this pull request and generated 5 comments.

Comment thread src/native/managed/cdac/tests/TestPlaceholderTarget.cs Outdated
Comment thread docs/design/datacontracts/MetadataLayoutSource.md Outdated
Comment thread src/coreclr/vm/datadescriptor/datadescriptor.inc
Copilot AI review requested due to automatic review settings May 5, 2026 22:00
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 40 out of 40 changed files in this pull request and generated 5 comments.

Comment thread docs/design/datacontracts/ComWrappers.md Outdated
Comment thread docs/design/datacontracts/ConditionalWeakTable.md
Comment thread src/coreclr/vm/datadescriptor/datadescriptor.inc
Comment thread docs/design/datacontracts/SyncBlock.md Outdated
@jkotas
Copy link
Copy Markdown
Member

jkotas commented May 6, 2026

I'd prefer to leave moving objects from one source to another be done in follow-ups.

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 Lock type layout to cdac json as an example - if we decided for whatever reasons that we do not want to look it up in runtime metadata? (just share a link to a commit in a throw-away branch, without pushing it to a PR)

@max-charlamb max-charlamb force-pushed the dev/max-charlamb/cdac-managed-type-layout-contract branch from c7883dc to 62223d6 Compare May 12, 2026 20:48
@max-charlamb max-charlamb changed the title [cdac] Add IManagedTypeLayout contract for metadata-driven managed type layouts [cdac] Add IManagedTypeSource contract for metadata-driven managed type access May 12, 2026
@max-charlamb max-charlamb changed the title [cdac] Add IManagedTypeSource contract for metadata-driven managed type access [cdac] Add IManagedTypeSource contract for FQN based type access May 12, 2026
@max-charlamb
Copy link
Copy Markdown
Member Author

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 Lock type layout to cdac json as an example - if we decided for whatever reasons that we do not want to look it up in runtime metadata? (just share a link to a commit in a throw-away branch, without pushing it to a PR)

max-charlamb/runtime@62223d6...b3dfdc8

Copilot AI review requested due to automatic review settings May 12, 2026 22:53
@max-charlamb max-charlamb force-pushed the dev/max-charlamb/cdac-managed-type-layout-contract branch from 62223d6 to 09ca47f Compare May 12, 2026 22:53
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

  • IRuntimeTypeSystem is a public interface; removing members like GetTypeByNameAndModule / GetCoreLibFieldDescAndDef is 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>
Copilot AI review requested due to automatic review settings May 13, 2026 05:03
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Copilot AI review requested due to automatic review settings May 13, 2026 05:43
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 24 out of 24 changed files in this pull request and generated 4 comments.

Comment thread docs/design/datacontracts/SyncBlock.md
Max Charlamb and others added 2 commits May 13, 2026 02:02
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>
@max-charlamb max-charlamb force-pushed the dev/max-charlamb/cdac-managed-type-layout-contract branch from 12f47f5 to f22b14e Compare May 13, 2026 06:10
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings May 13, 2026 06:10
@max-charlamb max-charlamb marked this pull request as ready for review May 13, 2026 06:12
@max-charlamb max-charlamb requested a review from noahfalk May 13, 2026 06:12
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Comment on lines +9 to +14
/// <summary>
/// Resolves layout information for managed CLR types by fully-qualified name.
/// </summary>
public interface IManagedTypeSource : IContract
{
static string IContract.Name { get; } = nameof(ManagedTypeSource);
Comment on lines +309 to +314
CorElementType.U4 => "uint32",
CorElementType.I8 => "int64",
CorElementType.U8 => "uint64",
CorElementType.I => "nint",
CorElementType.U => "nuint",
CorElementType.String
Comment on lines 94 to 106
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);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants