diff --git a/docs/design/datacontracts/RuntimeTypeSystem.md b/docs/design/datacontracts/RuntimeTypeSystem.md index f0b5a49382b5f5..e825a7955f631f 100644 --- a/docs/design/datacontracts/RuntimeTypeSystem.md +++ b/docs/design/datacontracts/RuntimeTypeSystem.md @@ -86,11 +86,14 @@ partial interface IRuntimeTypeSystem : IContract public virtual CorElementType GetSignatureCorElementType(TypeHandle typeHandle); // return true if the TypeHandle represents an array, and set the rank to either 0 (if the type is not an array), or the rank number if it is. - public virtual bool IsArray(TypeHandle typeHandle, out uint rank); - public virtual TypeHandle GetTypeParam(TypeHandle typeHandle); - public virtual bool IsGenericVariable(TypeHandle typeHandle, out TargetPointer module, out uint token); - public virtual bool IsFunctionPointer(TypeHandle typeHandle, out ReadOnlySpan retAndArgTypes, out byte callConv); - public virtual TargetPointer GetLoaderModule(TypeHandle typeHandle); + bool IsArray(TypeHandle typeHandle, out uint rank); + TypeHandle GetTypeParam(TypeHandle typeHandle); + TypeHandle GetConstructedType(TypeHandle typeHandle, CorElementType corElementType, int rank, ImmutableArray typeArguments); + TypeHandle GetPrimitiveType(CorElementType typeCode); + bool IsGenericVariable(TypeHandle typeHandle, out TargetPointer module, out uint token); + bool IsFunctionPointer(TypeHandle typeHandle, out ReadOnlySpan retAndArgTypes, out byte callConv); + bool IsPointer(TypeHandle typeHandle); + TargetPointer GetLoaderModule(TypeHandle typeHandle); #endregion TypeHandle inspection APIs } @@ -189,6 +192,16 @@ partial interface IRuntimeTypeSystem : IContract } ``` +### FieldDesc +```csharp +TargetPointer GetMTOfEnclosingClass(TargetPointer fieldDescPointer); +uint GetFieldDescMemberDef(TargetPointer fieldDescPointer); +bool IsFieldDescThreadStatic(TargetPointer fieldDescPointer); +bool IsFieldDescStatic(TargetPointer fieldDescPointer); +uint GetFieldDescType(TargetPointer fieldDescPointer); +uint GetFieldDescOffset(TargetPointer fieldDescPointer, FieldDefinition fieldDef); +``` + ## Version 1 ### TypeHandle @@ -403,6 +416,11 @@ Contracts used: | --- | | `Thread` | +### Contract Constants: +| Name | Type | Purpose | Value | +| --- | --- | --- | --- | +| `TYPE_MASK_OFFSET` | int | The number of bits the type is shifted left in the field desc flags2 | `27` | + ```csharp private readonly Dictionary _methodTables; @@ -717,6 +735,89 @@ Contracts used: throw new ArgumentException(nameof(typeHandle)); } + // helper functions + + private bool GenericInstantiationMatch(TypeHandle genericType, TypeHandle potentialMatch, ImmutableArray typeArguments) + { + ReadOnlySpan instantiation = GetInstantiation(potentialMatch); + if (instantiation.Length != typeArguments.Length) + return false; + + if (GetTypeDefToken(genericType) != GetTypeDefToken(potentialMatch)) + return false; + + if (GetModule(genericType) != GetModule(potentialMatch)) + return false; + + for (int i = 0; i < instantiation.Length; i++) + { + if (!(instantiation[i].Address == typeArguments[i].Address)) + return false; + } + return true; + } + + private bool ArrayPtrMatch(TypeHandle elementType, CorElementType corElementType, int rank, TypeHandle potentialMatch) + { + IsArray(potentialMatch, out uint typeHandleRank); + return GetSignatureCorElementType(potentialMatch) == corElementType && + GetTypeParam(potentialMatch).Address == elementType.Address && + (corElementType == CorElementType.SzArray || corElementType == CorElementType.Byref || + corElementType == CorElementType.Ptr || (rank == typeHandleRank)); + + } + + private bool IsLoaded(TypeHandle typeHandle) + { + if (typeHandle.Address == TargetPointer.Null) + return false; + if (typeHandle.IsTypeDesc()) + { + uint typeAndFlags = _target.Read(typeHandle.TypeDescAddress() + /* TypeDesc::TypeAndFlags offset */); + return (typeAndFlags & (uint)TypeDescFlags.IsNotFullyLoaded) == 0; + } + + MethodTable methodTable = _methodTables[typeHandle.Address]; + uint flags = _target.Read(methodTable.AuxiliaryData + /* AuxiliaryData::Flags offset */); + return (flags & (uint)MethodTableAuxiliaryFlags.IsNotFullyLoaded) == 0; + } + + TypeHandle GetConstructedType(TypeHandle typeHandle, CorElementType corElementType, int rank, ImmutableArray typeArguments) + { + if (typeHandle.Address == TargetPointer.Null) + return new TypeHandle(TargetPointer.Null); + ILoader loaderContract = _target.Contracts.Loader; + TargetPointer loaderModule = GetLoaderModule(typeHandle); + ModuleHandle moduleHandle = loaderContract.GetModuleHandleFromModulePtr(loaderModule); + TypeHandle potentialMatch = new TypeHandle(TargetPointer.Null); + foreach (TargetPointer ptr in loaderContract.GetAvailableTypeParams(moduleHandle)) + { + potentialMatch = GetTypeHandle(ptr); + if (corElementType == CorElementType.GenericInst) + { + if (GenericInstantiationMatch(typeHandle, potentialMatch, typeArguments) && IsLoaded(potentialMatch)) + { + _ = _typeHandles.TryAdd(new TypeKey(typeHandle, corElementType, rank, typeArguments), potentialMatch); + return potentialMatch; + } + } + else if (ArrayPtrMatch(typeHandle, corElementType, rank, potentialMatch) && IsLoaded(potentialMatch)) + { + _ = _typeHandles.TryAdd(new TypeKey(typeHandle, corElementType, rank, typeArguments), potentialMatch); + return potentialMatch; + } + } + return new TypeHandle(TargetPointer.Null); + } + + public TypeHandle GetPrimitiveType(CorElementType typeCode) + { + TargetPointer coreLib = _target.ReadGlobalPointer("CoreLib"); + TargetPointer classes = _target.ReadPointer(coreLib + /* CoreLibBinder::Classes offset */); + TargetPointer typeHandlePtr = _target.ReadPointer(classes + (ulong)typeCode * (ulong)_target.PointerSize); + return GetTypeHandle(typeHandlePtr); + } + public bool IsGenericVariable(TypeHandle typeHandle, out TargetPointer module, out uint token) { module = TargetPointer.Null; @@ -748,7 +849,7 @@ Contracts used: return false; int TypeAndFlags = // Read TypeAndFlags field from TypeDesc contract using address typeHandle.TypeDescAddress() - CorElementType elemType = (CorElementType)(typeDesc.TypeAndFlags & 0xFF); + CorElementType elemType = (CorElementType)(TypeAndFlags & 0xFF); if (elemType != CorElementType.FnPtr) return false; @@ -765,6 +866,16 @@ Contracts used: return true; } + public bool IsPointer(TypeHandle typeHandle) + { + if (!typeHandle.IsTypeDesc()) + return false; + + int TypeAndFlags = // Read TypeAndFlags field from TypeDesc contract using address typeHandle.TypeDescAddress() + CorElementType elemType = (CorElementType)(TypeAndFlags & 0xFF); + return elemType == CorElementType.Ptr; + } + public TargetPointer GetLoaderModule(TypeHandle typeHandle) { if (typeHandle.IsTypeDesc()) @@ -915,6 +1026,12 @@ And the following enumeration definitions { Initialized = 0x0001, IsInitError = 0x0100, + IsNotFullyLoaded = 0x0040, + } + + internal enum TypeDescFlags : uint + { + IsNotFullyLoaded = 0x00001000, } ``` @@ -1532,3 +1649,66 @@ Getting a MethodDesc for a certain slot in a MethodTable return GetMethodDescForEntrypoint(pCode); } ``` + +### FieldDesc + +The version 1 FieldDesc APIs depend on the following data descriptors: +| Data Descriptor Name | Field | Meaning | +| --- | --- | --- | +| `FieldDesc` | `MTOfEnclosingClass` | Pointer to method table of enclosing class | +| `FieldDesc` | `DWord1` | The FD's flags and token | +| `FieldDesc` | `DWord2` | The FD's kind and offset | + +```csharp +internal enum FieldDescFlags1 : uint +{ + TokenMask = 0xffffff, + IsStatic = 0x1000000, + IsThreadStatic = 0x2000000, +} + +internal enum FieldDescFlags2 : uint +{ + TypeMask = 0xf8000000, + OffsetMask = 0x07ffffff, +} + +TargetPointer GetMTOfEnclosingClass(TargetPointer fieldDescPointer) +{ + return target.ReadPointer(fieldDescPointer + /* FieldDesc::MTOfEnclosingClass offset */); +} + +uint GetFieldDescMemberDef(TargetPointer fieldDescPointer) +{ + uint DWord1 = target.Read(fieldDescPointer + /* FieldDesc::DWord1 offset */); + return EcmaMetadataUtils.CreateFieldDef(DWord1 & (uint)FieldDescFlags1.TokenMask); +} + +bool IsFieldDescThreadStatic(TargetPointer fieldDescPointer) +{ + uint DWord1 = target.Read(fieldDescPointer + /* FieldDesc::DWord1 offset */); + return (DWord1 & (uint)FieldDescFlags1.IsThreadStatic) != 0; +} + +bool IsFieldDescStatic(TargetPointer fieldDescPointer) +{ + uint DWord1 = target.Read(fieldDescPointer + /* FieldDesc::DWord1 offset */); + return (DWord1 & (uint)FieldDescFlags1.IsStatic) != 0; +} + +uint GetFieldDescType(TargetPointer fieldDescPointer) +{ + uint DWord2 = target.Read(fieldDescPointer + /* FieldDesc::DWord2 offset */); + return (DWord2 & (uint)FieldDescFlags2.TypeMask) >> 27; +} + +uint GetFieldDescOffset(TargetPointer fieldDescPointer) +{ + uint DWord2 = target.Read(fieldDescPointer + /* FieldDesc::DWord2 offset */); + if (DWord2 == _target.ReadGlobal("FieldOffsetBigRVA")) + { + return (uint)fieldDef.GetRelativeVirtualAddress(); + } + return DWord2 & (uint)FieldDescFlags2.OffsetMask; +} +``` \ No newline at end of file diff --git a/docs/design/datacontracts/SignatureDecoder.md b/docs/design/datacontracts/SignatureDecoder.md new file mode 100644 index 00000000000000..08f41f49917dd3 --- /dev/null +++ b/docs/design/datacontracts/SignatureDecoder.md @@ -0,0 +1,70 @@ +# Contract SignatureDecoder + +This contract encapsulates signature decoding in the cDAC. + +## APIs of contract + +```csharp +TypeHandle DecodeFieldSignature(BlobHandle blobHandle, ModuleHandle moduleHandle, TypeHandle ctx); +``` + +## Version 1 + +In version 1 of the SignatureDecoder contract we take advantage of the System.Reflection.Metadata signature decoding. We implement a SignatureTypeProvider that inherits from System.Reflection.Metadata ISignatureTypeProvider. + +Data descriptors used: +| Data Descriptor Name | Field | Meaning | +| --- | --- | --- | + + +Global variables used: +| Global Name | Type | Purpose | +| --- | --- | --- | + + +Contracts used: +| Contract Name | +| --- | +| RuntimeTypeSystem | +| Loader | +| EcmaMetadata | + +### SignatureTypeProvider +The cDAC implements the ISignatureTypeProvider with TType=TypeHandle. TGenericContext can either be a MethodDescHandle or TypeHandle; MethodDescHandle context is used to look up generic method parameters, and TypeHandle context is used to look up generic type parameters. + +A cDAC SignatureTypeProvider is instantiated over a Module which is used to lookup types. + +The following ISignatureTypeProvider APIs are trivially implemented using RuntimeTypeSystem.GetPrimitiveType and RuntimeTypeSystem.GetConstructedType: + +* GetArrayType - GetConstructedType +* GetByReferenceType - GetConstructedType +* GetFunctionPointerType - Implemented as primitive IntPtr type +* GetGenericInstantiation - GetConstructedType +* GetModifiedType - Returns unmodified type +* GetPinnedType - Returns unpinned type +* GetPointerType - GetConstructedType +* GetPrimitiveType - GetConstructedType +* GetSZArrayType - GetConstructedType + +GetGenericMethodParameter is only supported when TGenericContext=MethodDescHandle and looks up the method parameters from the context using RuntimeTypeSystem.GetGenericMethodInstantiation. + +GetGenericTypeParameter is only supported when TGenericContext=TypeHandle and looks up the type parameters from the context using RuntimeTypeSystem.GetInstantiation. + +GetTypeFromDefinition uses the SignatureTypeProvider's ModuleHandle to lookup the given Token in the Module's TypeDefToMethodTableMap. If a value is not found return null. + +GetTypeFromReference uses the SignatureTypeProvider's ModuleHandle to lookup the given Token in the Module's TypeRefToMethodTableMap. If a value is not found return null.The implementation when the type exists in a different module is incomplete. + +GetTypeFromSpecification is not currently implemented. + + +### APIs +```csharp +TypeHandle ISignatureDecoder.DecodeFieldSignature(BlobHandle blobHandle, ModuleHandle moduleHandle, TypeHandle ctx) +{ + SignatureTypeProvider provider = new(_target, moduleHandle); + MetadataReader mdReader = _target.Contracts.EcmaMetadata.GetMetadata(moduleHandle)!; + BlobReader blobReader = mdReader.GetBlobReader(blobHandle); + SignatureDecoder decoder = new(provider, mdReader, ctx); + return decoder.DecodeFieldSignature(ref blobReader); +} +``` diff --git a/src/coreclr/vm/binder.h b/src/coreclr/vm/binder.h index 8e9369cc85a1b0..ba3cc41aba0e9e 100644 --- a/src/coreclr/vm/binder.h +++ b/src/coreclr/vm/binder.h @@ -28,7 +28,7 @@ struct HardCodedMetaSig #define DEFINE_METASIG_T(body) extern body #define METASIG_BODY(varname, types) HardCodedMetaSig gsig_ ## varname; #include "metasig.h" - +#include "cdacdata.h" // // Use the Binder objects to avoid doing unnecessary name lookup // (esp. in the prejit case) @@ -294,6 +294,7 @@ class CoreLibBinder USHORT m_cFields; static CrstStatic s_SigConvertCrst; + friend struct ::cdac_data; #ifdef _DEBUG @@ -313,6 +314,12 @@ class CoreLibBinder #endif }; +template<> +struct cdac_data +{ + static constexpr size_t Classes = offsetof(CoreLibBinder, m_pClasses); +}; + // // Global bound modules: // diff --git a/src/coreclr/vm/datadescriptor/datadescriptor.inc b/src/coreclr/vm/datadescriptor/datadescriptor.inc index 3844f70514e612..9f057af35b7199 100644 --- a/src/coreclr/vm/datadescriptor/datadescriptor.inc +++ b/src/coreclr/vm/datadescriptor/datadescriptor.inc @@ -333,6 +333,13 @@ CDAC_TYPE_INDETERMINATE(TypeDesc) CDAC_TYPE_FIELD(TypeDesc, /*uint32*/, TypeAndFlags, cdac_data::TypeAndFlags) CDAC_TYPE_END(TypeDesc) +CDAC_TYPE_BEGIN(FieldDesc) +CDAC_TYPE_SIZE(sizeof(FieldDesc)) +CDAC_TYPE_FIELD(FieldDesc, /*uint32*/, DWord1, cdac_data::DWord1) +CDAC_TYPE_FIELD(FieldDesc, /*uint32*/, DWord2, cdac_data::DWord2) +CDAC_TYPE_FIELD(FieldDesc, /*pointer*/, MTOfEnclosingClass, cdac_data::MTOfEnclosingClass) +CDAC_TYPE_END(FieldDesc) + CDAC_TYPE_BEGIN(ParamTypeDesc) CDAC_TYPE_INDETERMINATE(ParamTypeDesc) CDAC_TYPE_FIELD(ParamTypeDesc, /*pointer*/, TypeArg, cdac_data::TypeArg) @@ -912,6 +919,11 @@ CDAC_TYPE_FIELD(InstMethodHashTable, /*pointer*/, VolatileEntryValue, cdac_data< CDAC_TYPE_FIELD(InstMethodHashTable, /*pointer*/, VolatileEntryNextEntry, cdac_data::VolatileEntryNextEntry) CDAC_TYPE_END(InstMethodHashTable) +CDAC_TYPE_BEGIN(CoreLibBinder) +CDAC_TYPE_INDETERMINATE(CoreLibBinder) +CDAC_TYPE_FIELD(CoreLibBinder, /*pointer*/, Classes, cdac_data::Classes) +CDAC_TYPE_END(CoreLibBinder) + // this is an SHash type CDAC_TYPE_BEGIN(DynamicILBlobTable) CDAC_TYPE_SIZE(cdac_data::EntrySize) @@ -999,6 +1011,7 @@ CDAC_GLOBAL(StaticsPointerMask, uintptr_t, DynamicStaticsInfo::STATICSPOINTERMAS CDAC_GLOBAL(PtrArrayOffsetToDataArray, uintptr_t, offsetof(PtrArray, m_Array)) CDAC_GLOBAL(NumberOfTlsOffsetsNotUsedInNoncollectibleArray, uint8, NUMBER_OF_TLSOFFSETS_NOT_USED_IN_NONCOLLECTIBLE_ARRAY) CDAC_GLOBAL(MaxClrNotificationArgs, uint32, MAX_CLR_NOTIFICATION_ARGS) +CDAC_GLOBAL(FieldOffsetBigRVA, uint32, FIELD_OFFSET_BIG_RVA) CDAC_GLOBAL_POINTER(ClrNotificationArguments, &::g_clrNotificationArguments) CDAC_GLOBAL_POINTER(ArrayBoundsZero, cdac_data::ArrayBoundsZero) CDAC_GLOBAL_POINTER(ExceptionMethodTable, &::g_pExceptionClass) @@ -1012,6 +1025,7 @@ CDAC_GLOBAL_POINTER(MiniMetaDataBuffMaxSize, &::g_MiniMetaDataBuffMaxSize) CDAC_GLOBAL_POINTER(DacNotificationFlags, &::g_dacNotificationFlags) CDAC_GLOBAL_POINTER(OffsetOfCurrentThreadInfo, &::g_offsetOfCurrentThreadInfo) CDAC_GLOBAL_POINTER(ThinlockThreadIdDispenser, &::g_pThinLockThreadIdDispenser) +CDAC_GLOBAL_POINTER(CoreLib, &::g_CoreLib) #ifdef TARGET_WINDOWS CDAC_GLOBAL_POINTER(TlsIndexBase, &::_tls_index) #endif // TARGET_WINDOWS @@ -1053,6 +1067,7 @@ CDAC_GLOBAL_CONTRACT(ReJIT, 1) CDAC_GLOBAL_CONTRACT(RuntimeInfo, 1) CDAC_GLOBAL_CONTRACT(RuntimeTypeSystem, 1) CDAC_GLOBAL_CONTRACT(SHash, 1) +CDAC_GLOBAL_CONTRACT(SignatureDecoder, 1) CDAC_GLOBAL_CONTRACT(StackWalk, 1) CDAC_GLOBAL_CONTRACT(StressLog, 2) CDAC_GLOBAL_CONTRACT(Thread, 1) diff --git a/src/coreclr/vm/field.h b/src/coreclr/vm/field.h index ddc5665190327c..2a8894480610f2 100644 --- a/src/coreclr/vm/field.h +++ b/src/coreclr/vm/field.h @@ -10,6 +10,7 @@ #define _FIELD_H_ #include "excep.h" +#include "cdacdata.h" // Temporary values stored in FieldDesc m_dwOffset during loading // The high 5 bits must be zero (because in field.h we steal them for other uses), so we must choose values > 0 @@ -36,17 +37,16 @@ class FieldDesc { friend class MethodTableBuilder; + friend struct ::cdac_data; protected: PTR_MethodTable m_pMTOfEnclosingClass; // This is used to hold the log2 of the field size temporarily during class loading. Yuck. // See also: FieldDesc::InitializeFrom method -#if defined(DACCESS_COMPILE) union { //create a union so I can get the correct offset for ClrDump. unsigned m_dword1; struct { -#endif unsigned m_mb : 24; // 8 bits... @@ -54,24 +54,17 @@ class FieldDesc unsigned m_isThreadLocal : 1; unsigned m_isRVA : 1; unsigned m_prot : 3; -#if defined(DACCESS_COMPILE) }; }; -#endif - -#if defined(DACCESS_COMPILE) union { //create a union so I can get the correct offset for ClrDump unsigned m_dword2; struct { -#endif // Note: this has been as low as 22 bits in the past & seemed to be OK. // we can steal some more bits here if we need them. unsigned m_dwOffset : 27; unsigned m_type : 5; -#if defined(DACCESS_COMPILE) }; }; -#endif #ifdef _DEBUG LPUTF8 m_debugName; @@ -744,5 +737,13 @@ class FieldDesc #endif }; +template<> +struct cdac_data +{ + static constexpr size_t DWord1 = offsetof(FieldDesc, m_dword1); + static constexpr size_t DWord2 = offsetof(FieldDesc, m_dword2); + static constexpr size_t MTOfEnclosingClass = offsetof(FieldDesc, m_pMTOfEnclosingClass); +}; + #endif // _FIELD_H_ 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 8b362ba896881b..3cb33a8e05317c 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/ContractRegistry.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/ContractRegistry.cs @@ -81,4 +81,8 @@ public abstract class ContractRegistry /// Gets an instance of the GC contract for the target. /// public abstract IGC GC { get; } + /// + /// Gets an instance of the SignatureDecoder contract for the target. + /// + public abstract ISignatureDecoder SignatureDecoder { get; } } 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 dacbde91f3dd82..cebe10538db5a4 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 @@ -2,7 +2,9 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; +using System.Reflection.Metadata; using System.Collections.Generic; +using System.Collections.Immutable; namespace Microsoft.Diagnostics.DataContractReader.Contracts; @@ -139,8 +141,11 @@ public interface IRuntimeTypeSystem : IContract // return true if the TypeHandle represents an array, and set the rank to either 0 (if the type is not an array), or the rank number if it is. bool IsArray(TypeHandle typeHandle, out uint rank) => throw new NotImplementedException(); TypeHandle GetTypeParam(TypeHandle typeHandle) => throw new NotImplementedException(); + TypeHandle GetConstructedType(TypeHandle typeHandle, CorElementType corElementType, int rank, ImmutableArray typeArguments) => throw new NotImplementedException(); + TypeHandle GetPrimitiveType(CorElementType typeCode) => throw new NotImplementedException(); bool IsGenericVariable(TypeHandle typeHandle, out TargetPointer module, out uint token) => throw new NotImplementedException(); bool IsFunctionPointer(TypeHandle typeHandle, out ReadOnlySpan retAndArgTypes, out byte callConv) => throw new NotImplementedException(); + bool IsPointer(TypeHandle typeHandle) => throw new NotImplementedException(); // Returns null if the TypeHandle is not a class/struct/generic variable #endregion TypeHandle inspection APIs @@ -194,6 +199,14 @@ public interface IRuntimeTypeSystem : IContract TargetPointer GetGCStressCodeCopy(MethodDescHandle methodDesc) => throw new NotImplementedException(); #endregion MethodDesc inspection APIs + #region FieldDesc inspection APIs + TargetPointer GetMTOfEnclosingClass(TargetPointer fieldDescPointer) => throw new NotImplementedException(); + uint GetFieldDescMemberDef(TargetPointer fieldDescPointer) => throw new NotImplementedException(); + bool IsFieldDescThreadStatic(TargetPointer fieldDescPointer) => throw new NotImplementedException(); + bool IsFieldDescStatic(TargetPointer fieldDescPointer) => throw new NotImplementedException(); + CorElementType GetFieldDescType(TargetPointer fieldDescPointer) => throw new NotImplementedException(); + uint GetFieldDescOffset(TargetPointer fieldDescPointer, FieldDefinition fieldDef) => throw new NotImplementedException(); + #endregion FieldDesc inspection APIs } public struct RuntimeTypeSystem : IRuntimeTypeSystem diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/ISignatureDecoder.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/ISignatureDecoder.cs new file mode 100644 index 00000000000000..5977d736b74d43 --- /dev/null +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/ISignatureDecoder.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 System; +using System.Reflection.Metadata; + +namespace Microsoft.Diagnostics.DataContractReader.Contracts; + +public interface ISignatureDecoder : IContract +{ + static string IContract.Name { get; } = nameof(SignatureDecoder); + TypeHandle DecodeFieldSignature(BlobHandle blobHandle, ModuleHandle moduleHandle, TypeHandle ctx) => throw new NotImplementedException(); +} + +public readonly struct SignatureDecoder : ISignatureDecoder +{ + // Everything throws NotImplementedException +} diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/DataType.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/DataType.cs index d2cf878cb8a6c7..ce6f60c7e3c918 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/DataType.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/DataType.cs @@ -50,6 +50,7 @@ public enum DataType MethodTable, DynamicStaticsInfo, EEClass, + CoreLibBinder, ArrayClass, MethodTableAuxiliaryData, GenericsDictInfo, @@ -57,6 +58,7 @@ public enum DataType ParamTypeDesc, TypeVarTypeDesc, FnPtrTypeDesc, + FieldDesc, DynamicMetadata, StressLog, StressLogModuleDesc, diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Constants.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Constants.cs index 084afe879f99c1..3d6834e30809e6 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Constants.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Constants.cs @@ -50,6 +50,7 @@ public static class Globals public const string SyncTableEntries = nameof(SyncTableEntries); public const string ArrayBoundsZero = nameof(ArrayBoundsZero); + public const string CoreLib = nameof(CoreLib); public const string SizeOfGenericModeBlock = nameof(SizeOfGenericModeBlock); public const string MethodDescTokenRemainderBitCount = nameof(MethodDescTokenRemainderBitCount); @@ -63,6 +64,7 @@ public static class Globals public const string NumberOfTlsOffsetsNotUsedInNoncollectibleArray = nameof(NumberOfTlsOffsetsNotUsedInNoncollectibleArray); public const string MaxClrNotificationArgs = nameof(MaxClrNotificationArgs); public const string ClrNotificationArguments = nameof(ClrNotificationArguments); + public const string FieldOffsetBigRVA = nameof(FieldOffsetBigRVA); public const string PlatformMetadata = nameof(PlatformMetadata); public const string ProfilerControlBlock = nameof(ProfilerControlBlock); 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 995d2efdddec50..c5b011aaeae35b 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 @@ -7,11 +7,14 @@ using System.Reflection.Metadata.Ecma335; using Microsoft.Diagnostics.DataContractReader.RuntimeTypeSystemHelpers; using Microsoft.Diagnostics.DataContractReader.Data; +using System.Reflection.Metadata; +using System.Collections.Immutable; namespace Microsoft.Diagnostics.DataContractReader.Contracts; internal partial struct RuntimeTypeSystem_1 : IRuntimeTypeSystem { + private const int TYPE_MASK_OFFSET = 27; // offset of type in field desc flags2 private readonly Target _target; private readonly TargetPointer _freeObjectMethodTablePointer; private readonly ulong _methodDescAlignment; @@ -22,6 +25,7 @@ internal partial struct RuntimeTypeSystem_1 : IRuntimeTypeSystem // If we need to invalidate our view of memory, we should clear this dictionary. private readonly Dictionary _methodTables = new(); private readonly Dictionary _methodDescs = new(); + private readonly Dictionary _typeHandles = new(); internal struct MethodTable { @@ -54,6 +58,45 @@ internal MethodTable(Data.MethodTable data) internal bool IsCanonMT => MethodTableFlags_1.GetEEClassOrCanonMTBits(EEClassOrCanonMT) == MethodTableFlags_1.EEClassOrCanonMTBits.EEClass; } + private readonly struct TypeKey : IEquatable + { + public TypeKey(TypeHandle typeHandle, CorElementType elementType, int rank, ImmutableArray typeArgs) + { + TypeHandle = typeHandle; + ElementType = elementType; + Rank = rank; + TypeArgs = typeArgs; + } + public TypeHandle TypeHandle { get; } + public CorElementType ElementType { get; } + public int Rank { get; } + public ImmutableArray TypeArgs { get; } + + public bool Equals(TypeKey other) + { + if (ElementType != other.ElementType || Rank != other.Rank || TypeArgs.Length != other.TypeArgs.Length) + return false; + for (int i = 0; i < TypeArgs.Length; i++) + { + if (!TypeArgs[i].Equals(other.TypeArgs[i])) + return false; + } + return true; + } + + public override bool Equals(object? obj) => obj is TypeKey other && Equals(other); + + public override int GetHashCode() + { + int hash = HashCode.Combine(TypeHandle.GetHashCode(), (int)ElementType, Rank); + foreach (TypeHandle th in TypeArgs) + { + hash = HashCode.Combine(hash, th.GetHashCode()); + } + return hash; + } + } + // Low order bits of TypeHandle address. // If the low bits contain a 2, then it is a TypeDesc [Flags] @@ -102,6 +145,25 @@ internal enum MethodTableAuxiliaryFlags : uint { Initialized = 0x0001, IsInitError = 0x0100, + IsNotFullyLoaded = 0x0040, + } + + internal enum TypeDescFlags : uint + { + IsNotFullyLoaded = 0x00001000, + } + + internal enum FieldDescFlags1 : uint + { + TokenMask = 0xffffff, + IsStatic = 0x1000000, + IsThreadStatic = 0x2000000, + } + + internal enum FieldDescFlags2 : uint + { + TypeMask = 0xf8000000, + OffsetMask = 0x07ffffff, } internal struct MethodDesc @@ -656,6 +718,89 @@ public TypeHandle GetTypeParam(TypeHandle typeHandle) throw new ArgumentException(nameof(typeHandle)); } + private bool GenericInstantiationMatch(TypeHandle genericType, TypeHandle potentialMatch, ImmutableArray typeArguments) + { + ReadOnlySpan instantiation = GetInstantiation(potentialMatch); + if (instantiation.Length != typeArguments.Length) + return false; + + if (GetTypeDefToken(genericType) != GetTypeDefToken(potentialMatch)) + return false; + + if (GetModule(genericType) != GetModule(potentialMatch)) + return false; + + for (int i = 0; i < instantiation.Length; i++) + { + if (!(instantiation[i].Address == typeArguments[i].Address)) + return false; + } + return true; + } + + private bool ArrayPtrMatch(TypeHandle elementType, CorElementType corElementType, int rank, TypeHandle potentialMatch) + { + IsArray(potentialMatch, out uint typeHandleRank); + return GetSignatureCorElementType(potentialMatch) == corElementType && + GetTypeParam(potentialMatch).Address == elementType.Address && + (corElementType == CorElementType.SzArray || corElementType == CorElementType.Byref || + corElementType == CorElementType.Ptr || (rank == typeHandleRank)); + + } + + private bool IsLoaded(TypeHandle typeHandle) + { + if (typeHandle.Address == TargetPointer.Null) + return false; + if (typeHandle.IsTypeDesc()) + { + Data.TypeDesc typeDesc = _target.ProcessedData.GetOrAdd(typeHandle.TypeDescAddress()); + return (typeDesc.TypeAndFlags & (uint)TypeDescFlags.IsNotFullyLoaded) == 0; // IsUnloaded + } + + MethodTable methodTable = _methodTables[typeHandle.Address]; + Data.MethodTableAuxiliaryData auxData = _target.ProcessedData.GetOrAdd(methodTable.AuxiliaryData); + return (auxData.Flags & (uint)MethodTableAuxiliaryFlags.IsNotFullyLoaded) == 0; // IsUnloaded + } + + TypeHandle IRuntimeTypeSystem.GetConstructedType(TypeHandle typeHandle, CorElementType corElementType, int rank, ImmutableArray typeArguments) + { + if (typeHandle.Address == TargetPointer.Null) + return new TypeHandle(TargetPointer.Null); + if (_typeHandles.TryGetValue(new TypeKey(typeHandle, corElementType, rank, typeArguments), out TypeHandle existing)) + return existing; + ILoader loaderContract = _target.Contracts.Loader; + TargetPointer loaderModule = GetLoaderModule(typeHandle); + ModuleHandle moduleHandle = loaderContract.GetModuleHandleFromModulePtr(loaderModule); + TypeHandle potentialMatch; + foreach (TargetPointer ptr in loaderContract.GetAvailableTypeParams(moduleHandle)) + { + potentialMatch = GetTypeHandle(ptr); + if (corElementType == CorElementType.GenericInst) + { + if (GenericInstantiationMatch(typeHandle, potentialMatch, typeArguments) && IsLoaded(potentialMatch)) + { + _ = _typeHandles.TryAdd(new TypeKey(typeHandle, corElementType, rank, typeArguments), potentialMatch); + return potentialMatch; + } + } + else if (ArrayPtrMatch(typeHandle, corElementType, rank, potentialMatch) && IsLoaded(potentialMatch)) + { + _ = _typeHandles.TryAdd(new TypeKey(typeHandle, corElementType, rank, typeArguments), potentialMatch); + return potentialMatch; + } + } + return new TypeHandle(TargetPointer.Null); + } + + TypeHandle IRuntimeTypeSystem.GetPrimitiveType(CorElementType typeCode) + { + TargetPointer coreLib = _target.ReadGlobalPointer(Constants.Globals.CoreLib); + CoreLibBinder coreLibData = _target.ProcessedData.GetOrAdd(coreLib); + TargetPointer typeHandlePtr = _target.ReadPointer(coreLibData.Classes + (ulong)typeCode * (ulong)_target.PointerSize); + return GetTypeHandle(typeHandlePtr); + } + public bool IsGenericVariable(TypeHandle typeHandle, out TargetPointer module, out uint token) { module = TargetPointer.Null; @@ -697,6 +842,16 @@ public bool IsFunctionPointer(TypeHandle typeHandle, out ReadOnlySpan(typeHandle.TypeDescAddress()); + CorElementType elemType = (CorElementType)(typeDesc.TypeAndFlags & 0xFF); + return elemType == CorElementType.Ptr; + } + public TargetPointer GetLoaderModule(TypeHandle typeHandle) { if (typeHandle.IsTypeDesc()) @@ -1003,7 +1158,6 @@ private bool HasMethodInstantiation(MethodDesc md) return md.Classification == MethodClassification.Instantiated && AsInstantiatedMethodDesc(md).HasMethodInstantiation; } - private bool IsGenericMethodDefinition(MethodDesc md) { return md.Classification == MethodClassification.Instantiated && AsInstantiatedMethodDesc(md).IsGenericMethodDefinition; @@ -1317,4 +1471,44 @@ private bool SlotIsVtableSlot(TargetPointer methodTablePointer, uint slot) TypeHandle typeHandle = GetTypeHandle(methodTablePointer); return slot < GetNumVtableSlots(typeHandle); } + TargetPointer IRuntimeTypeSystem.GetMTOfEnclosingClass(TargetPointer fieldDescPointer) + { + Data.FieldDesc fieldDesc = _target.ProcessedData.GetOrAdd(fieldDescPointer); + return fieldDesc.MTOfEnclosingClass; + } + + uint IRuntimeTypeSystem.GetFieldDescMemberDef(TargetPointer fieldDescPointer) + { + Data.FieldDesc fieldDesc = _target.ProcessedData.GetOrAdd(fieldDescPointer); + return EcmaMetadataUtils.CreateFieldDef(fieldDesc.DWord1 & (uint)FieldDescFlags1.TokenMask); + } + + bool IRuntimeTypeSystem.IsFieldDescThreadStatic(TargetPointer fieldDescPointer) + { + Data.FieldDesc fieldDesc = _target.ProcessedData.GetOrAdd(fieldDescPointer); + return (fieldDesc.DWord1 & (uint)FieldDescFlags1.IsThreadStatic) != 0; + } + + bool IRuntimeTypeSystem.IsFieldDescStatic(TargetPointer fieldDescPointer) + { + Data.FieldDesc fieldDesc = _target.ProcessedData.GetOrAdd(fieldDescPointer); + return (fieldDesc.DWord1 & (uint)FieldDescFlags1.IsStatic) != 0; + } + + CorElementType IRuntimeTypeSystem.GetFieldDescType(TargetPointer fieldDescPointer) + { + Data.FieldDesc fieldDesc = _target.ProcessedData.GetOrAdd(fieldDescPointer); + // 27 is the number of bits that the type is shifted left. if you change the enum, please change this too. + return (CorElementType)((fieldDesc.DWord2 & (uint)FieldDescFlags2.TypeMask) >> TYPE_MASK_OFFSET); + } + + uint IRuntimeTypeSystem.GetFieldDescOffset(TargetPointer fieldDescPointer, FieldDefinition fieldDef) + { + Data.FieldDesc fieldDesc = _target.ProcessedData.GetOrAdd(fieldDescPointer); + if (fieldDesc.DWord2 == _target.ReadGlobal(Constants.Globals.FieldOffsetBigRVA)) + { + return (uint)fieldDef.GetRelativeVirtualAddress(); + } + return fieldDesc.DWord2 & (uint)FieldDescFlags2.OffsetMask; + } } diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Signature/SignatureDecoderFactory.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Signature/SignatureDecoderFactory.cs new file mode 100644 index 00000000000000..9715fc7de8adb9 --- /dev/null +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Signature/SignatureDecoderFactory.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 System; + +namespace Microsoft.Diagnostics.DataContractReader.Contracts; + +public sealed class SignatureDecoderFactory : IContractFactory +{ + ISignatureDecoder IContractFactory.CreateContract(Target target, int version) + { + return version switch + { + 1 => new SignatureDecoder_1(target), + _ => default(SignatureDecoder), + }; + } +} diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Signature/SignatureDecoder_1.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Signature/SignatureDecoder_1.cs new file mode 100644 index 00000000000000..4ca665fab31915 --- /dev/null +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Signature/SignatureDecoder_1.cs @@ -0,0 +1,62 @@ +// 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.Reflection.Metadata; +using System.Reflection.Metadata.Ecma335; +using Microsoft.Diagnostics.DataContractReader.SignatureHelpers; + +namespace Microsoft.Diagnostics.DataContractReader.Contracts; + +/* NOTE: some elements of SignatureTypeProvider remain unimplemented or minimally implemented + * as they are not needed for the current usage of ISignatureDecoder. + * GetModifiedType and GetPinnedType ignore pinning and custom modifiers. + * GetTypeFromReference does not look up the type in another module. + * GetTypeFromSpecification is unimplemented. + * These can be completed as needed. + */ + +internal sealed class SignatureDecoder_1 : ISignatureDecoder +{ + private readonly Target _target; + private readonly Dictionary> _thProviders = []; + private readonly Dictionary> _mdhProviders = []; + + internal SignatureDecoder_1(Target target) + { + _target = target; + } + + private SignatureTypeProvider GetTypeHandleProvider(ModuleHandle moduleHandle) + { + if (_thProviders.TryGetValue(moduleHandle, out SignatureTypeProvider? thProvider)) + { + return thProvider; + } + + SignatureTypeProvider newProvider = new(_target, moduleHandle); + _thProviders[moduleHandle] = newProvider; + return newProvider; + } + + private SignatureTypeProvider GetMethodDescHandleProvider(ModuleHandle moduleHandle) + { + if (_mdhProviders.TryGetValue(moduleHandle, out SignatureTypeProvider? mdhProvider)) + { + return mdhProvider; + } + SignatureTypeProvider newProvider = new(_target, moduleHandle); + _mdhProviders[moduleHandle] = newProvider; + return newProvider; + } + + TypeHandle ISignatureDecoder.DecodeFieldSignature(BlobHandle blobHandle, ModuleHandle moduleHandle, TypeHandle ctx) + { + SignatureTypeProvider provider = GetTypeHandleProvider(moduleHandle); + MetadataReader mdReader = _target.Contracts.EcmaMetadata.GetMetadata(moduleHandle)!; + BlobReader blobReader = mdReader.GetBlobReader(blobHandle); + SignatureDecoder decoder = new(provider, mdReader, ctx); + return decoder.DecodeFieldSignature(ref blobReader); + } +} diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Signature/SignatureTypeProvider.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Signature/SignatureTypeProvider.cs new file mode 100644 index 00000000000000..82672504975f49 --- /dev/null +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Signature/SignatureTypeProvider.cs @@ -0,0 +1,92 @@ +// 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.Ecma335; +using System.Reflection.Metadata; +using Microsoft.Diagnostics.DataContractReader.Data; +using System; +using System.Collections.Immutable; +using Microsoft.Diagnostics.DataContractReader.Contracts; + +namespace Microsoft.Diagnostics.DataContractReader.SignatureHelpers; + +public class SignatureTypeProvider : ISignatureTypeProvider +{ + private readonly Target _target; + private readonly Contracts.ModuleHandle _moduleHandle; + private readonly Contracts.ILoader _loader; + private readonly Contracts.IRuntimeTypeSystem _runtimeTypeSystem; + + public SignatureTypeProvider(Target target, Contracts.ModuleHandle moduleHandle) + { + _target = target; + _moduleHandle = moduleHandle; + _loader = target.Contracts.Loader; + _runtimeTypeSystem = target.Contracts.RuntimeTypeSystem; + } + + public TypeHandle GetArrayType(TypeHandle elementType, ArrayShape shape) + => _runtimeTypeSystem.GetConstructedType(elementType, CorElementType.Array, shape.Rank, []); + + public TypeHandle GetByReferenceType(TypeHandle elementType) + => _runtimeTypeSystem.GetConstructedType(elementType, CorElementType.Byref, 0, []); + + public TypeHandle GetFunctionPointerType(MethodSignature signature) + => GetPrimitiveType(PrimitiveTypeCode.IntPtr); + + public TypeHandle GetGenericInstantiation(TypeHandle genericType, ImmutableArray typeArguments) + => _runtimeTypeSystem.GetConstructedType(genericType, CorElementType.GenericInst, 0, typeArguments); + + public TypeHandle GetGenericMethodParameter(T context, int index) + { + if (typeof(T) == typeof(MethodDescHandle)) + { + MethodDescHandle methodContext = (MethodDescHandle)(object)context!; + return _runtimeTypeSystem.GetGenericMethodInstantiation(methodContext)[index]; + } + throw new NotSupportedException(); + } + public TypeHandle GetGenericTypeParameter(T context, int index) + { + TypeHandle typeContext; + if (typeof(T) == typeof(TypeHandle)) + { + typeContext = (TypeHandle)(object)context!; + return _runtimeTypeSystem.GetInstantiation(typeContext)[index]; + } + throw new NotImplementedException(); + } + public TypeHandle GetModifiedType(TypeHandle modifier, TypeHandle unmodifiedType, bool isRequired) + => unmodifiedType; + + public TypeHandle GetPinnedType(TypeHandle elementType) + => elementType; + + public TypeHandle GetPointerType(TypeHandle elementType) + => _runtimeTypeSystem.GetConstructedType(elementType, CorElementType.Ptr, 0, []); + + public TypeHandle GetPrimitiveType(PrimitiveTypeCode typeCode) + => _runtimeTypeSystem.GetPrimitiveType((CorElementType)typeCode); + + public TypeHandle GetSZArrayType(TypeHandle elementType) + => _runtimeTypeSystem.GetConstructedType(elementType, CorElementType.SzArray, 1, []); + + public TypeHandle GetTypeFromDefinition(MetadataReader reader, TypeDefinitionHandle handle, byte rawTypeKind) + { + int token = MetadataTokens.GetToken((EntityHandle)handle); + TargetPointer typeDefToMethodTable = _loader.GetLookupTables(_moduleHandle).TypeDefToMethodTable; + TargetPointer typeHandlePtr = _loader.GetModuleLookupMapElement(typeDefToMethodTable, (uint)token, out _); + return typeHandlePtr == TargetPointer.Null ? new TypeHandle(TargetPointer.Null) : _runtimeTypeSystem.GetTypeHandle(typeHandlePtr); + } + + public TypeHandle GetTypeFromReference(MetadataReader reader, TypeReferenceHandle handle, byte rawTypeKind) + { + int token = MetadataTokens.GetToken((EntityHandle)handle); + TargetPointer typeRefToMethodTable = _loader.GetLookupTables(_moduleHandle).TypeRefToMethodTable; + TargetPointer typeHandlePtr = _loader.GetModuleLookupMapElement(typeRefToMethodTable, (uint)token, out _); + return typeHandlePtr == TargetPointer.Null ? new TypeHandle(TargetPointer.Null) : _runtimeTypeSystem.GetTypeHandle(typeHandlePtr); + } + + public TypeHandle GetTypeFromSpecification(MetadataReader reader, T context, TypeSpecificationHandle handle, byte rawTypeKind) + => throw new NotImplementedException(); +} diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/CoreLibBinder.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/CoreLibBinder.cs new file mode 100644 index 00000000000000..49e6155d680cfa --- /dev/null +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/CoreLibBinder.cs @@ -0,0 +1,19 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; + +namespace Microsoft.Diagnostics.DataContractReader.Data; + +internal sealed class CoreLibBinder : IData +{ + static CoreLibBinder IData.Create(Target target, TargetPointer address) => new CoreLibBinder(target, address); + public CoreLibBinder(Target target, TargetPointer address) + { + Target.TypeInfo type = target.GetTypeInfo(DataType.CoreLibBinder); + + Classes = target.ReadPointer(address + (ulong)type.Fields[nameof(Classes)].Offset); + } + + public TargetPointer Classes { get; init; } +} diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/FieldDesc.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/FieldDesc.cs new file mode 100644 index 00000000000000..6ce2f4d2227219 --- /dev/null +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/FieldDesc.cs @@ -0,0 +1,20 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.Diagnostics.DataContractReader.Data; + +internal sealed class FieldDesc : IData +{ + static FieldDesc IData.Create(Target target, TargetPointer address) => new FieldDesc(target, address); + public FieldDesc(Target target, TargetPointer address) + { + Target.TypeInfo type = target.GetTypeInfo(DataType.FieldDesc); + DWord1 = target.Read(address + (ulong)type.Fields[nameof(DWord1)].Offset); + DWord2 = target.Read(address + (ulong)type.Fields[nameof(DWord2)].Offset); + MTOfEnclosingClass = target.ReadPointer(address + (ulong)type.Fields[nameof(MTOfEnclosingClass)].Offset); + } + + public uint DWord1 { get; init; } + public uint DWord2 { get; init; } + public TargetPointer MTOfEnclosingClass { get; init; } +} diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/EcmaMetadataUtils.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/EcmaMetadataUtils.cs index f542da0e956ec0..e49be30507f531 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/EcmaMetadataUtils.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/EcmaMetadataUtils.cs @@ -18,7 +18,8 @@ internal static class EcmaMetadataUtils // Metadata table index is the most significant byte of the 4-byte token public enum TokenType : uint { - mdtMethodDef = 0x06 << 24 + mdtMethodDef = 0x06 << 24, + mdtFieldDef = 0x04 << 24 } public static uint CreateMethodDef(uint tokenParts) @@ -26,4 +27,10 @@ public static uint CreateMethodDef(uint tokenParts) Debug.Assert((tokenParts & 0xff000000) == 0, $"Token type should not be set in {nameof(tokenParts)}"); return (uint)TokenType.mdtMethodDef | tokenParts; } + + public static uint CreateFieldDef(uint tokenParts) + { + Debug.Assert((tokenParts & 0xff000000) == 0, $"Token type should not be set in {nameof(tokenParts)}"); + return (uint)TokenType.mdtFieldDef | tokenParts; + } } diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader/CachingContractRegistry.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader/CachingContractRegistry.cs index b48204a16f23de..8ae98b1896c19b 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader/CachingContractRegistry.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader/CachingContractRegistry.cs @@ -44,6 +44,7 @@ public CachingContractRegistry(Target target, TryGetContractVersionDelegate tryG [typeof(IDebugInfo)] = new DebugInfoFactory(), [typeof(ISHash)] = new SHashFactory(), [typeof(IGC)] = new GCFactory(), + [typeof(ISignatureDecoder)] = new SignatureDecoderFactory(), }; configureFactories?.Invoke(_factories); } @@ -66,6 +67,7 @@ public CachingContractRegistry(Target target, TryGetContractVersionDelegate tryG public override IDebugInfo DebugInfo => GetContract(); public override ISHash SHash => GetContract(); public override IGC GC => GetContract(); + public override ISignatureDecoder SignatureDecoder => GetContract(); private TContract GetContract() where TContract : IContract { diff --git a/src/native/managed/cdac/mscordaccore_universal/Legacy/ISOSDacInterface.cs b/src/native/managed/cdac/mscordaccore_universal/Legacy/ISOSDacInterface.cs index 067c494973b8ba..ac2d4d71b03ca6 100644 --- a/src/native/managed/cdac/mscordaccore_universal/Legacy/ISOSDacInterface.cs +++ b/src/native/managed/cdac/mscordaccore_universal/Legacy/ISOSDacInterface.cs @@ -4,6 +4,7 @@ using System; using System.Runtime.InteropServices; using System.Runtime.InteropServices.Marshalling; +using CorElementType = Microsoft.Diagnostics.DataContractReader.Contracts.CorElementType; namespace Microsoft.Diagnostics.DataContractReader.Legacy; @@ -348,6 +349,22 @@ internal unsafe partial interface ISOSEnum int GetCount(uint* pCount); } +internal struct DacpFieldDescData +{ + public CorElementType Type; + public CorElementType sigType; // ELEMENT_TYPE_XXX from signature. We need this to display pretty name for String in minidump's case + public ClrDataAddress MTOfType; // NULL if Type is not loaded + public ClrDataAddress ModuleOfType; + public uint TokenOfType; + public uint mb; + public ClrDataAddress MTOfEnclosingClass; + public uint dwOffset; + public int bIsThreadLocal; + public int bIsContextLocal; + public int bIsStatic; + public ClrDataAddress NextField; +}; + [GeneratedComInterface] [Guid("436f00f2-b42a-4b9f-870c-e73db66ae930")] internal unsafe partial interface ISOSDacInterface @@ -457,7 +474,7 @@ internal unsafe partial interface ISOSDacInterface // FieldDesc [PreserveSig] - int GetFieldDescData(ClrDataAddress fieldDesc, /*struct DacpFieldDescData*/ void* data); + int GetFieldDescData(ClrDataAddress fieldDesc, DacpFieldDescData* data); // Frames [PreserveSig] diff --git a/src/native/managed/cdac/mscordaccore_universal/Legacy/SOSDacImpl.cs b/src/native/managed/cdac/mscordaccore_universal/Legacy/SOSDacImpl.cs index 157406337bbd22..623b62981dc4c3 100644 --- a/src/native/managed/cdac/mscordaccore_universal/Legacy/SOSDacImpl.cs +++ b/src/native/managed/cdac/mscordaccore_universal/Legacy/SOSDacImpl.cs @@ -13,6 +13,8 @@ using Microsoft.Diagnostics.DataContractReader.Contracts; using Microsoft.Diagnostics.DataContractReader.Contracts.Extensions; using Microsoft.Diagnostics.DataContractReader.Contracts.StackWalkHelpers; +using System.Reflection.Metadata; +using System.Reflection.Metadata.Ecma335; namespace Microsoft.Diagnostics.DataContractReader.Legacy; @@ -753,8 +755,136 @@ int ISOSDacInterface.GetFailedAssemblyLocation(ClrDataAddress assembly, uint cou #endif return hr; } - int ISOSDacInterface.GetFieldDescData(ClrDataAddress fieldDesc, void* data) - => _legacyImpl is not null ? _legacyImpl.GetFieldDescData(fieldDesc, data) : HResults.E_NOTIMPL; + int ISOSDacInterface.GetFieldDescData(ClrDataAddress fieldDesc, DacpFieldDescData* data) + { + int hr = HResults.S_OK; + try + { + if (fieldDesc == 0 || data == null) + throw new ArgumentException(); + + IRuntimeTypeSystem rtsContract = _target.Contracts.RuntimeTypeSystem; + IEcmaMetadata ecmaMetadataContract = _target.Contracts.EcmaMetadata; + ISignatureDecoder signatureDecoder = _target.Contracts.SignatureDecoder; + + TargetPointer fieldDescTargetPtr = fieldDesc.ToTargetPointer(_target); + CorElementType fieldDescType = rtsContract.GetFieldDescType(fieldDescTargetPtr); + data->Type = fieldDescType; + data->sigType = fieldDescType; + + uint token = rtsContract.GetFieldDescMemberDef(fieldDescTargetPtr); + FieldDefinitionHandle fieldHandle = (FieldDefinitionHandle)MetadataTokens.Handle((int)token); + + TargetPointer enclosingMT = rtsContract.GetMTOfEnclosingClass(fieldDescTargetPtr); + TypeHandle ctx = rtsContract.GetTypeHandle(enclosingMT); + TargetPointer modulePtr = rtsContract.GetModule(ctx); + Contracts.ModuleHandle moduleHandle = _target.Contracts.Loader.GetModuleHandleFromModulePtr(modulePtr); + MetadataReader mdReader = ecmaMetadataContract.GetMetadata(moduleHandle)!; + FieldDefinition fieldDef = mdReader.GetFieldDefinition(fieldHandle); + try + { + // try to completely decode the signature + TypeHandle foundTypeHandle = signatureDecoder.DecodeFieldSignature(fieldDef.Signature, moduleHandle, ctx); + + // get the MT of the type + // This is an implementation detail of the DAC that we replicate here to get method tables for non-MT types + // that we can return to SOS for pretty-printing. + // In the future we may want to return a TypeHandle instead of a MethodTable, and modify SOS to do more complete pretty-printing. + // DAC equivalent: src/coreclr/vm/typehandle.inl TypeHandle::GetMethodTable + if (rtsContract.IsFunctionPointer(foundTypeHandle, out _, out _) || rtsContract.IsPointer(foundTypeHandle)) + data->MTOfType = rtsContract.GetPrimitiveType(CorElementType.U).Address.ToClrDataAddress(_target); + // array MTs + else if (rtsContract.IsArray(foundTypeHandle, out _)) + data->MTOfType = foundTypeHandle.Address.ToClrDataAddress(_target); + else + { + try + { + // value typedescs + TypeHandle paramTypeHandle = rtsContract.GetTypeParam(foundTypeHandle); + data->MTOfType = paramTypeHandle.Address.ToClrDataAddress(_target); + } + catch (ArgumentException) + { + // non-array MTs + data->MTOfType = foundTypeHandle.Address.ToClrDataAddress(_target); + } + } + } + catch (VirtualReadException) + { + // if we can't find the MT (e.g in a minidump) + data->MTOfType = 0; + } + + // partial decoding of signature + BlobReader blobReader = mdReader.GetBlobReader(fieldDef.Signature); + SignatureHeader header = blobReader.ReadSignatureHeader(); + // read the header byte and check for correctness + if (header.Kind != SignatureKind.Field) + throw new BadImageFormatException(); + // read the top-level type + CorElementType typeCode; + EntityHandle entityHandle; + // in a loop, read custom modifiers until we get to the underlying type + do + { + typeCode = (CorElementType)blobReader.ReadByte(); + entityHandle = blobReader.ReadTypeHandle(); // consume the type + } while (typeCode is CorElementType.CModReqd or CorElementType.CModOpt); // eat custom modifiers + + if (typeCode is CorElementType.Class or CorElementType.ValueType) + { + // if the typecode is class or value, we have been able to read the token that follows in the sig + data->TokenOfType = (uint)MetadataTokens.GetToken(entityHandle); + } + else + { + // otherwise we have not found the token here, but we can encode the underlying type in sigType + data->TokenOfType = (uint)CorTokenType.mdtTypeDef; + if (data->MTOfType == 0) + data->sigType = typeCode; + } + + data->ModuleOfType = modulePtr.ToClrDataAddress(_target); + data->mb = token; + data->MTOfEnclosingClass = ctx.Address.ToClrDataAddress(_target); + data->dwOffset = rtsContract.GetFieldDescOffset(fieldDescTargetPtr, fieldDef); + data->bIsThreadLocal = rtsContract.IsFieldDescThreadStatic(fieldDescTargetPtr) ? 1 : 0; + data->bIsContextLocal = 0; + data->bIsStatic = rtsContract.IsFieldDescStatic(fieldDescTargetPtr) ? 1 : 0; + data->NextField = fieldDescTargetPtr + _target.GetTypeInfo(DataType.FieldDesc).Size!.Value; + } + catch (System.Exception ex) + { + hr = ex.HResult; + } +#if DEBUG + if (_legacyImpl is not null) + { + DacpFieldDescData dataLocal = default; + int hrLocal = _legacyImpl.GetFieldDescData(fieldDesc, &dataLocal); + Debug.Assert(hrLocal == hr, $"cDAC: {hr:x}, DAC: {hrLocal:x}"); + if (hr == HResults.S_OK) + { + Debug.Assert(data->Type == dataLocal.Type, $"cDAC: {data->Type}, DAC: {dataLocal.Type}"); + Debug.Assert(data->sigType == dataLocal.sigType, $"cDAC: {data->sigType}, DAC: {dataLocal.sigType}"); + Debug.Assert(data->TokenOfType == dataLocal.TokenOfType, $"cDAC: {data->TokenOfType:x}, DAC: {dataLocal.TokenOfType:x}"); + Debug.Assert(data->MTOfType == dataLocal.MTOfType, $"cDAC: {data->MTOfType:x}, DAC: {dataLocal.MTOfType:x}"); + Debug.Assert(data->ModuleOfType == dataLocal.ModuleOfType, $"cDAC: {data->ModuleOfType:x}, DAC: {dataLocal.ModuleOfType:x}"); + Debug.Assert(data->mb == dataLocal.mb, $"cDAC: {data->mb:x}, DAC: {dataLocal.mb:x}"); + Debug.Assert(data->MTOfEnclosingClass == dataLocal.MTOfEnclosingClass, $"cDAC: {data->MTOfEnclosingClass:x}, DAC: {dataLocal.MTOfEnclosingClass:x}"); + Debug.Assert(data->dwOffset == dataLocal.dwOffset, $"cDAC: {data->dwOffset:x}, DAC: {dataLocal.dwOffset:x}"); + Debug.Assert(data->bIsThreadLocal == dataLocal.bIsThreadLocal, $"cDAC: {data->bIsThreadLocal}, DAC: {dataLocal.bIsThreadLocal}"); + Debug.Assert(data->bIsContextLocal == dataLocal.bIsContextLocal, $"cDAC: {data->bIsContextLocal}, DAC: {dataLocal.bIsContextLocal}"); + Debug.Assert(data->bIsStatic == dataLocal.bIsStatic, $"cDAC: {data->bIsStatic}, DAC: {dataLocal.bIsStatic}"); + Debug.Assert(data->NextField == dataLocal.NextField, $"cDAC: {data->NextField:x}, DAC: {dataLocal.NextField:x}"); + } + } +#endif + return hr; + } + int ISOSDacInterface.GetFrameName(ClrDataAddress vtable, uint count, char* frameName, uint* pNeeded) { if (vtable == 0)