From 7e8df0bd9251b0a004f01629cfec3f8b20b95ff0 Mon Sep 17 00:00:00 2001 From: Max Charlamb Date: Tue, 12 May 2026 16:47:17 -0400 Subject: [PATCH 1/7] base implementation --- .../ContractRegistry.cs | 4 + .../Contracts/IManagedTypeSource.cs | 32 ++ .../Contracts/IRuntimeTypeSystem.cs | 4 - .../Contracts/ManagedTypeSource_1.cs | 326 ++++++++++++++++++ .../Contracts/RuntimeTypeSystem_1.cs | 147 -------- .../CoreCLRContracts.cs | 1 + 6 files changed, 363 insertions(+), 151 deletions(-) create mode 100644 src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IManagedTypeSource.cs create mode 100644 src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ManagedTypeSource_1.cs diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/ContractRegistry.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/ContractRegistry.cs index 17cfcd1000ee19..a539964b0fe7d6 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/ContractRegistry.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/ContractRegistry.cs @@ -117,6 +117,10 @@ public abstract class ContractRegistry /// public virtual IConditionalWeakTable ConditionalWeakTable => GetContract(); /// + /// Gets an instance of the ManagedTypeSource contract for the target. + /// + public virtual IManagedTypeSource ManagedTypeSource => GetContract(); + /// /// Gets an instance of the AuxiliarySymbols contract for the target. /// public virtual IAuxiliarySymbols AuxiliarySymbols => GetContract(); diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IManagedTypeSource.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IManagedTypeSource.cs new file mode 100644 index 00000000000000..97154a6cbe0669 --- /dev/null +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IManagedTypeSource.cs @@ -0,0 +1,32 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using Microsoft.Diagnostics.DataContractReader.Data; + +namespace Microsoft.Diagnostics.DataContractReader.Contracts; + +/// +/// Resolves layout information for managed CLR types by fully-qualified name. +/// +public interface IManagedTypeSource : IContract +{ + static string IContract.Name { get; } = nameof(ManagedTypeSource); + + bool TryGetTypeInfo(string fullyQualifiedName, out Target.TypeInfo info) => throw new NotImplementedException(); + Target.TypeInfo GetTypeInfo(string fullyQualifiedName) => throw new NotImplementedException(); + + bool TryGetTypeHandle(string fullyQualifiedName, out TypeHandle typeHandle) => throw new NotImplementedException(); + TypeHandle GetTypeHandle(string fullyQualifiedName) => throw new NotImplementedException(); + + bool TryGetStaticFieldAddress(string fullyQualifiedName, string fieldName, out TargetPointer address) => throw new NotImplementedException(); + TargetPointer GetStaticFieldAddress(string fullyQualifiedName, string fieldName) => throw new NotImplementedException(); + + bool TryGetThreadStaticFieldAddress(string fullyQualifiedName, string fieldName, TargetPointer thread, out TargetPointer address) => throw new NotImplementedException(); + TargetPointer GetThreadStaticFieldAddress(string fullyQualifiedName, string fieldName, TargetPointer thread) => throw new NotImplementedException(); +} + +public readonly struct ManagedTypeSource : IManagedTypeSource +{ + // Everything throws NotImplementedException +} 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 3d1c8cf60fbaaf..35be67b4b8a32b 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 @@ -178,7 +178,6 @@ public interface IRuntimeTypeSystem : IContract TypeHandle GetTypeParam(TypeHandle typeHandle) => throw new NotImplementedException(); TypeHandle GetConstructedType(TypeHandle typeHandle, CorElementType corElementType, int rank, ImmutableArray typeArguments) => throw new NotImplementedException(); TypeHandle GetPrimitiveType(CorElementType typeCode) => throw new NotImplementedException(); - TypeHandle GetTypeByNameAndModule(string name, string nameSpace, ModuleHandle moduleHandle) => throw new NotImplementedException(); bool IsGenericVariable(TypeHandle typeHandle, out TargetPointer module, out uint token) => throw new NotImplementedException(); bool IsFunctionPointer(TypeHandle typeHandle, out ReadOnlySpan retAndArgTypes, out byte callConv) => throw new NotImplementedException(); bool IsPointer(TypeHandle typeHandle) => throw new NotImplementedException(); @@ -264,9 +263,6 @@ public interface IRuntimeTypeSystem : IContract TargetPointer GetFieldDescStaticAddress(TargetPointer fieldDescPointer) => throw new NotImplementedException(); TargetPointer GetFieldDescThreadStaticAddress(TargetPointer fieldDescPointer, TargetPointer thread) => throw new NotImplementedException(); #endregion FieldDesc inspection APIs - #region Other APIs - void GetCoreLibFieldDescAndDef(string typeNamespace, string typeName, string fieldName, out TargetPointer fieldDescAddr, out FieldDefinition fieldDef) => throw new NotImplementedException(); - #endregion Other APIs } public struct RuntimeTypeSystem : IRuntimeTypeSystem diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ManagedTypeSource_1.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ManagedTypeSource_1.cs new file mode 100644 index 00000000000000..046dc6c499efd5 --- /dev/null +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ManagedTypeSource_1.cs @@ -0,0 +1,326 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Reflection; +using System.Reflection.Metadata; +using System.Reflection.Metadata.Ecma335; + +namespace Microsoft.Diagnostics.DataContractReader.Contracts; + +internal sealed class ManagedTypeSource_1 : IManagedTypeSource +{ + private readonly Target _target; + private readonly Dictionary _typeInfoCache = new(); + private readonly Dictionary _typeHandleCache = new(); + private readonly Dictionary<(string Fqn, string FieldName), TargetPointer> _fieldDescCache = new(); + + public ManagedTypeSource_1(Target target) + { + _target = target; + } + + public void Flush() + { + _typeInfoCache.Clear(); + _typeHandleCache.Clear(); + _fieldDescCache.Clear(); + } + + public Target.TypeInfo GetTypeInfo(string fullyQualifiedName) + { + if (!TryGetTypeInfo(fullyQualifiedName, out Target.TypeInfo info)) + throw new InvalidOperationException($"Managed type '{fullyQualifiedName}' is not resolvable through {nameof(ManagedTypeSource_1)}."); + + return info; + } + + public bool TryGetTypeInfo(string fullyQualifiedName, out Target.TypeInfo info) + { + if (_typeInfoCache.TryGetValue(fullyQualifiedName, out info)) + return true; + + if (!TryBuildTypeInfo(fullyQualifiedName, out info)) + return false; + + _typeInfoCache[fullyQualifiedName] = info; + return true; + } + + public TypeHandle GetTypeHandle(string fullyQualifiedName) + { + if (!TryGetTypeHandle(fullyQualifiedName, out TypeHandle typeHandle)) + throw new InvalidOperationException($"Managed type '{fullyQualifiedName}' is not resolvable through {nameof(ManagedTypeSource_1)}."); + + return typeHandle; + } + + public bool TryGetTypeHandle(string fullyQualifiedName, out TypeHandle typeHandle) + { + if (_typeHandleCache.TryGetValue(fullyQualifiedName, out typeHandle)) + return true; + + if (!TryResolveType(fullyQualifiedName, out typeHandle, out _, out _)) + return false; + + _typeHandleCache[fullyQualifiedName] = typeHandle; + return true; + } + + public TargetPointer GetStaticFieldAddress(string fullyQualifiedName, string fieldName) + { + if (!TryGetStaticFieldAddress(fullyQualifiedName, fieldName, out TargetPointer address)) + throw new InvalidOperationException($"Static field '{fieldName}' on managed type '{fullyQualifiedName}' is not resolvable through {nameof(ManagedTypeSource_1)}."); + + return address; + } + + public bool TryGetStaticFieldAddress(string fullyQualifiedName, string fieldName, out TargetPointer address) + { + address = TargetPointer.Null; + if (!TryGetFieldDesc(fullyQualifiedName, fieldName, out TargetPointer fieldDescAddr)) + return false; + + IRuntimeTypeSystem rts = _target.Contracts.RuntimeTypeSystem; + + // Thread-statics return a per-thread offset, not an absolute address — use the + // dedicated thread-static API for those. + if (rts.IsFieldDescThreadStatic(fieldDescAddr)) + return false; + + // Gate on the statics base being allocated for the enclosing class so callers cannot + // dereference a small offset-from-zero when the class has not been initialized. + TargetPointer enclosingMT = rts.GetMTOfEnclosingClass(fieldDescAddr); + TypeHandle ctx = rts.GetTypeHandle(enclosingMT); + CorElementType type = rts.GetFieldDescType(fieldDescAddr); + bool isGC = type is CorElementType.Class or CorElementType.ValueType; + TargetPointer @base = isGC ? rts.GetGCStaticsBasePointer(ctx) : rts.GetNonGCStaticsBasePointer(ctx); + if (@base == TargetPointer.Null) + return false; + + address = rts.GetFieldDescStaticAddress(fieldDescAddr); + return true; + } + + public TargetPointer GetThreadStaticFieldAddress(string fullyQualifiedName, string fieldName, TargetPointer thread) + { + if (!TryGetThreadStaticFieldAddress(fullyQualifiedName, fieldName, thread, out TargetPointer address)) + throw new InvalidOperationException($"Thread-static field '{fieldName}' on managed type '{fullyQualifiedName}' is not resolvable through {nameof(ManagedTypeSource_1)}."); + + return address; + } + + public bool TryGetThreadStaticFieldAddress(string fullyQualifiedName, string fieldName, TargetPointer thread, out TargetPointer address) + { + address = TargetPointer.Null; + if (!TryGetFieldDesc(fullyQualifiedName, fieldName, out TargetPointer fieldDescAddr)) + return false; + + IRuntimeTypeSystem rts = _target.Contracts.RuntimeTypeSystem; + + // Non-thread-statics have an absolute address — use the dedicated static API. + if (!rts.IsFieldDescThreadStatic(fieldDescAddr)) + return false; + + // Gate on the per-thread base being allocated for the enclosing class so callers + // cannot dereference a small offset-from-zero when this thread has not initialized + // thread-static storage for the type. + TargetPointer enclosingMT = rts.GetMTOfEnclosingClass(fieldDescAddr); + TypeHandle ctx = rts.GetTypeHandle(enclosingMT); + CorElementType type = rts.GetFieldDescType(fieldDescAddr); + bool isGC = type is CorElementType.Class or CorElementType.ValueType; + TargetPointer @base = isGC + ? rts.GetGCThreadStaticsBasePointer(ctx, thread) + : rts.GetNonGCThreadStaticsBasePointer(ctx, thread); + if (@base == TargetPointer.Null) + return false; + + address = rts.GetFieldDescThreadStaticAddress(fieldDescAddr, thread); + return true; + } + + private bool TryGetFieldDesc(string fullyQualifiedName, string fieldName, out TargetPointer fieldDescAddr) + { + (string Fqn, string FieldName) key = (fullyQualifiedName, fieldName); + if (_fieldDescCache.TryGetValue(key, out fieldDescAddr)) + return fieldDescAddr != TargetPointer.Null; + + if (!TryResolveType(fullyQualifiedName, out TypeHandle th, out _, out _)) + { + fieldDescAddr = TargetPointer.Null; + _fieldDescCache[key] = TargetPointer.Null; + return false; + } + + fieldDescAddr = _target.Contracts.RuntimeTypeSystem.GetFieldDescByName(th, fieldName); + _fieldDescCache[key] = fieldDescAddr; + return fieldDescAddr != TargetPointer.Null; + } + + private bool TryBuildTypeInfo(string managedFqName, out Target.TypeInfo info) + { + info = default; + + if (!TryResolveType(managedFqName, out TypeHandle th, out MetadataReader? mdReader, out TypeDefinition typeDef)) + return false; + + IRuntimeTypeSystem rts = _target.Contracts.RuntimeTypeSystem; + + Dictionary instanceFields = new(); + + foreach (FieldDefinitionHandle fieldHandle in typeDef.GetFields()) + { + FieldDefinition fieldDef = mdReader.GetFieldDefinition(fieldHandle); + string fieldName = mdReader.GetString(fieldDef.Name); + + if ((fieldDef.Attributes & FieldAttributes.Static) != 0) + continue; + + TargetPointer fieldDescAddr = rts.GetFieldDescByName(th, fieldName); + if (fieldDescAddr == TargetPointer.Null) + continue; + + uint fdOffset = rts.GetFieldDescOffset(fieldDescAddr, fieldDef); + CorElementType elementType = rts.GetFieldDescType(fieldDescAddr); + // Raw field offset, relative to the start of the instance data. Reference-type + // consumers must add the object header size; value-type consumers (e.g. struct + // entries embedded in arrays) read directly from the slot address. + instanceFields[fieldName] = new Target.FieldInfo + { + Offset = (int)fdOffset, + TypeName = MapCorElementTypeToDescriptorName(elementType), + }; + } + + info = new Target.TypeInfo + { + Fields = instanceFields, + }; + return true; + } + + private bool TryResolveType(string managedFqName, out TypeHandle th, [NotNullWhen(true)] out MetadataReader? mdReader, out TypeDefinition typeDef) + { + th = new TypeHandle(TargetPointer.Null); + typeDef = default; + + ILoader loader = _target.Contracts.Loader; + TargetPointer systemAssembly = loader.GetSystemAssembly(); + ModuleHandle moduleHandle = loader.GetModuleHandleFromAssemblyPtr(systemAssembly); + + if (!TryFindTypeDefinition(moduleHandle, managedFqName, out mdReader, out TypeDefinitionHandle typeDefHandle)) + return false; + + // Look up the runtime TypeHandle via the module's TypeDef → MethodTable map. + int token = MetadataTokens.GetToken((EntityHandle)typeDefHandle); + TargetPointer typeDefToMethodTable = loader.GetLookupTables(moduleHandle).TypeDefToMethodTable; + TargetPointer typeHandlePtr = loader.GetModuleLookupMapElement(typeDefToMethodTable, (uint)token, out _); + if (typeHandlePtr == TargetPointer.Null) + return false; + + th = _target.Contracts.RuntimeTypeSystem.GetTypeHandle(typeHandlePtr); + typeDef = mdReader.GetTypeDefinition(typeDefHandle); + return true; + } + + /// + /// Walks the metadata of to locate the + /// for the supplied fully-qualified type name. Nested + /// types are encoded with + separators (e.g. Outer+Inner); the outer-most + /// segment is matched against Namespace + "." + Name on each top-level type, which + /// avoids any fragility around dots within type or namespace names. + /// Assembly forwarders are not followed — all managed types resolved through this contract + /// are expected to live in System.Private.CoreLib. + /// + private bool TryFindTypeDefinition( + ModuleHandle moduleHandle, + string fullyQualifiedName, + [NotNullWhen(true)] out MetadataReader? mdReader, + out TypeDefinitionHandle typeDefHandle) + { + mdReader = _target.Contracts.EcmaMetadata.GetMetadata(moduleHandle); + typeDefHandle = default; + if (mdReader is null) + return false; + + string[] parts = fullyQualifiedName.Split('+'); + string outerFqn = parts[0]; + TypeDefinitionHandle currentHandle = default; + + foreach (TypeDefinitionHandle handle in mdReader.TypeDefinitions) + { + TypeDefinition typedef = mdReader.GetTypeDefinition(handle); + // Nested types have an empty Namespace in metadata; the enclosing type owns the + // namespace. Skip them — they are only reachable via GetNestedTypes() below. + if (typedef.IsNested) + continue; + + string ns = mdReader.GetString(typedef.Namespace); + string name = mdReader.GetString(typedef.Name); + string candidate = ns.Length == 0 ? name : ns + "." + name; + if (candidate == outerFqn) + { + currentHandle = handle; + break; + } + } + + if (currentHandle == default) + return false; + + // Walk down nested types. + for (int i = 1; i < parts.Length; i++) + { + string nestedName = parts[i]; + bool found = false; + foreach (TypeDefinitionHandle nestedHandle in mdReader.GetTypeDefinition(currentHandle).GetNestedTypes()) + { + TypeDefinition nestedDef = mdReader.GetTypeDefinition(nestedHandle); + if (mdReader.GetString(nestedDef.Name) == nestedName) + { + currentHandle = nestedHandle; + found = true; + break; + } + } + if (!found) + return false; + } + + typeDefHandle = currentHandle; + return true; + } + + /// + /// Maps an ECMA-335 to a descriptor-type-name string consumed + /// by debug assertions. Returns null when no precise + /// mapping applies (the assertions treat null/empty as "skip validation"). + /// + private static string? MapCorElementTypeToDescriptorName(CorElementType type) => type switch + { + CorElementType.Boolean => "bool", + CorElementType.I1 => "int8", + CorElementType.U1 => "uint8", + CorElementType.Char or CorElementType.U2 => "uint16", + CorElementType.I2 => "int16", + CorElementType.I4 => "int32", + CorElementType.U4 => "uint32", + CorElementType.I8 => "int64", + CorElementType.U8 => "uint64", + CorElementType.I or CorElementType.U => "nuint", + CorElementType.String + or CorElementType.Ptr + or CorElementType.Byref + or CorElementType.Class + or CorElementType.Array + or CorElementType.SzArray + or CorElementType.GenericInst + or CorElementType.Object + or CorElementType.Var + or CorElementType.MVar + or CorElementType.FnPtr => "pointer", + _ => null, + }; +} diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeTypeSystem_1.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeTypeSystem_1.cs index cd9fb666c51962..9c29754f4a4c9d 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeTypeSystem_1.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeTypeSystem_1.cs @@ -28,14 +28,12 @@ internal partial struct RuntimeTypeSystem_1 : IRuntimeTypeSystem private readonly Dictionary _methodTables = new(); private readonly Dictionary _methodDescs = new(); private readonly Dictionary _typeHandles = new(); - private readonly Dictionary _typeHandlesByName = new(); public void Flush() { _methodTables.Clear(); _methodDescs.Clear(); _typeHandles.Clear(); - _typeHandlesByName.Clear(); } internal struct MethodTable @@ -108,22 +106,6 @@ public override int GetHashCode() } } - private readonly struct TypeKeyByName : IEquatable - { - public TypeKeyByName(string name, string namespaceName, TargetPointer module) - { - Name = name; - Namespace = namespaceName; - Module = module; - } - public string Name { get; } - public string Namespace { get; } - public TargetPointer Module { get; } - public bool Equals(TypeKeyByName other) => Name == other.Name && Namespace == other.Namespace && Module == other.Module; - public override bool Equals(object? obj) => obj is TypeKeyByName other && Equals(other); - public override int GetHashCode() => HashCode.Combine(Name, Namespace, Module); - } - // Low order bits of TypeHandle address. // If the low bits contain a 2, then it is a TypeDesc [Flags] @@ -1061,121 +1043,6 @@ TypeHandle IRuntimeTypeSystem.GetPrimitiveType(CorElementType typeCode) return GetTypeHandle(typeHandlePtr); } - private static bool ModuleMatch(AssemblyReference assemblyRef, AssemblyDefinition assemblyDef) - { - AssemblyName assemblyRefName = assemblyRef.GetAssemblyName(); - AssemblyName assemblyDefName = assemblyDef.GetAssemblyName(); - if ((assemblyRefName.Name != assemblyDefName.Name) || - (assemblyRefName.Version != assemblyDefName.Version) || - (assemblyRefName.CultureName != assemblyDefName.CultureName)) - { - return false; - } - - ReadOnlySpan refToken = assemblyRefName.GetPublicKeyToken(); - ReadOnlySpan defToken = assemblyDefName.GetPublicKeyToken(); - return refToken.SequenceEqual(defToken); - } - - private MetadataReader? LookForHandle(AssemblyReference exportedAssemblyRef) - { - TargetPointer appDomainPointer = _target.ReadGlobalPointer(Constants.Globals.AppDomain); - TargetPointer appDomain = _target.ReadPointer(appDomainPointer); - foreach (ModuleHandle mdhandle in _target.Contracts.Loader.GetModuleHandles(appDomain, AssemblyIterationFlags.IncludeLoaded)) - { - MetadataReader? md2 = _target.Contracts.EcmaMetadata.GetMetadata(mdhandle); - if (md2 == null) - continue; - AssemblyDefinition assemblyDefinition = md2.GetAssemblyDefinition(); - if (ModuleMatch(exportedAssemblyRef, assemblyDefinition)) - { - return md2; - } - } - return null; - } - - TypeHandle IRuntimeTypeSystem.GetTypeByNameAndModule(string name, string nameSpace, ModuleHandle moduleHandle) - { - ILoader loader = _target.Contracts.Loader; - TargetPointer modulePtr = loader.GetModule(moduleHandle); - if (_typeHandlesByName.TryGetValue(new TypeKeyByName(name, nameSpace, modulePtr), out TypeHandle existing)) - return existing; - string[] parts = name.Split('+'); - string outerName = parts[0]; - MetadataReader? md = _target.Contracts.EcmaMetadata.GetMetadata(moduleHandle); - TypeDefinitionHandle currentHandle = default; - // create a hash set of MDs and if we come across the same one more than once in a loop then we return null typehandle - HashSet seenMDs = new(); - // 1. find the outer type - while (md != null && seenMDs.Add(md)) - { - foreach (TypeDefinitionHandle typeDefHandle in md.TypeDefinitions) - { - TypeDefinition typedef = md.GetTypeDefinition(typeDefHandle); - if (md.GetString(typedef.Name) == outerName && md.GetString(typedef.Namespace) == nameSpace) - { - // found our outermost type, remember it - currentHandle = typeDefHandle; - break; - } - } - - if (currentHandle == default) - { - // look for forwarded types - foreach (ExportedTypeHandle exportedTypeHandle in md.ExportedTypes) - { - ExportedType exportedType = md.GetExportedType(exportedTypeHandle); - if (exportedType.Implementation.Kind != HandleKind.AssemblyReference || !exportedType.IsForwarder) - continue; - if (md.GetString(exportedType.Name) == outerName && md.GetString(exportedType.Namespace) == nameSpace) - { - // get the assembly ref for target - AssemblyReferenceHandle arefHandle = (AssemblyReferenceHandle)exportedType.Implementation; - AssemblyReference exportedAssemblyRef = md.GetAssemblyReference(arefHandle); - md = LookForHandle(exportedAssemblyRef); - break; - } - } - } - else break; // if we found our typedef without forwarding break out of the while loop - } - - if (currentHandle == default) - return new TypeHandle(TargetPointer.Null); - - // 2. Walk down the nested types - for (int i = 1; i < parts.Length; i++) - { - string nestedName = parts[i]; - bool found = false; - foreach (TypeDefinitionHandle nestedHandle in md!.GetTypeDefinition(currentHandle).GetNestedTypes()) - { - TypeDefinition nestedDef = md.GetTypeDefinition(nestedHandle); - if (md.GetString(nestedDef.Name) == nestedName) - { - currentHandle = nestedHandle; - found = true; - break; - } - } - if (!found) - return new TypeHandle(TargetPointer.Null); - } - - // 3. We have the handle, look up the type handle - int token = MetadataTokens.GetToken((EntityHandle)currentHandle); - TargetPointer typeDefToMethodTable = loader.GetLookupTables(moduleHandle).TypeDefToMethodTable; - TargetPointer typeHandlePtr = loader.GetModuleLookupMapElement(typeDefToMethodTable, (uint)token, out _); - IRuntimeTypeSystem rts = _target.Contracts.RuntimeTypeSystem; - if (typeHandlePtr == TargetPointer.Null) - return new TypeHandle(TargetPointer.Null); - TypeHandle foundTypeHandle = rts.GetTypeHandle(typeHandlePtr); - _ = _typeHandlesByName.TryAdd(new TypeKeyByName(name, nameSpace, modulePtr), foundTypeHandle); - return foundTypeHandle; - } - public bool IsGenericVariable(TypeHandle typeHandle, out TargetPointer module, out uint token) { module = TargetPointer.Null; @@ -2119,18 +1986,4 @@ private TargetPointer GetFieldDescStaticOrThreadStaticAddress(TargetPointer fiel TargetPointer IRuntimeTypeSystem.GetFieldDescStaticAddress(TargetPointer fieldDescPointer) => GetFieldDescStaticOrThreadStaticAddress(fieldDescPointer); TargetPointer IRuntimeTypeSystem.GetFieldDescThreadStaticAddress(TargetPointer fieldDescPointer, TargetPointer thread) => GetFieldDescStaticOrThreadStaticAddress(fieldDescPointer, thread); - - void IRuntimeTypeSystem.GetCoreLibFieldDescAndDef(string @namespace, string typeName, string fieldName, out TargetPointer fieldDescAddr, out FieldDefinition fieldDef) - { - ILoader loader = _target.Contracts.Loader; - TargetPointer systemAssembly = loader.GetSystemAssembly(); - ModuleHandle moduleHandle = loader.GetModuleHandleFromAssemblyPtr(systemAssembly); - IRuntimeTypeSystem rts = (IRuntimeTypeSystem)this; - TypeHandle th = rts.GetTypeByNameAndModule(typeName, @namespace, moduleHandle); - fieldDescAddr = rts.GetFieldDescByName(th, fieldName); - uint token = rts.GetFieldDescMemberDef(fieldDescAddr); - FieldDefinitionHandle fieldHandle = (FieldDefinitionHandle)MetadataTokens.Handle((int)token); - MetadataReader mdReader = _target.Contracts.EcmaMetadata.GetMetadata(moduleHandle)!; - fieldDef = mdReader.GetFieldDefinition(fieldHandle); - } } diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/CoreCLRContracts.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/CoreCLRContracts.cs index 83996e38ef1d42..f477f3ed9bfd8b 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/CoreCLRContracts.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/CoreCLRContracts.cs @@ -29,6 +29,7 @@ public static void Register(ContractRegistry registry) registry.Register("c1", static t => new BuiltInCOM_1(t)); registry.Register("c1", static t => new ObjectiveCMarshal_1(t)); registry.Register("c1", static t => new ConditionalWeakTable_1(t)); + registry.Register("c1", static t => new ManagedTypeSource_1(t)); registry.Register("c1", static t => new AuxiliarySymbols_1(t)); registry.Register("c1", static t => new Debugger_1(t)); From 09ca47fe16b2b6082413401c5e8c2e630d17390c Mon Sep 17 00:00:00 2001 From: Max Charlamb Date: Tue, 12 May 2026 16:47:26 -0400 Subject: [PATCH 2/7] update users --- .../Contracts/ComWrappers_1.cs | 55 ++---------- .../Contracts/ConditionalWeakTable_1.cs | 85 +++---------------- .../Contracts/SyncBlock_1.cs | 37 +------- .../Data/Managed/ComWrappers.cs | 28 ++++++ .../Data/Managed/ConditionalWeakTable.cs | 29 +++++++ .../Managed/ConditionalWeakTableContainer.cs | 33 +++++++ .../Data/Managed/ConditionalWeakTableEntry.cs | 35 ++++++++ .../Data/Managed/List.cs | 32 +++++++ .../Data/Managed/Lock.cs | 30 +++++++ .../Data/Managed/NativeObjectWrapper.cs | 18 ++++ .../DumpTests/AsyncContinuationDumpTests.cs | 64 ++------------ .../IXCLRDataMethodDefinitionDumpTests.cs | 6 +- .../DumpTests/RuntimeTypeSystemDumpTests.cs | 27 ++---- 13 files changed, 240 insertions(+), 239 deletions(-) create mode 100644 src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Managed/ComWrappers.cs create mode 100644 src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Managed/ConditionalWeakTable.cs create mode 100644 src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Managed/ConditionalWeakTableContainer.cs create mode 100644 src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Managed/ConditionalWeakTableEntry.cs create mode 100644 src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Managed/List.cs create mode 100644 src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Managed/Lock.cs create mode 100644 src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Managed/NativeObjectWrapper.cs diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ComWrappers_1.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ComWrappers_1.cs index e7cdfc0400c0f7..ede62ccec6e374 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ComWrappers_1.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ComWrappers_1.cs @@ -4,29 +4,16 @@ using System; using System.Collections.Generic; using System.Diagnostics; -using System.Reflection.Metadata; using Microsoft.Diagnostics.DataContractReader.Data; namespace Microsoft.Diagnostics.DataContractReader.Contracts; internal struct ComWrappers_1 : IComWrappers { - private const string NativeObjectWrapperNamespace = "System.Runtime.InteropServices"; - private const string NativeObjectWrapperName = "ComWrappers+NativeObjectWrapper"; - private const string ComWrappersNamespace = "System.Runtime.InteropServices"; - private const string ComWrappersName = "ComWrappers"; - private const string NativeObjectWrapperCWTFieldName = "s_nativeObjectWrapperTable"; - private const string AllManagedObjectWrapperTableFieldName = "s_allManagedObjectWrapperTable"; - private const string ListNamespace = "System.Collections.Generic"; - private const string ListName = "List`1"; - private const string ListItemsFieldName = "_items"; - private const string ListSizeFieldName = "_size"; private static readonly Guid IID_IUnknown = new Guid("00000000-0000-0000-C000-000000000046"); private const int CallerDefinedIUnknown = 1; private TargetPointer? _mowTableAddr = null; private TargetPointer? _nativeObjectWrapperCWTAddr = null; - private uint? _listItemsOffset = null; - private uint? _listSizeOffset = null; private readonly Target _target; public ComWrappers_1(Target target) @@ -124,12 +111,7 @@ public TargetPointer GetIdentityForMOW(TargetPointer mow) public List GetMOWs(TargetPointer obj, out bool hasMOWTable) { hasMOWTable = false; - IRuntimeTypeSystem rts = _target.Contracts.RuntimeTypeSystem; - if (_mowTableAddr is null) - { - rts.GetCoreLibFieldDescAndDef(ComWrappersNamespace, ComWrappersName, AllManagedObjectWrapperTableFieldName, out TargetPointer fieldDescAddr, out _); - _mowTableAddr = _target.ReadPointer(rts.GetFieldDescStaticAddress(fieldDescAddr)); - } + _mowTableAddr ??= Data.Managed.ComWrappers.AllManagedObjectWrapperTable(_target); List mows = new List(); @@ -139,20 +121,9 @@ public List GetMOWs(TargetPointer obj, out bool hasMOWTable) if (cwt.TryGetValue(_mowTableAddr.Value, obj, out TargetPointer mowListObj)) { hasMOWTable = true; - Data.Object listObj = _target.ProcessedData.GetOrAdd(mowListObj); - if (_listItemsOffset is null) - { - rts.GetCoreLibFieldDescAndDef(ListNamespace, ListName, ListItemsFieldName, out TargetPointer itemsFieldDescAddr, out FieldDefinition itemsFieldDef); - _listItemsOffset = rts.GetFieldDescOffset(itemsFieldDescAddr, itemsFieldDef); - } - TargetPointer listItemsPtr = _target.ReadPointer(listObj.Data + _listItemsOffset.Value); - - if (_listSizeOffset is null) - { - rts.GetCoreLibFieldDescAndDef(ListNamespace, ListName, ListSizeFieldName, out TargetPointer sizeFieldDescAddr, out FieldDefinition sizeFieldDef); - _listSizeOffset = rts.GetFieldDescOffset(sizeFieldDescAddr, sizeFieldDef); - } - int size = _target.Read(listObj.Data + _listSizeOffset.Value); + Data.Managed.List listData = _target.ProcessedData.GetOrAdd(mowListObj); + TargetPointer listItemsPtr = listData.Items; + int size = listData.Size; if (size > 0 && listItemsPtr != TargetPointer.Null) { @@ -171,26 +142,12 @@ public List GetMOWs(TargetPointer obj, out bool hasMOWTable) public bool IsComWrappersRCW(TargetPointer rcw) { TargetPointer mt = _target.Contracts.Object.GetMethodTableAddress(rcw); - - // get system module - ILoader loader = _target.Contracts.Loader; - TargetPointer systemAssembly = loader.GetSystemAssembly(); - ModuleHandle moduleHandle = loader.GetModuleHandleFromAssemblyPtr(systemAssembly); - - // lookup by name - IRuntimeTypeSystem rts = _target.Contracts.RuntimeTypeSystem; - TargetPointer typeHandlePtr = rts.GetTypeByNameAndModule(NativeObjectWrapperName, NativeObjectWrapperNamespace, moduleHandle).Address; - return mt == typeHandlePtr; + return mt == Data.Managed.NativeObjectWrapper.TypeHandle(_target).Address; } public TargetPointer GetComWrappersRCWForObject(TargetPointer obj) { - IRuntimeTypeSystem rts = _target.Contracts.RuntimeTypeSystem; - if (_nativeObjectWrapperCWTAddr is null) - { - rts.GetCoreLibFieldDescAndDef(ComWrappersNamespace, ComWrappersName, NativeObjectWrapperCWTFieldName, out TargetPointer fieldDescAddr, out _); - _nativeObjectWrapperCWTAddr = _target.ReadPointer(rts.GetFieldDescStaticAddress(fieldDescAddr)); - } + _nativeObjectWrapperCWTAddr ??= Data.Managed.ComWrappers.NativeObjectWrapperTable(_target); if (_nativeObjectWrapperCWTAddr.Value == TargetPointer.Null) return TargetPointer.Null; IConditionalWeakTable cwt = _target.Contracts.ConditionalWeakTable; diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ConditionalWeakTable_1.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ConditionalWeakTable_1.cs index 6ab3e10859b32c..05b80e13188ff7 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ConditionalWeakTable_1.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ConditionalWeakTable_1.cs @@ -1,30 +1,10 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System.Reflection.Metadata; -using System.Reflection.Metadata.Ecma335; - namespace Microsoft.Diagnostics.DataContractReader.Contracts; internal struct ConditionalWeakTable_1 : IConditionalWeakTable { - private const string CWTNamespace = "System.Runtime.CompilerServices"; - private const string CWTTypeName = "ConditionalWeakTable`2"; - private const string ContainerTypeName = "ConditionalWeakTable`2+Container"; - private const string EntryTypeName = "ConditionalWeakTable`2+Entry"; - private const string ContainerFieldName = "_container"; - private const string BucketsFieldName = "_buckets"; - private const string EntriesFieldName = "_entries"; - private const string HashCodeFieldName = "HashCode"; - private const string NextFieldName = "Next"; - private const string DepHndFieldName = "depHnd"; - private uint? _containerFieldOffset = null; - private uint? _bucketsFieldOffset = null; - private uint? _entriesFieldOffset = null; - private uint? _hashCodeFieldOffset = null; - private uint? _nextFieldOffset = null; - private uint? _depHndFieldOffset = null; - private readonly Target _target; internal ConditionalWeakTable_1(Target target) @@ -35,32 +15,10 @@ internal ConditionalWeakTable_1(Target target) bool IConditionalWeakTable.TryGetValue(TargetPointer conditionalWeakTable, TargetPointer key, out TargetPointer value) { value = TargetPointer.Null; - IRuntimeTypeSystem rts = _target.Contracts.RuntimeTypeSystem; - - // Read _container field from the ConditionalWeakTable object - if (_containerFieldOffset is null) - { - rts.GetCoreLibFieldDescAndDef(CWTNamespace, CWTTypeName, ContainerFieldName, out TargetPointer containerFieldDescAddr, out FieldDefinition containerFieldDef); - _containerFieldOffset = rts.GetFieldDescOffset(containerFieldDescAddr, containerFieldDef); - } - Data.Object cwtObj = _target.ProcessedData.GetOrAdd(conditionalWeakTable); - TargetPointer container = _target.ReadPointer(cwtObj.Data + _containerFieldOffset.Value); - // Read _buckets and _entries fields from the Container object - if (_bucketsFieldOffset is null) - { - rts.GetCoreLibFieldDescAndDef(CWTNamespace, ContainerTypeName, BucketsFieldName, out TargetPointer bucketsFieldDescAddr, out FieldDefinition bucketsFieldDef); - _bucketsFieldOffset = rts.GetFieldDescOffset(bucketsFieldDescAddr, bucketsFieldDef); - } - if (_entriesFieldOffset is null) - { - rts.GetCoreLibFieldDescAndDef(CWTNamespace, ContainerTypeName, EntriesFieldName, out TargetPointer entriesFieldDescAddr, out FieldDefinition entriesFieldDef); - _entriesFieldOffset = rts.GetFieldDescOffset(entriesFieldDescAddr, entriesFieldDef); - } - - Data.Object containerObj = _target.ProcessedData.GetOrAdd(container); - TargetPointer bucketsPtr = _target.ReadPointer(containerObj.Data + _bucketsFieldOffset.Value); - TargetPointer entriesPtr = _target.ReadPointer(containerObj.Data + _entriesFieldOffset.Value); + // Read _container from the CWT object and _buckets/_entries from the Container. + Data.Managed.ConditionalWeakTable cwt = _target.ProcessedData.GetOrAdd(conditionalWeakTable); + Data.Managed.ConditionalWeakTableContainer container = _target.ProcessedData.GetOrAdd(cwt.Container); int hashCode = _target.Contracts.Object.TryGetHashCode(key); if (hashCode == 0) @@ -68,44 +26,25 @@ bool IConditionalWeakTable.TryGetValue(TargetPointer conditionalWeakTable, Targe hashCode &= int.MaxValue; - // Read the buckets array - Data.Array bucketsArray = _target.ProcessedData.GetOrAdd(bucketsPtr); + Data.Array bucketsArray = _target.ProcessedData.GetOrAdd(container.Buckets); uint bucketCount = bucketsArray.NumComponents; int bucket = hashCode & (int)(bucketCount - 1); int entriesIndex = _target.Read(bucketsArray.DataPointer + (ulong)(bucket * sizeof(int))); - // Resolve Entry field offsets via RuntimeTypeSystem - if (_hashCodeFieldOffset is null) - { - rts.GetCoreLibFieldDescAndDef(CWTNamespace, EntryTypeName, HashCodeFieldName, out TargetPointer hashCodeFieldDescAddr, out FieldDefinition hashCodeFieldDef); - _hashCodeFieldOffset = rts.GetFieldDescOffset(hashCodeFieldDescAddr, hashCodeFieldDef); - } - if (_nextFieldOffset is null) - { - rts.GetCoreLibFieldDescAndDef(CWTNamespace, EntryTypeName, NextFieldName, out TargetPointer nextFieldDescAddr, out FieldDefinition nextFieldDef); - _nextFieldOffset = rts.GetFieldDescOffset(nextFieldDescAddr, nextFieldDef); - } - if (_depHndFieldOffset is null) - { - rts.GetCoreLibFieldDescAndDef(CWTNamespace, EntryTypeName, DepHndFieldName, out TargetPointer depHndFieldDescAddr, out FieldDefinition depHndFieldDef); - _depHndFieldOffset = rts.GetFieldDescOffset(depHndFieldDescAddr, depHndFieldDef); - } - - // Get entry size from the entries array's component size - Data.Array entriesArray = _target.ProcessedData.GetOrAdd(entriesPtr); - TargetPointer entriesMT = _target.Contracts.Object.GetMethodTableAddress(entriesPtr); - TypeHandle entriesTypeHandle = rts.GetTypeHandle(entriesMT); - uint entrySize = rts.GetComponentSize(entriesTypeHandle); + Data.Array entriesArray = _target.ProcessedData.GetOrAdd(container.Entries); + TargetPointer entriesMT = _target.Contracts.Object.GetMethodTableAddress(container.Entries); + TypeHandle entriesTypeHandle = _target.Contracts.RuntimeTypeSystem.GetTypeHandle(entriesMT); + uint entrySize = _target.Contracts.RuntimeTypeSystem.GetComponentSize(entriesTypeHandle); while (entriesIndex != -1) { TargetPointer entryAddress = entriesArray.DataPointer + (ulong)((uint)entriesIndex * entrySize); + Data.Managed.ConditionalWeakTableEntry entry = _target.ProcessedData.GetOrAdd(entryAddress); - int entryHashCode = _target.Read(entryAddress + _hashCodeFieldOffset.Value); - if (entryHashCode == hashCode) + if (entry.HashCode == hashCode) { - Data.ObjectHandle handle = _target.ProcessedData.GetOrAdd(entryAddress + _depHndFieldOffset.Value); + Data.ObjectHandle handle = _target.ProcessedData.GetOrAdd(entry.DepHndAddress); if (handle.Object == key) { TargetNUInt extraInfo = _target.Contracts.GC.GetHandleExtraInfo(handle.Handle); @@ -115,7 +54,7 @@ bool IConditionalWeakTable.TryGetValue(TargetPointer conditionalWeakTable, Targe } } - entriesIndex = _target.Read(entryAddress + _nextFieldOffset.Value); + entriesIndex = entry.Next; } return false; diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/SyncBlock_1.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/SyncBlock_1.cs index f19648ed6459a3..a8d143cdb4cb13 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/SyncBlock_1.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/SyncBlock_1.cs @@ -1,18 +1,10 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System.Reflection.Metadata; -using System.Reflection.Metadata.Ecma335; - namespace Microsoft.Diagnostics.DataContractReader.Contracts; internal readonly struct SyncBlock_1 : ISyncBlock { - private const string LockStateName = "_state"; - private const string LockOwningThreadIdName = "_owningThreadId"; - private const string LockRecursionCountName = "_recursionCount"; - private const string LockName = "Lock"; - private const string LockNamespace = "System.Threading"; private readonly Target _target; private readonly TargetPointer _syncTableEntries; @@ -55,23 +47,12 @@ public bool TryGetLockInfo(TargetPointer syncBlock, out uint owningThreadId, out if (sb.Lock != null) { - ILoader loader = _target.Contracts.Loader; - TargetPointer systemAssembly = loader.GetSystemAssembly(); - ModuleHandle moduleHandle = loader.GetModuleHandleFromAssemblyPtr(systemAssembly); - - IRuntimeTypeSystem rts = _target.Contracts.RuntimeTypeSystem; - IEcmaMetadata ecmaMetadataContract = _target.Contracts.EcmaMetadata; - TypeHandle lockType = rts.GetTypeByNameAndModule(LockName, LockNamespace, moduleHandle); - MetadataReader mdReader = ecmaMetadataContract.GetMetadata(moduleHandle)!; - TargetPointer lockObjPtr = sb.Lock.Object; - Data.Object lockObj = _target.ProcessedData.GetOrAdd(lockObjPtr); - TargetPointer dataAddr = lockObj.Data; - uint state = ReadUintField(lockType, LockStateName, rts, mdReader, dataAddr); - bool monitorHeld = (state & 1) != 0; + Data.Managed.Lock lockData = _target.ProcessedData.GetOrAdd(sb.Lock.Object); + bool monitorHeld = (lockData.State & 1) != 0; if (monitorHeld) { - owningThreadId = ReadUintField(lockType, LockOwningThreadIdName, rts, mdReader, dataAddr); - recursion = ReadUintField(lockType, LockRecursionCountName, rts, mdReader, dataAddr); + owningThreadId = lockData.OwningThreadId; + recursion = lockData.RecursionCount; } return monitorHeld; } @@ -133,14 +114,4 @@ public bool GetBuiltInComData(TargetPointer syncBlock, out TargetPointer rcw, ou ccf = interopInfo.CCF == 1 ? TargetPointer.Null : interopInfo.CCF; return rcw != TargetPointer.Null || ccw != TargetPointer.Null || ccf != TargetPointer.Null; } - - private uint ReadUintField(TypeHandle enclosingType, string fieldName, IRuntimeTypeSystem rts, MetadataReader mdReader, TargetPointer dataAddr) - { - TargetPointer field = rts.GetFieldDescByName(enclosingType, fieldName); - uint token = rts.GetFieldDescMemberDef(field); - FieldDefinitionHandle fieldHandle = (FieldDefinitionHandle)MetadataTokens.Handle((int)token); - FieldDefinition fieldDef = mdReader.GetFieldDefinition(fieldHandle); - uint offset = rts.GetFieldDescOffset(field, fieldDef); - return _target.Read(dataAddr + offset); - } } diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Managed/ComWrappers.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Managed/ComWrappers.cs new file mode 100644 index 00000000000000..c1d2da231c871e --- /dev/null +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Managed/ComWrappers.cs @@ -0,0 +1,28 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Microsoft.Diagnostics.DataContractReader.Contracts; + +namespace Microsoft.Diagnostics.DataContractReader.Data.Managed; + +internal sealed class ComWrappers : IData +{ + private const string FullyQualifiedName = "System.Runtime.InteropServices.ComWrappers"; + private const string AllManagedObjectWrapperTableFieldName = "s_allManagedObjectWrapperTable"; + private const string NativeObjectWrapperTableFieldName = "s_nativeObjectWrapperTable"; + + public static TypeHandle TypeHandle(Target target) + => target.Contracts.ManagedTypeSource.GetTypeHandle(FullyQualifiedName); + + // Both static fields are managed object references (the ConditionalWeakTable<,> instances). + // The accessors dereference the static slot and return the object pointer. + public static TargetPointer AllManagedObjectWrapperTable(Target target) + => target.ReadPointer(target.Contracts.ManagedTypeSource.GetStaticFieldAddress(FullyQualifiedName, AllManagedObjectWrapperTableFieldName)); + + public static TargetPointer NativeObjectWrapperTable(Target target) + => target.ReadPointer(target.Contracts.ManagedTypeSource.GetStaticFieldAddress(FullyQualifiedName, NativeObjectWrapperTableFieldName)); + + static ComWrappers IData.Create(Target target, TargetPointer address) => new ComWrappers(); + + private ComWrappers() { } +} diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Managed/ConditionalWeakTable.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Managed/ConditionalWeakTable.cs new file mode 100644 index 00000000000000..42869e84e14e37 --- /dev/null +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Managed/ConditionalWeakTable.cs @@ -0,0 +1,29 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Microsoft.Diagnostics.DataContractReader.Contracts; + +namespace Microsoft.Diagnostics.DataContractReader.Data.Managed; + +/// Wraps a System.Runtime.CompilerServices.ConditionalWeakTable<TKey, TValue> instance. +internal sealed class ConditionalWeakTable : IData +{ + private const string FullyQualifiedName = "System.Runtime.CompilerServices.ConditionalWeakTable`2"; + + public static TypeHandle TypeHandle(Target target) + => target.Contracts.ManagedTypeSource.GetTypeHandle(FullyQualifiedName); + + static ConditionalWeakTable IData.Create(Target target, TargetPointer address) + => new ConditionalWeakTable(target, address); + + public ConditionalWeakTable(Target target, TargetPointer address) + { + Target.TypeInfo typeInfo = target.Contracts.ManagedTypeSource.GetTypeInfo(FullyQualifiedName); + TargetPointer dataAddress = address + target.GetTypeInfo(DataType.Object).Size!.Value; + + Container = target.ReadPointerField(dataAddress, typeInfo, "_container"); + } + + /// Pointer to the active Container object. + public TargetPointer Container { get; init; } +} diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Managed/ConditionalWeakTableContainer.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Managed/ConditionalWeakTableContainer.cs new file mode 100644 index 00000000000000..aca1d99d0d9035 --- /dev/null +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Managed/ConditionalWeakTableContainer.cs @@ -0,0 +1,33 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Microsoft.Diagnostics.DataContractReader.Contracts; + +namespace Microsoft.Diagnostics.DataContractReader.Data.Managed; + +/// Wraps a ConditionalWeakTable<,>+Container instance. +internal sealed class ConditionalWeakTableContainer : IData +{ + private const string FullyQualifiedName = "System.Runtime.CompilerServices.ConditionalWeakTable`2+Container"; + + public static TypeHandle TypeHandle(Target target) + => target.Contracts.ManagedTypeSource.GetTypeHandle(FullyQualifiedName); + + static ConditionalWeakTableContainer IData.Create(Target target, TargetPointer address) + => new ConditionalWeakTableContainer(target, address); + + public ConditionalWeakTableContainer(Target target, TargetPointer address) + { + Target.TypeInfo typeInfo = target.Contracts.ManagedTypeSource.GetTypeInfo(FullyQualifiedName); + TargetPointer dataAddress = address + target.GetTypeInfo(DataType.Object).Size!.Value; + + Buckets = target.ReadPointerField(dataAddress, typeInfo, "_buckets"); + Entries = target.ReadPointerField(dataAddress, typeInfo, "_entries"); + } + + /// Pointer to the int[] hash buckets array. + public TargetPointer Buckets { get; init; } + + /// Pointer to the Entry[] entries array. + public TargetPointer Entries { get; init; } +} diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Managed/ConditionalWeakTableEntry.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Managed/ConditionalWeakTableEntry.cs new file mode 100644 index 00000000000000..706abac87b69b6 --- /dev/null +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Managed/ConditionalWeakTableEntry.cs @@ -0,0 +1,35 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Microsoft.Diagnostics.DataContractReader.Contracts; + +namespace Microsoft.Diagnostics.DataContractReader.Data.Managed; + +/// +/// Wraps a single ConditionalWeakTable<,>+Entry value-type slot embedded +/// inline in the _entries array. The slot has no object header. +/// +internal sealed class ConditionalWeakTableEntry : IData +{ + private const string FullyQualifiedName = "System.Runtime.CompilerServices.ConditionalWeakTable`2+Entry"; + + public static TypeHandle TypeHandle(Target target) + => target.Contracts.ManagedTypeSource.GetTypeHandle(FullyQualifiedName); + + static ConditionalWeakTableEntry IData.Create(Target target, TargetPointer address) + => new ConditionalWeakTableEntry(target, address); + + public ConditionalWeakTableEntry(Target target, TargetPointer address) + { + // Value-type slot — no object header; the address IS the data address. + Target.TypeInfo typeInfo = target.Contracts.ManagedTypeSource.GetTypeInfo(FullyQualifiedName); + + HashCode = target.ReadField(address, typeInfo, "HashCode"); + Next = target.ReadField(address, typeInfo, "Next"); + DepHndAddress = address + (uint)typeInfo.Fields["depHnd"].Offset; + } + + public int HashCode { get; init; } + public int Next { get; init; } + public TargetPointer DepHndAddress { get; init; } +} diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Managed/List.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Managed/List.cs new file mode 100644 index 00000000000000..cdce3f263e4176 --- /dev/null +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Managed/List.cs @@ -0,0 +1,32 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Microsoft.Diagnostics.DataContractReader.Contracts; + +namespace Microsoft.Diagnostics.DataContractReader.Data.Managed; + +/// Wraps a System.Collections.Generic.List<T> instance. +internal sealed class List : IData +{ + private const string FullyQualifiedName = "System.Collections.Generic.List`1"; + + public static TypeHandle TypeHandle(Target target) + => target.Contracts.ManagedTypeSource.GetTypeHandle(FullyQualifiedName); + + static List IData.Create(Target target, TargetPointer address) => new List(target, address); + + public List(Target target, TargetPointer address) + { + Target.TypeInfo typeInfo = target.Contracts.ManagedTypeSource.GetTypeInfo(FullyQualifiedName); + TargetPointer dataAddress = address + target.GetTypeInfo(DataType.Object).Size!.Value; + + Items = target.ReadPointerField(dataAddress, typeInfo, "_items"); + Size = target.ReadField(dataAddress, typeInfo, "_size"); + } + + /// Pointer to the backing T[] array. + public TargetPointer Items { get; init; } + + /// Logical element count. + public int Size { get; init; } +} diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Managed/Lock.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Managed/Lock.cs new file mode 100644 index 00000000000000..6d82411cbb46ed --- /dev/null +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Managed/Lock.cs @@ -0,0 +1,30 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Microsoft.Diagnostics.DataContractReader.Contracts; + +namespace Microsoft.Diagnostics.DataContractReader.Data.Managed; + +internal sealed class Lock : IData +{ + private const string FullyQualifiedName = "System.Threading.Lock"; + + public static TypeHandle TypeHandle(Target target) + => target.Contracts.ManagedTypeSource.GetTypeHandle(FullyQualifiedName); + + static Lock IData.Create(Target target, TargetPointer address) => new Lock(target, address); + + public Lock(Target target, TargetPointer address) + { + Target.TypeInfo typeInfo = target.Contracts.ManagedTypeSource.GetTypeInfo(FullyQualifiedName); + TargetPointer dataAddress = address + target.GetTypeInfo(DataType.Object).Size!.Value; + + State = target.ReadField(dataAddress, typeInfo, "_state"); + OwningThreadId = target.ReadField(dataAddress, typeInfo, "_owningThreadId"); + RecursionCount = target.ReadField(dataAddress, typeInfo, "_recursionCount"); + } + + public uint State { get; init; } + public uint OwningThreadId { get; init; } + public uint RecursionCount { get; init; } +} diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Managed/NativeObjectWrapper.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Managed/NativeObjectWrapper.cs new file mode 100644 index 00000000000000..8a7aaa3fcebac8 --- /dev/null +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Managed/NativeObjectWrapper.cs @@ -0,0 +1,18 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Microsoft.Diagnostics.DataContractReader.Contracts; + +namespace Microsoft.Diagnostics.DataContractReader.Data.Managed; + +internal sealed class NativeObjectWrapper : IData +{ + private const string FullyQualifiedName = "System.Runtime.InteropServices.ComWrappers+NativeObjectWrapper"; + + public static TypeHandle TypeHandle(Target target) + => target.Contracts.ManagedTypeSource.GetTypeHandle(FullyQualifiedName); + + static NativeObjectWrapper IData.Create(Target target, TargetPointer address) => new NativeObjectWrapper(); + + private NativeObjectWrapper() { } +} diff --git a/src/native/managed/cdac/tests/DumpTests/AsyncContinuationDumpTests.cs b/src/native/managed/cdac/tests/DumpTests/AsyncContinuationDumpTests.cs index ce52b0cfec4ccd..d7fe7baa071773 100644 --- a/src/native/managed/cdac/tests/DumpTests/AsyncContinuationDumpTests.cs +++ b/src/native/managed/cdac/tests/DumpTests/AsyncContinuationDumpTests.cs @@ -1,8 +1,6 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System.Reflection.Metadata; -using System.Reflection.Metadata.Ecma335; using Microsoft.Diagnostics.DataContractReader.Contracts; using Xunit; @@ -69,59 +67,13 @@ public void ThreadLocalContinuation_IsContinuation(TestConfiguration config) { InitializeDumpTest(config); IRuntimeTypeSystem rts = Target.Contracts.RuntimeTypeSystem; - ILoader loader = Target.Contracts.Loader; IThread threadContract = Target.Contracts.Thread; - IEcmaMetadata ecmaMetadata = Target.Contracts.EcmaMetadata; - - // 1. Locate the AsyncDispatcherInfo type in System.Private.CoreLib. - TargetPointer systemAssembly = loader.GetSystemAssembly(); - ModuleHandle coreLibModule = loader.GetModuleHandleFromAssemblyPtr(systemAssembly); - TypeHandle asyncDispatcherInfoHandle = rts.GetTypeByNameAndModule( - "AsyncDispatcherInfo", - "System.Runtime.CompilerServices", - coreLibModule); - Assert.True(asyncDispatcherInfoHandle.Address != 0, - "Could not find AsyncDispatcherInfo type in CoreLib"); - - // 2. Find the t_current field's offset within the non-GC thread statics block. - // Walk the FieldDescList to find the ThreadStatic field named "t_current". - System.Reflection.Metadata.MetadataReader? md = ecmaMetadata.GetMetadata(coreLibModule); - Assert.NotNull(md); - - TargetPointer fieldDescList = rts.GetFieldDescList(asyncDispatcherInfoHandle); - ushort numStaticFields = rts.GetNumStaticFields(asyncDispatcherInfoHandle); - ushort numThreadStaticFields = rts.GetNumThreadStaticFields(asyncDispatcherInfoHandle); - ushort numInstanceFields = rts.GetNumInstanceFields(asyncDispatcherInfoHandle); - - // FieldDescList has instance fields first, then static fields. - // Thread-static fields are among the static fields. - uint tCurrentOffset = 0; - bool foundField = false; - int totalFields = numInstanceFields + numStaticFields; - uint fieldDescSize = Target.GetTypeInfo(DataType.FieldDesc).Size!.Value; - - for (int i = numInstanceFields; i < totalFields; i++) - { - TargetPointer fieldDesc = fieldDescList + (ulong)(i * (int)fieldDescSize); - if (!rts.IsFieldDescThreadStatic(fieldDesc)) - continue; - - uint memberDef = rts.GetFieldDescMemberDef(fieldDesc); - var fieldDefHandle = (FieldDefinitionHandle)MetadataTokens.Handle((int)memberDef); - var fieldDef = md.GetFieldDefinition(fieldDefHandle); - string fieldName = md.GetString(fieldDef.Name); - - if (fieldName == "t_current") - { - tCurrentOffset = rts.GetFieldDescOffset(fieldDesc, fieldDef); - foundField = true; - break; - } - } + IManagedTypeSource mts = Target.Contracts.ManagedTypeSource; - Assert.True(foundField, $"Could not find t_current field. numStatic={numStaticFields} numThreadStatic={numThreadStaticFields} numInstance={numInstanceFields}"); + const string AsyncDispatcherInfoFqn = "System.Runtime.CompilerServices.AsyncDispatcherInfo"; + const string TCurrentFieldName = "t_current"; - // 3. Walk all threads and read t_current at the discovered offset. + // Walk all threads and locate one whose t_current points at a continuation. ThreadStoreData threadStore = threadContract.GetThreadStoreData(); TargetPointer threadPtr = threadStore.FirstThread; @@ -130,13 +82,9 @@ public void ThreadLocalContinuation_IsContinuation(TestConfiguration config) { ThreadData threadData = threadContract.GetThreadData(threadPtr); - TargetPointer nonGCBase = rts.GetNonGCThreadStaticsBasePointer( - asyncDispatcherInfoHandle, threadPtr); - - if (nonGCBase != TargetPointer.Null) + if (mts.TryGetThreadStaticFieldAddress(AsyncDispatcherInfoFqn, TCurrentFieldName, threadPtr, out TargetPointer tCurrentSlot)) { - TargetPointer tCurrent = Target.ReadPointer(nonGCBase + tCurrentOffset); - + TargetPointer tCurrent = Target.ReadPointer(tCurrentSlot); if (tCurrent != TargetPointer.Null) { // AsyncDispatcherInfo layout: diff --git a/src/native/managed/cdac/tests/DumpTests/IXCLRDataMethodDefinitionDumpTests.cs b/src/native/managed/cdac/tests/DumpTests/IXCLRDataMethodDefinitionDumpTests.cs index d627f24e0ffbe6..5b872af3bd5954 100644 --- a/src/native/managed/cdac/tests/DumpTests/IXCLRDataMethodDefinitionDumpTests.cs +++ b/src/native/managed/cdac/tests/DumpTests/IXCLRDataMethodDefinitionDumpTests.cs @@ -231,10 +231,8 @@ private IXCLRDataMethodDefinition GetGenericMethodDefinition() TargetPointer systemAssembly = loader.GetSystemAssembly(); Contracts.ModuleHandle coreLibModule = loader.GetModuleHandleFromAssemblyPtr(systemAssembly); - TypeHandle listTypeDef = rts.GetTypeByNameAndModule( - "List`1", - "System.Collections.Generic", - coreLibModule); + TypeHandle listTypeDef = Target.Contracts.ManagedTypeSource.GetTypeHandle( + "System.Collections.Generic.List`1"); Assert.True(listTypeDef.Address != 0, "Could not find List<> type definition in CoreLib"); TargetPointer modulePtr = rts.GetModule(listTypeDef); diff --git a/src/native/managed/cdac/tests/DumpTests/RuntimeTypeSystemDumpTests.cs b/src/native/managed/cdac/tests/DumpTests/RuntimeTypeSystemDumpTests.cs index 8a049c6a436af8..ce75f1b66a5f58 100644 --- a/src/native/managed/cdac/tests/DumpTests/RuntimeTypeSystemDumpTests.cs +++ b/src/native/managed/cdac/tests/DumpTests/RuntimeTypeSystemDumpTests.cs @@ -234,7 +234,6 @@ public void RuntimeTypeSystem_IsObjRef_AreConsistent(TestConfiguration config) { InitializeDumpTest(config); IRuntimeTypeSystem rts = Target.Contracts.RuntimeTypeSystem; - ILoader loader = Target.Contracts.Loader; TargetPointer objectMT = Target.ReadPointer(Target.ReadGlobalPointer("ObjectMethodTable")); TargetPointer stringMT = Target.ReadPointer(Target.ReadGlobalPointer("StringMethodTable")); @@ -244,9 +243,7 @@ public void RuntimeTypeSystem_IsObjRef_AreConsistent(TestConfiguration config) TypeHandle stringHandle = rts.GetTypeHandle(stringMT); TypeHandle objectArrayHandle = rts.GetTypeHandle(objectArrayMT); - TargetPointer systemAssembly = loader.GetSystemAssembly(); - ModuleHandle coreLibModule = loader.GetModuleHandleFromAssemblyPtr(systemAssembly); - TypeHandle intPtrHandle = rts.GetTypeByNameAndModule("IntPtr", "System", coreLibModule); + TypeHandle intPtrHandle = Target.Contracts.ManagedTypeSource.GetTypeHandle("System.IntPtr"); Assert.True(rts.IsObjRef(objectHandle)); Assert.True(rts.IsObjRef(stringHandle)); @@ -344,7 +341,6 @@ public void RuntimeTypeSystem_IsValueType(TestConfiguration config) { InitializeDumpTest(config); IRuntimeTypeSystem rts = Target.Contracts.RuntimeTypeSystem; - ILoader loader = Target.Contracts.Loader; // Object and String are not value types TargetPointer objectMTGlobal = Target.ReadGlobalPointer("ObjectMethodTable"); @@ -356,20 +352,12 @@ public void RuntimeTypeSystem_IsValueType(TestConfiguration config) Assert.False(rts.IsValueType(rts.GetTypeHandle(stringMT))); // Int32 is a value type (TruePrimitive category) - TargetPointer systemAssembly = loader.GetSystemAssembly(); - ModuleHandle coreLibModule = loader.GetModuleHandleFromAssemblyPtr(systemAssembly); - TypeHandle int32Type = rts.GetTypeByNameAndModule( - "Int32", - "System", - coreLibModule); + TypeHandle int32Type = Target.Contracts.ManagedTypeSource.GetTypeHandle("System.Int32"); Assert.True(int32Type.Address != 0, "Could not find Int32 type in CoreLib"); Assert.True(rts.IsValueType(int32Type)); // Nullable<> is a value type (Category_Nullable) — loaded because Container.Value is int? - TypeHandle nullableType = rts.GetTypeByNameAndModule( - "Nullable`1", - "System", - coreLibModule); + TypeHandle nullableType = Target.Contracts.ManagedTypeSource.GetTypeHandle("System.Nullable`1"); Assert.True(nullableType.Address != 0, "Could not find Nullable<> type in CoreLib"); Assert.True(rts.IsValueType(nullableType)); } @@ -380,17 +368,12 @@ public void RuntimeTypeSystem_GenericTypeDefinitionContainsGenericVariables(Test { InitializeDumpTest(config); IRuntimeTypeSystem rts = Target.Contracts.RuntimeTypeSystem; - ILoader loader = Target.Contracts.Loader; // Look up the generic type definition List<> in System.Private.CoreLib. // The debuggee instantiates List, so the runtime has loaded // both the closed List MT and the open List type definition MT. - TargetPointer systemAssembly = loader.GetSystemAssembly(); - ModuleHandle coreLibModule = loader.GetModuleHandleFromAssemblyPtr(systemAssembly); - TypeHandle listTypeDef = rts.GetTypeByNameAndModule( - "List`1", - "System.Collections.Generic", - coreLibModule); + TypeHandle listTypeDef = Target.Contracts.ManagedTypeSource.GetTypeHandle( + "System.Collections.Generic.List`1"); Assert.True(listTypeDef.Address != 0, "Could not find List<> type definition in CoreLib"); Assert.True(rts.IsGenericTypeDefinition(listTypeDef)); From 5787821fb583b1e9fd4701421526bada08e12f6d Mon Sep 17 00:00:00 2001 From: Max Charlamb Date: Tue, 12 May 2026 19:23:27 -0400 Subject: [PATCH 3/7] Address Copilot review: fix Lock _owningThreadId type and ComWrappers 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> --- .../Contracts/SyncBlock_1.cs | 2 +- .../Data/Managed/ComWrappers.cs | 13 ++++++++++--- .../Data/Managed/Lock.cs | 4 ++-- 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/SyncBlock_1.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/SyncBlock_1.cs index a8d143cdb4cb13..ba8abee3c02e9d 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/SyncBlock_1.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/SyncBlock_1.cs @@ -51,7 +51,7 @@ public bool TryGetLockInfo(TargetPointer syncBlock, out uint owningThreadId, out bool monitorHeld = (lockData.State & 1) != 0; if (monitorHeld) { - owningThreadId = lockData.OwningThreadId; + owningThreadId = (uint)lockData.OwningThreadId; recursion = lockData.RecursionCount; } return monitorHeld; diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Managed/ComWrappers.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Managed/ComWrappers.cs index c1d2da231c871e..1b1d9ea7dddabc 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Managed/ComWrappers.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Managed/ComWrappers.cs @@ -15,12 +15,19 @@ public static TypeHandle TypeHandle(Target target) => target.Contracts.ManagedTypeSource.GetTypeHandle(FullyQualifiedName); // Both static fields are managed object references (the ConditionalWeakTable<,> instances). - // The accessors dereference the static slot and return the object pointer. + // The accessors dereference the static slot and return the object pointer, or + // TargetPointer.Null when the static slot is not yet allocated (e.g. ComWrappers has not + // been used in this process). Callers depend on the null-return contract — throwing here + // would prevent them from reporting S_FALSE / "no data" through the Legacy SOS DAC path. public static TargetPointer AllManagedObjectWrapperTable(Target target) - => target.ReadPointer(target.Contracts.ManagedTypeSource.GetStaticFieldAddress(FullyQualifiedName, AllManagedObjectWrapperTableFieldName)); + => target.Contracts.ManagedTypeSource.TryGetStaticFieldAddress(FullyQualifiedName, AllManagedObjectWrapperTableFieldName, out TargetPointer address) + ? target.ReadPointer(address) + : TargetPointer.Null; public static TargetPointer NativeObjectWrapperTable(Target target) - => target.ReadPointer(target.Contracts.ManagedTypeSource.GetStaticFieldAddress(FullyQualifiedName, NativeObjectWrapperTableFieldName)); + => target.Contracts.ManagedTypeSource.TryGetStaticFieldAddress(FullyQualifiedName, NativeObjectWrapperTableFieldName, out TargetPointer address) + ? target.ReadPointer(address) + : TargetPointer.Null; static ComWrappers IData.Create(Target target, TargetPointer address) => new ComWrappers(); diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Managed/Lock.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Managed/Lock.cs index 6d82411cbb46ed..e53573e05fbbe0 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Managed/Lock.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Managed/Lock.cs @@ -20,11 +20,11 @@ public Lock(Target target, TargetPointer address) TargetPointer dataAddress = address + target.GetTypeInfo(DataType.Object).Size!.Value; State = target.ReadField(dataAddress, typeInfo, "_state"); - OwningThreadId = target.ReadField(dataAddress, typeInfo, "_owningThreadId"); + OwningThreadId = target.ReadField(dataAddress, typeInfo, "_owningThreadId"); RecursionCount = target.ReadField(dataAddress, typeInfo, "_recursionCount"); } public uint State { get; init; } - public uint OwningThreadId { get; init; } + public int OwningThreadId { get; init; } public uint RecursionCount { get; init; } } From 6b1f1d2b9a1d35c3bfa157d68f499edec729af50 Mon Sep 17 00:00:00 2001 From: Max Charlamb Date: Wed, 13 May 2026 02:02:17 -0400 Subject: [PATCH 4/7] Register ManagedTypeSource contract in datadescriptor.inc 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> --- src/coreclr/vm/datadescriptor/datadescriptor.inc | 1 + 1 file changed, 1 insertion(+) diff --git a/src/coreclr/vm/datadescriptor/datadescriptor.inc b/src/coreclr/vm/datadescriptor/datadescriptor.inc index 307b2eef5bb9f7..343d19f15c471c 100644 --- a/src/coreclr/vm/datadescriptor/datadescriptor.inc +++ b/src/coreclr/vm/datadescriptor/datadescriptor.inc @@ -1573,6 +1573,7 @@ CDAC_GLOBAL_CONTRACT(Exception, c1) CDAC_GLOBAL_CONTRACT(ExecutionManager, c2) CDAC_GLOBAL_CONTRACT(GCInfo, c1) CDAC_GLOBAL_CONTRACT(Loader, c1) +CDAC_GLOBAL_CONTRACT(ManagedTypeSource, c1) CDAC_GLOBAL_CONTRACT(Notifications, c1) #ifdef FEATURE_OBJCMARSHAL CDAC_GLOBAL_CONTRACT(ObjectiveCMarshal, c1) From f22b14e84e653cd0611e74225e7d7e84ccba02b9 Mon Sep 17 00:00:00 2001 From: Max Charlamb Date: Wed, 13 May 2026 02:10:11 -0400 Subject: [PATCH 5/7] Document ManagedTypeSource contract and update consumers 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> --- docs/design/datacontracts/ComWrappers.md | 33 +-- .../datacontracts/ConditionalWeakTable.md | 68 +++--- .../design/datacontracts/ManagedTypeSource.md | 200 ++++++++++++++++++ docs/design/datacontracts/SyncBlock.md | 43 ++-- 4 files changed, 264 insertions(+), 80 deletions(-) create mode 100644 docs/design/datacontracts/ManagedTypeSource.md diff --git a/docs/design/datacontracts/ComWrappers.md b/docs/design/datacontracts/ComWrappers.md index 0c8da25e1ce8bd..8755d09ce357a9 100644 --- a/docs/design/datacontracts/ComWrappers.md +++ b/docs/design/datacontracts/ComWrappers.md @@ -25,7 +25,8 @@ TargetPointer GetComWrappersRCWForObject(TargetPointer obj); ## Version 1 -Data descriptors used: +### Data descriptors used + | Data Descriptor Name | Field | Meaning | | --- | --- | --- | | `NativeObjectWrapperObject` | `ExternalComObject` | Address of the external COM object | @@ -40,26 +41,33 @@ Data descriptors used: | `InternalComInterfaceDispatch` | `Entries` | Start of vtable entry pointers within the dispatch block | | `ComWrappersVtablePtrs` | `Size` | Size of vtable pointers array | -Global variables used: +### Global variables used + | Global Name | Type | Purpose | | --- | --- | --- | | `ComWrappersVtablePtrs` | TargetPointer | Pointer to struct containing ComWrappers-related function pointers | | `DispatchThisPtrMask` | TargetPointer | Used to mask low bits of CCW pointer to the nearest valid address from which to read a managed object wrapper | -### Contract Constants: +### Managed types used + +| Fully-qualified name | Module | Members read | Purpose | +| --- | --- | --- | --- | +| `System.Runtime.InteropServices.ComWrappers+NativeObjectWrapper` | `System.Private.CoreLib` | (type layout only) | Identifies RCWs by comparing their MethodTable against this type | +| `System.Runtime.InteropServices.ComWrappers` | `System.Private.CoreLib` | `static s_allManagedObjectWrapperTable`, `static s_nativeObjectWrapperTable` | Per-object lookups for managed-object-wrapper enumeration and RCW retrieval | + +### Contract Constants + | Name | Type | Purpose | Value | | --- | --- | --- | --- | -| `NativeObjectWrapperNamespace` | string | Namespace of System.Runtime.InteropServices.ComWrappers+NativeObjectWrapper | `System.Runtime.InteropServices` | -| `NativeObjectWrapperName` | string | Name of System.Runtime.InteropServices.ComWrappers+NativeObjectWrapper | `ComWrappers+NativeObjectWrapper` | | `CallerDefinedIUnknown` | int | Flag bit for `CreateComInterfaceFlagsEx` indicating caller-defined IUnknown | `1` | | `IID_IUnknown` | Guid | The IID for IUnknown | `00000000-0000-0000-C000-000000000046` | -Contracts used: +### Contracts used + | Contract Name | | --- | | `Object` | -| `RuntimeTypeSystem` | -| `Loader` | +| `ManagedTypeSource` | | `ConditionalWeakTable` | @@ -157,7 +165,7 @@ public TargetPointer GetIdentityForMOW(TargetPointer mow) public List GetMOWs(TargetPointer obj, out bool hasMOWTable) { - // Look up the static field ComWrappers.s_allManagedObjectWrapperTable via RuntimeTypeSystem + // Look up the static field ComWrappers.s_allManagedObjectWrapperTable via ManagedTypeSource // Use the ConditionalWeakTable contract to find the List value // Iterate the list and return each holder's Wrapper pointer (the ManagedObjectWrapperLayout address) } @@ -165,14 +173,13 @@ public List GetMOWs(TargetPointer obj, out bool hasMOWTable) public bool IsComWrappersRCW(TargetPointer rcw) { // Get method table from rcw using Object contract GetMethodTableAddress - // Find module from the system assembly - // Then use RuntimeTypeSystem contract to look up type handle by name/namespace hardcoded in contract - // Then compare the rcw method table with the method table found by name/namespace/module + // Resolve ComWrappers+NativeObjectWrapper via ManagedTypeSource by fully-qualified name + // Then compare the rcw method table with the method table found by name } public TargetPointer GetComWrappersRCWForObject(TargetPointer obj) { - // Look up the static field ComWrappers.s_nativeObjectWrapperTable via RuntimeTypeSystem + // Look up the static field ComWrappers.s_nativeObjectWrapperTable via ManagedTypeSource // Use the ConditionalWeakTable contract to find the value associated with obj // If found, return the NativeObjectWrapper reference (tagged with low bit by caller) TargetPointer cwtTable = /* address of ComWrappers.s_nativeObjectWrapperTable static field */; diff --git a/docs/design/datacontracts/ConditionalWeakTable.md b/docs/design/datacontracts/ConditionalWeakTable.md index 0b81d76edd0062..36c6fa9554a212 100644 --- a/docs/design/datacontracts/ConditionalWeakTable.md +++ b/docs/design/datacontracts/ConditionalWeakTable.md @@ -13,38 +13,35 @@ bool TryGetValue(TargetPointer conditionalWeakTable, TargetPointer key, out Targ ## Version 1 This contract reads the field layout of `ConditionalWeakTable` and its nested types -(`Container`, `Container+Entry`) via the `RuntimeTypeSystem` contract rather than cDAC data descriptors. -Field offsets are resolved by name at runtime. +(`Container`, `Container+Entry`) via the [`ManagedTypeSource`](ManagedTypeSource.md) contract rather +than cDAC data descriptors. Field offsets are resolved by name at runtime. + +### Data descriptors used -Contract constants: -| Constant | Value | Meaning | -| --- | --- | --- | -| `CWTNamespace` | `System.Runtime.CompilerServices` | Namespace of the `ConditionalWeakTable` type | -| `CWTTypeName` | ``ConditionalWeakTable`2`` | Name of the `ConditionalWeakTable` type | -| `ContainerTypeName` | ``ConditionalWeakTable`2+Container`` | Name of the nested `Container` type | -| `EntryTypeName` | ``ConditionalWeakTable`2+Entry`` | Name of the nested `Entry` value type | -| `ContainerFieldName` | `_container` | Field on `ConditionalWeakTable` pointing to the active container | -| `BucketsFieldName` | `_buckets` | Field on `Container` pointing to the `int[]` buckets array | -| `EntriesFieldName` | `_entries` | Field on `Container` pointing to the `Entry[]` entries array | -| `HashCodeFieldName` | `HashCode` | Field on `Entry` storing the hash code (masked to positive int) | -| `NextFieldName` | `Next` | Field on `Entry` storing the next index in the chain, or -1 | -| `DepHndFieldName` | `depHnd` | Field on `Entry` storing the dependent handle | - -Data descriptors used: | Data Descriptor Name | Field | Meaning | | --- | --- | --- | | `Array` | `m_NumComponents` | Number of elements in the array | -Contracts used: +### Managed types used + +| Fully-qualified name | Module | Members read | Purpose | +| --- | --- | --- | --- | +| ``System.Runtime.CompilerServices.ConditionalWeakTable`2`` | `System.Private.CoreLib` | `_container` | Pointer to the active container | +| ``System.Runtime.CompilerServices.ConditionalWeakTable`2+Container`` | `System.Private.CoreLib` | `_buckets`, `_entries` | `int[]` bucket map and `Entry[]` storage | +| ``System.Runtime.CompilerServices.ConditionalWeakTable`2+Entry`` | `System.Private.CoreLib` | `HashCode`, `Next`, `depHnd` | Hash code, next-in-chain index, and dependent handle for each entry | + +### Contracts used + | Contract Name | | --- | | `Object` | | `GC` | +| `ManagedTypeSource` | | `RuntimeTypeSystem` | The algorithm looks up the `_container` field of the `ConditionalWeakTable` object, then reads the `_buckets` and `_entries` fields from the container. It resolves `Entry` field offsets (`HashCode`, -`Next`, `depHnd`) via `RuntimeTypeSystem` and determines the entry stride from the entries array's +`Next`, `depHnd`) via `ManagedTypeSource` and determines the entry stride from the entries array's component size. ``` csharp @@ -52,28 +49,19 @@ bool TryGetValue(TargetPointer conditionalWeakTable, TargetPointer key, out Targ { value = TargetPointer.Null; - // Resolve field offsets by name from CoreLib via RuntimeTypeSystem. - // GetCoreLibFieldDescAndDef returns a FieldDesc address and FieldDefinition; - // GetFieldDescOffset extracts the byte offset from those. + // Resolve field offsets by name via ManagedTypeSource. IRuntimeTypeSystem rts = target.Contracts.RuntimeTypeSystem; - - rts.GetCoreLibFieldDescAndDef(CWTNamespace, CWTTypeName, ContainerFieldName, out fd, out fDef); - uint containerOffset = rts.GetFieldDescOffset(fd, fDef); - - rts.GetCoreLibFieldDescAndDef(CWTNamespace, ContainerTypeName, BucketsFieldName, out fd, out fDef); - uint bucketsOffset = rts.GetFieldDescOffset(fd, fDef); - - rts.GetCoreLibFieldDescAndDef(CWTNamespace, ContainerTypeName, EntriesFieldName, out fd, out fDef); - uint entriesOffset = rts.GetFieldDescOffset(fd, fDef); - - rts.GetCoreLibFieldDescAndDef(CWTNamespace, EntryTypeName, HashCodeFieldName, out fd, out fDef); - uint hashCodeOffset = rts.GetFieldDescOffset(fd, fDef); - - rts.GetCoreLibFieldDescAndDef(CWTNamespace, EntryTypeName, NextFieldName, out fd, out fDef); - uint nextOffset = rts.GetFieldDescOffset(fd, fDef); - - rts.GetCoreLibFieldDescAndDef(CWTNamespace, EntryTypeName, DepHndFieldName, out fd, out fDef); - uint depHndOffset = rts.GetFieldDescOffset(fd, fDef); + IManagedTypeSource mts = target.Contracts.ManagedTypeSource; + Target.TypeInfo cwtType = mts.GetTypeInfo("System.Runtime.CompilerServices.ConditionalWeakTable`2"); + Target.TypeInfo containerType = mts.GetTypeInfo("System.Runtime.CompilerServices.ConditionalWeakTable`2+Container"); + Target.TypeInfo entryType = mts.GetTypeInfo("System.Runtime.CompilerServices.ConditionalWeakTable`2+Entry"); + + uint containerOffset = (uint)cwtType.Fields["_container"].Offset; + uint bucketsOffset = (uint)containerType.Fields["_buckets"].Offset; + uint entriesOffset = (uint)containerType.Fields["_entries"].Offset; + uint hashCodeOffset = (uint)entryType.Fields["HashCode"].Offset; + uint nextOffset = (uint)entryType.Fields["Next"].Offset; + uint depHndOffset = (uint)entryType.Fields["depHnd"].Offset; // Navigate from the ConditionalWeakTable object to its container TargetPointer container = target.ReadPointer(conditionalWeakTable + /* Object data offset */ + containerOffset); diff --git a/docs/design/datacontracts/ManagedTypeSource.md b/docs/design/datacontracts/ManagedTypeSource.md new file mode 100644 index 00000000000000..2e4b3a3ada9ae3 --- /dev/null +++ b/docs/design/datacontracts/ManagedTypeSource.md @@ -0,0 +1,200 @@ +# Contract ManagedTypeSource + +Resolves the runtime layout of managed CLR types and the addresses of their +static and thread-static fields by fully-qualified name. Consumers use this +contract when an algorithm needs to read a managed type that is defined in the +assemblies (e.g. `System.Threading.Lock`, `System.Runtime.CompilerServices.ConditionalWeakTable+Container`) rather than via a native data descriptor. + +All lookups are performed against `System.Private.CoreLib` (the runtime's +system assembly). Assembly forwarders are not followed. Nested types are +addressed by separating the outer and inner type names with `+` (matching +ECMA-335 / `Type.FullName` conventions), e.g. +`System.Runtime.InteropServices.ComWrappers+NativeObjectWrapper`. + +## APIs of contract + +``` csharp +// Return true and populate `info` with the instance-field layout of the type, or +// false if the type cannot be resolved. +bool TryGetTypeInfo(string fullyQualifiedName, out Target.TypeInfo info); + +// Throws InvalidOperationException if the type cannot be resolved. +Target.TypeInfo GetTypeInfo(string fullyQualifiedName); + +// Return true and populate `typeHandle` with the runtime TypeHandle for the type, +// or false if the type cannot be resolved. +bool TryGetTypeHandle(string fullyQualifiedName, out TypeHandle typeHandle); +TypeHandle GetTypeHandle(string fullyQualifiedName); + +// Return true and populate `address` with the address of the named static field, +// or false if the type / field cannot be resolved or its statics storage has not +// been allocated. Returns false for thread-static fields — use the thread-static +// API for those. +bool TryGetStaticFieldAddress(string fullyQualifiedName, string fieldName, out TargetPointer address); +TargetPointer GetStaticFieldAddress(string fullyQualifiedName, string fieldName); + +// Return true and populate `address` with the per-thread address of the named +// thread-static field on the supplied `thread`, or false if the type / field +// cannot be resolved or per-thread storage has not been allocated for the +// enclosing class on this thread. Returns false for non-thread-static fields. +bool TryGetThreadStaticFieldAddress(string fullyQualifiedName, string fieldName, TargetPointer thread, out TargetPointer address); +TargetPointer GetThreadStaticFieldAddress(string fullyQualifiedName, string fieldName, TargetPointer thread); +``` + +## Version 1 + +### Data descriptors used + +Data descriptors used: none + +### Global variables used + +Global variables used: none + +### Managed types used + +The contract does not itself enumerate a fixed set of managed types — the +caller supplies the FQN. Consumers should document the specific managed types +they read in their own `### Managed types used` section. + +### Contracts used + +| Contract Name | +| --- | +| `Loader` | +| `EcmaMetadata` | +| `RuntimeTypeSystem` | + +``` csharp +// Type resolution: parse the fully-qualified name, walk System.Private.CoreLib's +// metadata to locate the TypeDef, then map TypeDef -> MethodTable via the loader. +bool TryResolveType(string managedFqName, out TypeHandle th, out MetadataReader mdReader, out TypeDefinition typeDef) +{ + ILoader loader = target.Contracts.Loader; + TargetPointer systemAssembly = loader.GetSystemAssembly(); + ModuleHandle moduleHandle = loader.GetModuleHandleFromAssemblyPtr(systemAssembly); + + mdReader = target.Contracts.EcmaMetadata.GetMetadata(moduleHandle); + if (mdReader == null) + return false; + + // Split outer+nested segments on '+'. The outer segment is matched against + // (Namespace + "." + Name); nested segments are matched against the Name of + // GetNestedTypes() entries. + if (!TryFindTypeDefinition(mdReader, managedFqName, out TypeDefinitionHandle typeDefHandle)) + return false; + + // Resolve the TypeDef token via the module's TypeDef -> MethodTable map. + int token = MetadataTokens.GetToken(typeDefHandle); + TargetPointer typeDefToMT = loader.GetLookupTables(moduleHandle).TypeDefToMethodTable; + TargetPointer mt = loader.GetModuleLookupMapElement(typeDefToMT, (uint)token, out _); + if (mt == TargetPointer.Null) + return false; + + th = target.Contracts.RuntimeTypeSystem.GetTypeHandle(mt); + typeDef = mdReader.GetTypeDefinition(typeDefHandle); + return true; +} + +bool TryGetTypeHandle(string fqn, out TypeHandle th) +{ + return TryResolveType(fqn, out th, out _, out _); +} + +bool TryGetTypeInfo(string fqn, out Target.TypeInfo info) +{ + if (!TryResolveType(fqn, out TypeHandle th, out MetadataReader mdReader, out TypeDefinition typeDef)) + return false; + + IRuntimeTypeSystem rts = target.Contracts.RuntimeTypeSystem; + Dictionary fields = new(); + + foreach (FieldDefinitionHandle fh in typeDef.GetFields()) + { + FieldDefinition fd = mdReader.GetFieldDefinition(fh); + if ((fd.Attributes & FieldAttributes.Static) != 0) + continue; + + string fieldName = mdReader.GetString(fd.Name); + TargetPointer fdAddr = rts.GetFieldDescByName(th, fieldName); + if (fdAddr == TargetPointer.Null) + continue; + + uint offset = rts.GetFieldDescOffset(fdAddr, fd); + CorElementType et = rts.GetFieldDescType(fdAddr); + // Raw field offset relative to the instance data start. Reference-type + // consumers must add the object header size; value-type consumers (e.g. + // struct entries embedded in arrays) read directly from the slot. + fields[fieldName] = new Target.FieldInfo { Offset = (int)offset, TypeName = MapElementType(et) }; + } + info = new Target.TypeInfo { Fields = fields }; + return true; +} + +bool TryGetStaticFieldAddress(string fqn, string fieldName, out TargetPointer address) +{ + address = TargetPointer.Null; + if (!TryGetFieldDesc(fqn, fieldName, out TargetPointer fdAddr)) + return false; + + IRuntimeTypeSystem rts = target.Contracts.RuntimeTypeSystem; + + // Thread-statics return a per-thread offset, not an absolute address. + if (rts.IsFieldDescThreadStatic(fdAddr)) + return false; + + // Gate on the statics base being allocated for the enclosing class so callers + // cannot dereference a small offset-from-zero when the class has not been + // initialized. + TargetPointer enclosingMT = rts.GetMTOfEnclosingClass(fdAddr); + TypeHandle ctx = rts.GetTypeHandle(enclosingMT); + CorElementType et = rts.GetFieldDescType(fdAddr); + bool isGC = et is CorElementType.Class or CorElementType.ValueType; + TargetPointer @base = isGC + ? rts.GetGCStaticsBasePointer(ctx) + : rts.GetNonGCStaticsBasePointer(ctx); + if (@base == TargetPointer.Null) + return false; + + address = rts.GetFieldDescStaticAddress(fdAddr); + return true; +} + +bool TryGetThreadStaticFieldAddress(string fqn, string fieldName, TargetPointer thread, out TargetPointer address) +{ + address = TargetPointer.Null; + if (!TryGetFieldDesc(fqn, fieldName, out TargetPointer fdAddr)) + return false; + + IRuntimeTypeSystem rts = target.Contracts.RuntimeTypeSystem; + if (!rts.IsFieldDescThreadStatic(fdAddr)) + return false; + + // Gate on the per-thread base being allocated for the enclosing class on this + // thread so callers cannot dereference a small offset-from-zero when this + // thread has not initialized thread-static storage for the type. + TargetPointer enclosingMT = rts.GetMTOfEnclosingClass(fdAddr); + TypeHandle ctx = rts.GetTypeHandle(enclosingMT); + CorElementType et = rts.GetFieldDescType(fdAddr); + bool isGC = et is CorElementType.Class or CorElementType.ValueType; + TargetPointer @base = isGC + ? rts.GetGCThreadStaticsBasePointer(ctx, thread) + : rts.GetNonGCThreadStaticsBasePointer(ctx, thread); + if (@base == TargetPointer.Null) + return false; + + address = rts.GetFieldDescThreadStaticAddress(fdAddr, thread); + return true; +} + +bool TryGetFieldDesc(string fqn, string fieldName, out TargetPointer fdAddr) +{ + if (!TryResolveType(fqn, out TypeHandle th, out _, out _)) + { + fdAddr = TargetPointer.Null; + return false; + } + fdAddr = target.Contracts.RuntimeTypeSystem.GetFieldDescByName(th, fieldName); + return fdAddr != TargetPointer.Null; +} +``` diff --git a/docs/design/datacontracts/SyncBlock.md b/docs/design/datacontracts/SyncBlock.md index ea51bb4503fc44..93d8387c9b258d 100644 --- a/docs/design/datacontracts/SyncBlock.md +++ b/docs/design/datacontracts/SyncBlock.md @@ -18,7 +18,8 @@ bool GetBuiltInComData(TargetPointer syncBlock, out TargetPointer rcw, out Targe ## Version 1 -Data descriptors used: +### Data descriptors used + | Data Descriptor Name | Field | Meaning | | --- | --- | --- | | `SyncTableEntry` | `SyncBlock` | Pointer to the sync block for a sync table entry | @@ -33,7 +34,8 @@ Data descriptors used: | `InteropSyncBlockInfo` | `CCW` | CCW pointer; sentinel value `0x1` means previously had a CCW (treat as null) | | `InteropSyncBlockInfo` | `CCF` | COM class factory pointer; sentinel value `0x1` means previously had a CCF (treat as null) | -Global variables used: +### Global variables used + | Global Name | Type | Purpose | | --- | --- | --- | | `SyncTableEntries` | TargetPointer | Pointer to the sync table entries array | @@ -42,21 +44,17 @@ Global variables used: | `SyncBlockMaskLockRecursionLevel` | uint32 | Mask for extracting recursion level from `SyncBlock.ThinLock` | | `SyncBlockRecursionLevelShift` | uint32 | Shift value for `SyncBlock.ThinLock` recursion level | -### Contract Constants: -| Name | Type | Purpose | Value | +### Managed types used + +| Fully-qualified name | Module | Members read | Purpose | | --- | --- | --- | --- | -| `LockStateName` | string | Field name in `System.Threading.Lock` storing monitor-held state bits. | `_state` | -| `LockOwningThreadIdName` | string | Field name in `System.Threading.Lock` storing owning thread id. | `_owningThreadId` | -| `LockRecursionCountName` | string | Field name in `System.Threading.Lock` storing monitor recursion count. | `_recursionCount` | -| `LockName` | string | Type name used to resolve `System.Threading.Lock`. | `Lock` | -| `LockNamespace` | string | Namespace used to resolve `System.Threading.Lock`. | `System.Threading` | +| `System.Threading.Lock` | `System.Private.CoreLib` | `_state`, `_owningThreadId`, `_recursionCount` | Monitor-held state, owning thread id, and recursion count for fat-lock sync blocks | + +### Contracts used -Contracts used: | Contract Name | | --- | -| `Loader` | -| `RuntimeTypeSystem` | -| `EcmaMetadata` | +| `ManagedTypeSource` | ``` csharp TargetPointer GetSyncBlock(uint index) @@ -97,13 +95,14 @@ bool TryGetLockInfo(TargetPointer syncBlock, out uint owningThreadId, out uint r 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(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(lockObject + (uint)lockType.Fields["_owningThreadId"].Offset); + recursion = target.Read(lockObject + (uint)lockType.Fields["_recursionCount"].Offset); } return monitorHeld; @@ -126,16 +125,6 @@ bool TryGetLockInfo(TargetPointer syncBlock, out uint owningThreadId, out uint r return false; } -private uint ReadUintField(TypeHandle enclosingType, string fieldName, IRuntimeTypeSystem rts, MetadataReader mdReader, TargetPointer dataAddr) -{ - TargetPointer field = rts.GetFieldDescByName(enclosingType, fieldName); - uint token = rts.GetFieldDescMemberDef(field); - FieldDefinitionHandle fieldHandle = (FieldDefinitionHandle)MetadataTokens.Handle((int)token); - FieldDefinition fieldDef = mdReader.GetFieldDefinition(fieldHandle); - uint offset = rts.GetFieldDescOffset(field, fieldDef); - return _target.Read(dataAddr + offset); -} - uint GetAdditionalThreadCount(TargetPointer syncBlock) { // TODO: read conditional weaktable From 11453daa502864db91e4d54f30908379d8e3233e Mon Sep 17 00:00:00 2001 From: Max Charlamb <44248479+max-charlamb@users.noreply.github.com> Date: Wed, 13 May 2026 01:42:57 -0400 Subject: [PATCH 6/7] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- .../Contracts/ManagedTypeSource_1.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ManagedTypeSource_1.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ManagedTypeSource_1.cs index 046dc6c499efd5..9c63e668115924 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ManagedTypeSource_1.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ManagedTypeSource_1.cs @@ -309,7 +309,8 @@ private bool TryFindTypeDefinition( CorElementType.U4 => "uint32", CorElementType.I8 => "int64", CorElementType.U8 => "uint64", - CorElementType.I or CorElementType.U => "nuint", + CorElementType.I => "nint", + CorElementType.U => "nuint", CorElementType.String or CorElementType.Ptr or CorElementType.Byref From 25e00960baa72d7a83e1e84c65229133839d28db Mon Sep 17 00:00:00 2001 From: Max Charlamb Date: Wed, 13 May 2026 13:42:12 -0400 Subject: [PATCH 7/7] Add object data offset to SyncBlock.md ManagedTypeSource reads Field offsets returned by ManagedTypeSource are relative to the start of instance data. Reference-type consumers must skip the object header, matching the convention used in ConditionalWeakTable.md. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- docs/design/datacontracts/SyncBlock.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/design/datacontracts/SyncBlock.md b/docs/design/datacontracts/SyncBlock.md index 93d8387c9b258d..beb673970d9fa3 100644 --- a/docs/design/datacontracts/SyncBlock.md +++ b/docs/design/datacontracts/SyncBlock.md @@ -97,12 +97,12 @@ bool TryGetLockInfo(TargetPointer syncBlock, out uint owningThreadId, out uint r { // Resolve the layout of System.Threading.Lock via ManagedTypeSource. Target.TypeInfo lockType = target.Contracts.ManagedTypeSource.GetTypeInfo("System.Threading.Lock"); - uint state = target.Read(lockObject + (uint)lockType.Fields["_state"].Offset); + uint state = target.Read(lockObject + /* Object data offset */ + (uint)lockType.Fields["_state"].Offset); bool monitorHeld = (state & 1) != 0; if (monitorHeld) { - owningThreadId = target.Read(lockObject + (uint)lockType.Fields["_owningThreadId"].Offset); - recursion = target.Read(lockObject + (uint)lockType.Fields["_recursionCount"].Offset); + owningThreadId = target.Read(lockObject + /* Object data offset */ + (uint)lockType.Fields["_owningThreadId"].Offset); + recursion = target.Read(lockObject + /* Object data offset */ + (uint)lockType.Fields["_recursionCount"].Offset); } return monitorHeld;