diff --git a/docs/design/datacontracts/DacStreams.md b/docs/design/datacontracts/DacStreams.md new file mode 100644 index 00000000000000..4ddc1eb84ad3ab --- /dev/null +++ b/docs/design/datacontracts/DacStreams.md @@ -0,0 +1,66 @@ +# Contract DacStreams + +This contract is for getting information from the streams embedded into a dump file as it crashes + +## APIs of contract + +``` csharp +// Return string corresponding to type system data structure if it exists, or null otherwise +string StringFromEEAddress(TargetPointer address); +``` + +## Version 1 + +Global variables used +| Global Name | Type | Purpose | +| --- | --- | --- | +| MiniMetaDataBuffAddress | TargetPointer | Identify where the mini metadata stream exists | +| MiniMetaDataBuffMaxSize | uint | Identify where the size of the mini metadata stream | + +Magic numbers +| Name | Value | +| --- | --- | +| MiniMetadataSignature | 0x6d727473 | +| EENameStreamSignature | 0x614e4545 | + +The format of the MiniMetadataStream begins with a Streams header, which has 3 fields + +| Field | Type | Offset | Meaning | +| --- | --- | --- | --- | +| MiniMetadataSignature| uint | 0 | Magic value used to identify that there are streams | +| TotalSize | uint | 4 | Total size of the entire set of MiniMetadata streams including this header | +| Count of Streams | uint | 8 | Number of streams in the MiniMetadata | + +The concept is that each stream simply follows the previous stream in the buffer. +There is no padding, so the data is not expected to be aligned within the buffer. +NOTE: At the moment there is only 1 supported stream type, so Count of Streams can only be 1. + +The `EENameStream` is structured as a header, plus a series of null-terminated utf8 strings, and pointers. + +The EENameStream header +| Field | Type | Offset | Meaning | +| --- | --- | --- | --- | +| EENameStreamSignature | uint | 0 | Magic value used to identify that the bytes immediately following are an `EENameStream` | +| CountOfNames | uint | 4 | Number of names encoded | + +EENameStream entry +| Field | Type | Offset | Meaning | +| --- | --- | --- | --- | +| Pointer | pointer | 0 | Pointer to type system structure | +| String | null-terminated UTF-8 sting | 4 or 8 based on target pointer size | Pointer to type system structure | + +Following the EENameStream header, there are CountOfNames entries. Each entry begins with a target pointer sized block which identifies a particular type system data structure, followed by a utf8 encoded null-terminated string. + +``` csharp +string StringFromEEAddress(TargetPointer address) +{ + TargetPointer miniMetaDataBuffAddress = _target.Read(_target.ReadGlobalPointer(Constants.Globals.MiniMetaDataBuffAddress)); + uint miniMetaDataBuffMaxSize = _target.Read(_target.ReadGlobalPointer(Constants.Globals.MiniMetaDataBuffMaxSize)); + + // Parse MiniMetadataStream according the the format described above to produce a dictionary from pointer to string from the EENameStream. + // Then lookup in the dictionary, to produce a result if it was present in the table. + // In general, since this api is intended for fallback scenarios, implementations of this api should attempt + // to return null instead of producing errors. + // Since in normal execution of the runtime no stream is constructed, it is normal when examining full dumps and live process state without a stream encoded. +} +``` diff --git a/docs/design/datacontracts/Loader.md b/docs/design/datacontracts/Loader.md index 4291c96569b278..bc45eaee7a671c 100644 --- a/docs/design/datacontracts/Loader.md +++ b/docs/design/datacontracts/Loader.md @@ -26,6 +26,75 @@ record struct ModuleLookupTables( TargetPointer MethodDefToDesc, TargetPointer TypeDefToMethodTable, TargetPointer TypeRefToMethodTable); + +internal struct EcmaMetadataSchema +{ + public EcmaMetadataSchema(string metadataVersion, bool largeStringHeap, bool largeBlobHeap, bool largeGuidHeap, int[] rowCount, bool[] isSorted, bool variableSizedColumnsAre4BytesLong) + { + MetadataVersion = metadataVersion; + LargeStringHeap = largeStringHeap; + LargeBlobHeap = largeBlobHeap; + LargeGuidHeap = largeGuidHeap; + + _rowCount = rowCount; + _isSorted = isSorted; + + VariableSizedColumnsAreAll4BytesLong = variableSizedColumnsAre4BytesLong; + } + + public readonly string MetadataVersion; + + public readonly bool LargeStringHeap; + public readonly bool LargeBlobHeap; + public readonly bool LargeGuidHeap; + + // Table data, these structures hold MetadataTable.Count entries + private readonly int[] _rowCount; + public readonly ReadOnlySpan RowCount => _rowCount; + + private readonly bool[] _isSorted; + public readonly ReadOnlySpan IsSorted => _isSorted; + + // In certain scenarios the size of the tables is forced to be the maximum size + // Otherwise the size of columns should be computed based on RowSize/the various heap flags + public readonly bool VariableSizedColumnsAreAll4BytesLong; +} + +internal class TargetEcmaMetadata +{ + public TargetEcmaMetadata(EcmaMetadataSchema schema, + TargetSpan[] tables, + TargetSpan stringHeap, + TargetSpan userStringHeap, + TargetSpan blobHeap, + TargetSpan guidHeap) + { + Schema = schema; + _tables = tables; + StringHeap = stringHeap; + UserStringHeap = userStringHeap; + BlobHeap = blobHeap; + GuidHeap = guidHeap; + } + + public EcmaMetadataSchema Schema { get; init; } + + private TargetSpan[] _tables; + public ReadOnlySpan Tables => _tables; + public TargetSpan StringHeap { get; init; } + public TargetSpan UserStringHeap { get; init; } + public TargetSpan BlobHeap { get; init; } + public TargetSpan GuidHeap { get; init; } +} + +[Flags] +internal enum AvailableMetadataType +{ + None = 0, + ReadOnly = 1, + ReadWriteSavedCopy = 2, + ReadWrite = 4 +} ``` ``` csharp @@ -36,13 +105,31 @@ TargetPointer GetLoaderAllocator(ModuleHandle handle); TargetPointer GetThunkHeap(ModuleHandle handle); TargetPointer GetILBase(ModuleHandle handle); TargetPointer GetMetadataAddress(ModuleHandle handle, out ulong size); +AvailableMetadataType GetAvailableMetadataType(ModuleHandle handle); +TargetPointer GetReadWriteSavedMetadataAddress(ModuleHandle handle, out ulong size); +TargetEcmaMetadata GetReadWriteMetadata(ModuleHandle handle); ModuleLookupTables GetLookupTables(ModuleHandle handle); ``` ## Version 1 Data descriptors used: -- `Module` +| Data Descriptor Name | Field | Meaning | +| --- | --- | --- | +| `Module` | `Assembly` | Assembly of the Module | +| `Module` | `Base` | Pointer to start of PE file in memory | +| `Module` | `Flags` | Assembly of the Module | +| `Module` | `LoaderAllocator` | LoaderAllocator of the Module | +| `Module` | `ThunkHeap` | Pointer to the thunk heap | +| `Module` | `DynamicMetadata` | Pointer to saved metadata for reflection emit modules | +| `Module` | `FieldDefToDescMap` | Mapping table | +| `Module` | `ManifestModuleReferencesMap` | Mapping table | +| `Module` | `MemberRefToDescMap` | Mapping table | +| `Module` | `MethodDefToDescMap` | Mapping table | +| `Module` | `TypeDefToMethodTableMap` | Mapping table | +| `Module` | `TypeRefToMethodTableMap` | Mapping table | +| `DynamicMetadata` | `Size` | Size of the dynamic metadata blob (as a 32bit uint) | +| `DynamicMetadata` | `Data` | Start of dynamic metadata data array | ``` csharp ModuleHandle GetModuleHandle(TargetPointer modulePointer) @@ -94,6 +181,32 @@ TargetPointer GetMetadataAddress(ModuleHandle handle, out ulong size) return baseAddress + rva; } +AvailableMetadataType ILoader.GetAvailableMetadataType(ModuleHandle handle) +{ + Data.Module module = _target.ProcessedData.GetOrAdd(handle.Address); + + AvailableMetadataType flags = AvailableMetadataType.None; + + TargetPointer dynamicMetadata = target.ReadPointer(handle.Address + /* Module::DynamicMetadata offset */); + + if (dynamicMetadata != TargetPointer.Null) + flags |= AvailableMetadataType.ReadWriteSavedCopy; + else + flags |= AvailableMetadataType.ReadOnly; + + return flags; +} + +TargetPointer ILoader.GetReadWriteSavedMetadataAddress(ModuleHandle handle, out ulong size) +{ + Data.Module module = _target.ProcessedData.GetOrAdd(handle.Address); + TargetPointer dynamicMetadata = target.ReadPointer(handle.Address + /* Module::DynamicMetadata offset */); + + size = target.Read(handle.Address + /* DynamicMetadata::Size offset */); + TargetPointer result = handle.Address + /* DynamicMetadata::Data offset */; + return result; +} + ModuleLookupTables GetLookupTables(ModuleHandle handle) { return new ModuleLookupTables( diff --git a/docs/design/datacontracts/RuntimeTypeSystem.md b/docs/design/datacontracts/RuntimeTypeSystem.md index f8382ee4fdc599..41d01f6e7db473 100644 --- a/docs/design/datacontracts/RuntimeTypeSystem.md +++ b/docs/design/datacontracts/RuntimeTypeSystem.md @@ -4,46 +4,75 @@ This contract is for exploring the properties of the runtime types of values on ## APIs of contract -A `MethodTable` is the runtime representation of the type information about a value. Given a `TargetPointer` address, the `RuntimeTypeSystem` contract provides a `MethodTableHandle` for querying the `MethodTable`. +A `TypeHandle` is the runtime representation of the type information about a value which is represented as a TypeHandle. +Given a `TargetPointer` address, the `RuntimeTypeSystem` contract provides a `TypeHandle` for querying the details of the `TypeHandle`. + ``` csharp -struct MethodTableHandle +struct TypeHandle { - // no public properties or constructors + // no public constructors + + public TargetPointer Address { get; } + public bool IsNull => Address != 0; +} - internal TargetPointer Address { get; } +internal enum CorElementType +{ + // Values defined in ECMA-335 - II.23.1.16 Element types used in signatures + // + + Internal = 0x21, // Indicates that the next pointer sized number of bytes is the address of a TypeHandle. Signatures that contain the Internal CorElementType cannot exist in metadata that is saved into a serialized format. } ``` +A `TypeHandle` is the runtime representation of the type information about a value. This can be constructed from the address of a `TypeHandle` or a `MethodTable`. + ``` csharp - #region MethodTable inspection APIs - public virtual MethodTableHandle GetMethodTableHandle(TargetPointer targetPointer); + #region TypeHandle inspection APIs + public virtual TypeHandle GetTypeHandle(TargetPointer targetPointer); - public virtual TargetPointer GetModule(MethodTableHandle methodTable); + public virtual TargetPointer GetModule(TypeHandle typeHandle); // A canonical method table is either the MethodTable itself, or in the case of a generic instantiation, it is the // MethodTable of the prototypical instance. - public virtual TargetPointer GetCanonicalMethodTable(MethodTableHandle methodTable); - public virtual TargetPointer GetParentMethodTable(MethodTableHandle methodTable); + public virtual TargetPointer GetCanonicalMethodTable(TypeHandle typeHandle); + public virtual TargetPointer GetParentMethodTable(TypeHandle typeHandle); - public virtual uint GetBaseSize(MethodTableHandle methodTable); + public virtual uint GetBaseSize(TypeHandle typeHandle); // The component size is only available for strings and arrays. It is the size of the element type of the array, or the size of an ECMA 335 character (2 bytes) - public virtual uint GetComponentSize(MethodTableHandle methodTable); + public virtual uint GetComponentSize(TypeHandle typeHandle); // True if the MethodTable is the sentinel value associated with unallocated space in the managed heap - public virtual bool IsFreeObjectMethodTable(MethodTableHandle methodTable); - public virtual bool IsString(MethodTableHandle methodTable); + public virtual bool IsFreeObjectMethodTable(TypeHandle typeHandle); + public virtual bool IsString(TypeHandle typeHandle); // True if the MethodTable represents a type that contains managed references - public virtual bool ContainsGCPointers(MethodTableHandle methodTable); - public virtual bool IsDynamicStatics(MethodTableHandle methodTable); - public virtual ushort GetNumMethods(MethodTableHandle methodTable); - public virtual ushort GetNumInterfaces(MethodTableHandle methodTable); + public virtual bool ContainsGCPointers(TypeHandle typeHandle); + public virtual bool IsDynamicStatics(TypeHandle typeHandle); + public virtual ushort GetNumMethods(TypeHandle typeHandle); + public virtual ushort GetNumInterfaces(TypeHandle typeHandle); // Returns an ECMA-335 TypeDef table token for this type, or for its generic type definition if it is a generic instantiation - public virtual uint GetTypeDefToken(MethodTableHandle methodTable); + public virtual uint GetTypeDefToken(TypeHandle typeHandle); // Returns the ECMA 335 TypeDef table Flags value (a bitmask of TypeAttributes) for this type, // or for its generic type definition if it is a generic instantiation - public virtual uint GetTypeDefTypeAttributes(MethodTableHandle methodTable); - #endregion MethodTable inspection APIs + public virtual uint GetTypeDefTypeAttributes(TypeHandle typeHandle); + public virtual ReadOnlySpan GetInstantiation(TypeHandle typeHandle); + public virtual bool IsGenericTypeDefinition(TypeHandle typeHandle); + + public virtual TypeHandle TypeHandleFromAddress(TargetPointer address); + public virtual bool HasTypeParam(TypeHandle typeHandle); + + // Element type of the type. NOTE: this drops the CorElementType.GenericInst, and CorElementType.String is returned as CorElementType.Class. + // NOTE: If this returns CorElementType.ValueType it may be a normal valuetype or a "NATIVE" valuetype used to represent an interop view of a structure + // HasTypeParam will return true for cases where this is the interop view, and false for normal valuetypes. + 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); + + #endregion TypeHandle inspection APIs ``` ## Version 1 @@ -60,6 +89,7 @@ internal partial struct RuntimeTypeSystem_1 { GenericsMask = 0x00000030, GenericsMask_NonGeneric = 0x00000000, // no instantiation + GenericsMask_TypicalInstantiation = 0x00000030, // the type instantiated at its formal parameters, e.g. List StringArrayValues = GenericsMask_NonGeneric, } @@ -69,9 +99,17 @@ internal partial struct RuntimeTypeSystem_1 internal enum WFLAGS_HIGH : uint { Category_Mask = 0x000F0000, - Category_Array = 0x00080000, + Category_ElementType_Mask = 0x000E0000, Category_Array_Mask = 0x000C0000, + + Category_IfArrayThenSzArray = 0x00020000, + Category_Array = 0x00080000, + Category_ValueType = 0x00040000, + Category_Nullable = 0x00050000, + Category_PrimitiveValueType = 0x00060000, + Category_TruePrimitive = 0x00070000, Category_Interface = 0x000C0000, + ContainsGCPointers = 0x01000000, HasComponentSize = 0x80000000, // This is set if lower 16 bits is used for the component size, // otherwise the lower bits are used for WFLAGS_LOW @@ -118,6 +156,7 @@ internal partial struct RuntimeTypeSystem_1 public bool HasInstantiation => !TestFlagWithMask(WFLAGS_LOW.GenericsMask, WFLAGS_LOW.GenericsMask_NonGeneric); public bool ContainsGCPointers => GetFlag(WFLAGS_HIGH.ContainsGCPointers) != 0; public bool IsDynamicStatics => GetFlag(WFLAGS2_ENUM.DynamicStatics) != 0; + public bool IsGenericTypeDefinition => TestFlagWithMask(WFLAGS_LOW.GenericsMask, WFLAGS_LOW.GenericsMask_TypicalInstantiation); } [Flags] @@ -127,6 +166,16 @@ internal partial struct RuntimeTypeSystem_1 CanonMT = 1, Mask = 1, } + + // Low order bits of TypeHandle address. + // If the low bits contain a 2, then it is a TypeDesc + [Flags] + internal enum TypeHandleBits + { + MethodTable = 0, + TypeDesc = 2, + ValidMask = 2, + } } ``` @@ -141,6 +190,7 @@ internal struct MethodTable_1 internal TargetPointer ParentMethodTable { get; } internal TargetPointer Module { get; } internal TargetPointer EEClassOrCanonMT { get; } + internal TargetPointer PerInstInfo { get; } internal MethodTable_1(Data.MethodTable data) { Flags = new RuntimeTypeSystem_1.MethodTableFlags @@ -154,24 +204,77 @@ internal struct MethodTable_1 EEClassOrCanonMT = data.EEClassOrCanonMT; Module = data.Module; ParentMethodTable = data.ParentMethodTable; + PerInstInfo = data.PerInstInfo; + } +} +``` + +Internally the contract uses extension methods on the `TypeHandle` api so that it can distinguish between `MethodTable` and `TypeDesc` +```csharp +static class RuntimeTypeSystem_1_Helpers +{ + public static bool IsTypeDesc(this TypeHandle type) + { + return type.Address != 0 && (type.Address & TypeHandleBits.ValidMask) == TypeHandleBits.TypeDesc; + } + + public static bool IsMethodTable(this TypeHandle type) + { + return type.Address != 0 && (type.Address & TypeHandleBits.ValidMask) == TypeHandleBits.MethodTable; + } + + public static TargetPointer TypeDescAddress(this TypeHandle type) + { + if (!type.IsTypeDesc()) + return 0; + + return (ulong)type.Address & ~(ulong)TypeHandleBits.ValidMask; } } ``` The contract depends on the global pointer value `FreeObjectMethodTablePointer`. -The contract additionally depends on the `EEClass` data descriptor. + +The contract additionally depends on these data descriptors + +| Data Descriptor Name | Field | Meaning | +| --- | --- | --- | +| `MethodTable` | `MTFlags` | One of the flags fields on `MethodTable` | +| `MethodTable` | `MTFlags2` | One of the flags fields on `MethodTable` | +| `MethodTable` | `BaseSize` | BaseSize of a `MethodTable` | +| `MethodTable` | `EEClassOrCanonMT` | Path to both EEClass and canonical MethodTable of a MethodTable | +| `MethodTable` | `Module` | Module for `MethodTable` | +| `MethodTable` | `ParentMethodTable` | Parent type pointer of `MethodTable` | +| `MethodTable` | `NumInterfaces` | Number of interfaces of `MethodTable` | +| `MethodTable` | `NumVirtuals` | Number of virtual methods in `MethodTable` | +| `MethodTable` | `PerInstInfo` | Either the array element type, or pointer to generic information for `MethodTable` | +| `EEClass` | `InternalCorElementType` | An InternalCorElementType uses the enum values of a CorElementType to indicate some of the information about the type of the type which uses the EEClass In particular, all reference types are CorElementType.Class, Enums are the element type of their underlying type and ValueTypes which can exactly be represented as an element type are represented as such, all other values types are represented as CorElementType.ValueType. | +| `EEClass` | `MethodTable` | Pointer to the canonical MethodTable of this type | +| `EEClass` | `NumMethods` | Count of methods attached to the EEClass | +| `EEClass` | `CorTypeAttr` | Various flags | +| `ArrayClass` | `Rank` | Rank of the associated array MethodTable | +| `TypeDesc` | `TypeAndFlags` | The lower 8 bits are the CorElementType of the `TypeDesc`, the upper 24 bits are reserved for flags | +| `ParamTypeDesc` | `TypeArg` | Associated type argument | +| `TypeVarTypeDesc` | `Module` | Pointer to module which defines the type variable | +| `TypeVarTypeDesc` | `Token` | Token of the type variable | +| `FnPtrTypeDesc` | `NumArgs` | Number of arguments to the function described by the `TypeDesc` | +| `FnPtrTypeDesc` | `CallConv` | Lower 8 bits is the calling convention bit as extracted by the signature that defines this `TypeDesc` | +| `FnPtrTypeDesc` | `RetAndArgTypes` | Pointer to an array of TypeHandle addresses. This length of this is 1 more than `NumArgs` | +| `GenericsDictInfo` | `NumTypeArgs` | Number of type arguments in the type or method instantiation described by this `GenericsDictInfo` | + ```csharp private readonly Dictionary _methodTables; internal TargetPointer FreeObjectMethodTablePointer {get; } - public MethodTableHandle GetMethodTableHandle(TargetPointer methodTablePointer) + public TypeHandle GetTypeHandle(TargetPointer typeHandlePointer) { - ... // validate that methodTablePointer points to something that looks like a MethodTable. - ... // read Data.MethodTable from methodTablePointer. - ... // create a MethodTable_1 and add it to _methodTables. - return MethodTableHandle { Address = methodTablePointer } + ... // validate that typeHandlePointer points to something that looks like a MethodTable or a TypeDesc. + ... // If this is a MethodTable + ... // read Data.MethodTable from typeHandlePointer. + ... // create a MethodTable_1 and add it to _methodTables. + return TypeHandle { Address = typeHandlePointer } } internal static EEClassOrCanonMTBits GetEEClassOrCanonMTBits(TargetPointer eeClassOrCanonMTPtr) @@ -179,11 +282,11 @@ The contract additionally depends on the `EEClass` data descriptor. return (EEClassOrCanonMTBits)(eeClassOrCanonMTPtr & (ulong)EEClassOrCanonMTBits.Mask); } - public uint GetBaseSize(MethodTableHandle methodTableHandle) => _methodTables[methodTableHandle.Address].Flags.BaseSize; + public uint GetBaseSize(TypeHandle TypeHandle) => !typeHandle.IsMethodTable() ? (uint)0 : _methodTables[TypeHandle.Address].Flags.BaseSize; - public uint GetComponentSize(MethodTableHandle methodTableHandle) => GetComponentSize(_methodTables[methodTableHandle.Address]); + public uint GetComponentSize(TypeHandle TypeHandle) =>!typeHandle.IsMethodTable() ? (uint)0 : GetComponentSize(_methodTables[TypeHandle.Address]); - private TargetPointer GetClassPointer(MethodTableHandle methodTableHandle) + private TargetPointer GetClassPointer(TypeHandle TypeHandle) { ... // if the MethodTable stores a pointer to the EEClass, return it // otherwise the MethodTable stores a pointer to the canonical MethodTable @@ -191,34 +294,232 @@ The contract additionally depends on the `EEClass` data descriptor. // Canonical MethodTables always store an EEClass pointer. } - private Data.EEClass GetClassData(MethodTableHandle methodTableHandle) + private Data.EEClass GetClassData(TypeHandle TypeHandle) { - TargetPointer eeClassPtr = GetClassPointer(methodTableHandle); + TargetPointer eeClassPtr = GetClassPointer(TypeHandle); ... // read Data.EEClass data from eeClassPtr } - public TargetPointer GetCanonicalMethodTable(MethodTableHandle methodTableHandle) => GetClassData(methodTableHandle).MethodTable; + public TargetPointer GetCanonicalMethodTable(TypeHandle TypeHandle) => !typeHandle.IsMethodTable() ? TargetPointer.Null : GetClassData(TypeHandle).MethodTable; + + public TargetPointer GetModule(TypeHandle TypeHandle) + { + if (typeHandle.IsMethodTable()) + { + return _methodTables[TypeHandle.Address].Module; + } + else if (typeHandle.IsTypeDesc()) + { + if (HasTypeParam(typeHandle)) + { + return GetModule(GetTypeParam(typeHandle)); + } + else if (IsGenericVariable(typeHandle, out TargetPointer genericParamModule, out _)) + { + return genericParamModule; + } + } + return TargetPointer.Null; + } + + public TargetPointer GetParentMethodTable(TypeHandle TypeHandle) => !typeHandle.IsMethodTable() ? TargetPointer.Null : _methodTables[TypeHandle.Address].ParentMethodTable; + + public bool IsFreeObjectMethodTable(TypeHandle TypeHandle) => FreeObjectMethodTablePointer == TypeHandle.Address; + + public bool IsString(TypeHandle TypeHandle) => !typeHandle.IsMethodTable() ? false : _methodTables[TypeHandle.Address].Flags.IsString; + public bool ContainsGCPointers(TypeHandle TypeHandle) => !typeHandle.IsMethodTable() ? false : _methodTables[TypeHandle.Address].Flags.ContainsGCPointers; + + public uint GetTypeDefToken(TypeHandle TypeHandle) + { + if (!typeHandle.IsMethodTable()) + return 0; + + MethodTable_1 typeHandle = _methodTables[TypeHandle.Address]; + return (uint)(typeHandle.Flags.GetTypeDefRid() | ((int)TableIndex.TypeDef << 24)); + } + + public ushort GetNumMethods(TypeHandle TypeHandle) => !typeHandle.IsMethodTable() ? 0 : GetClassData(TypeHandle).NumMethods; + + public ushort GetNumInterfaces(TypeHandle TypeHandle) => !typeHandle.IsMethodTable() ? 0 : _methodTables[TypeHandle.Address].NumInterfaces; + + public uint GetTypeDefTypeAttributes(TypeHandle TypeHandle) => !typeHandle.IsMethodTable() ? 0 : GetClassData(TypeHandle).CorTypeAttr; + + public bool IsDynamicStatics(TypeHandle TypeHandle) => !typeHandle.IsMethodTable() ? false : _methodTables[TypeHandle.Address].Flags.IsDynamicStatics; + + public ReadOnlySpan GetInstantiation(TypeHandle TypeHandle) + { + if (!typeHandle.IsMethodTable()) + return default; + + MethodTable_1 typeHandle = _methodTables[TypeHandle.Address]; + if (!typeHandle.Flags.HasInstantiation) + return default; + + TargetPointer perInstInfo = typeHandle.PerInstInfo; + TargetPointer genericsDictInfo = perInstInfo - (ulong)_target.PointerSize; + TargetPointer dictionaryPointer = _target.ReadPointer(perInstInfo); + + int NumTypeArgs = // Read NumTypeArgs from genericsDictInfo using GenericsDictInfo contract + TypeHandle[] instantiation = new TypeHandle[NumTypeArgs]; + for (int i = 0; i < NumTypeArgs; i++) + instantiation[i] = GetTypeHandle(_target.ReadPointer(dictionaryPointer + _target.PointerSize * i)); + + return instantiation; + } + + public bool IsDynamicStatics(TypeHandle TypeHandle) => !typeHandle.IsMethodTable() ? false : _methodTables[TypeHandle.Address].Flags.IsDynamicStatics; + + public bool HasTypeParam(TypeHandle typeHandle) + { + if (typeHandle.IsMethodTable()) + { + MethodTable typeHandle = _methodTables[typeHandle.Address]; + return typeHandle.Flags.IsArray; + } + else if (typeHandle.IsTypeDesc()) + { + int TypeAndFlags = // Read TypeAndFlags field from TypeDesc contract using address typeHandle.TypeDescAddress() + CorElementType elemType = (CorElementType)(TypeAndFlags & 0xFF); + switch (elemType) + { + case CorElementType.ValueType: + case CorElementType.Byref: + case CorElementType.Ptr: + return true; + } + } + return false; + } - public TargetPointer GetModule(MethodTableHandle methodTableHandle) => _methodTables[methodTableHandle.Address].Module; - public TargetPointer GetParentMethodTable(MethodTableHandle methodTableHandle) => _methodTables[methodTableHandle.Address].ParentMethodTable; + public CorElementType GetSignatureCorElementType(TypeHandle typeHandle) + { + if (typeHandle.IsMethodTable()) + { + MethodTable typeHandle = _methodTables[typeHandle.Address]; - public bool IsFreeObjectMethodTable(MethodTableHandle methodTableHandle) => FreeObjectMethodTablePointer == methodTableHandle.Address; + switch (typeHandle.Flags.GetFlag(WFLAGS_HIGH.Category_Mask)) + { + case WFLAGS_HIGH.Category_Array: + return CorElementType.Array; + case WFLAGS_HIGH.Category_Array | WFLAGS_HIGH.Category_IfArrayThenSzArray: + return CorElementType.SzArray; + case WFLAGS_HIGH.Category_ValueType: + case WFLAGS_HIGH.Category_Nullable: + case WFLAGS_HIGH.Category_PrimitiveValueType: + return CorElementType.ValueType; + case WFLAGS_HIGH.Category_TruePrimitive: + return (CorElementType)GetClassData(typeHandle).InternalCorElementType; + default: + return CorElementType.Class; + } + } + else if (typeHandle.IsTypeDesc()) + { + int TypeAndFlags = // Read TypeAndFlags field from TypeDesc contract using address typeHandle.TypeDescAddress() + return (CorElementType)(TypeAndFlags & 0xFF); + } + return default(CorElementType); + } - public bool IsString(MethodTableHandle methodTableHandle) => _methodTables[methodTableHandle.Address].Flags.IsString; - public bool ContainsGCPointers(MethodTableHandle methodTableHandle) => _methodTables[methodTableHandle.Address].Flags.ContainsGCPointers; + // 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 bool IsArray(TypeHandle typeHandle, out uint rank) + { + if (typeHandle.IsMethodTable()) + { + MethodTable typeHandle = _methodTables[typeHandle.Address]; - public uint GetTypeDefToken(MethodTableHandle methodTableHandle) + switch (typeHandle.Flags.GetFlag(WFLAGS_HIGH.Category_Mask)) + { + case WFLAGS_HIGH.Category_Array: + TargetPointer clsPtr = GetClassPointer(typeHandle); + rank = // Read Rank field from ArrayClass contract using address clsPtr + return true; + + case WFLAGS_HIGH.Category_Array | WFLAGS_HIGH.Category_IfArrayThenSzArray: + rank = 1; + return true; + } + } + + rank = 0; + return false; + } + + public TypeHandle GetTypeParam(TypeHandle typeHandle) { - MethodTable_1 methodTable = _methodTables[methodTableHandle.Address]; - return (uint)(methodTable.Flags.GetTypeDefRid() | ((int)TableIndex.TypeDef << 24)); + if (typeHandle.IsMethodTable()) + { + MethodTable typeHandle = _methodTables[typeHandle.Address]; + + // Validate that this is an array + if (!typeHandle.Flags.IsArray) + throw new ArgumentException(nameof(typeHandle)); + + return TypeHandleFromAddress(typeHandle.PerInstInfo); + } + else if (typeHandle.IsTypeDesc()) + { + int TypeAndFlags = // Read TypeAndFlags field from TypeDesc contract using address typeHandle.TypeDescAddress() + CorElementType elemType = (CorElementType)(typeDesc.TypeAndFlags & 0xFF); + + switch (elemType) + { + case CorElementType.ValueType: + case CorElementType.Byref: + case CorElementType.Ptr: + TargetPointer typeArgPointer = // Read TypeArg field from ParamTypeDesc contract using address typeHandle.TypeDescAddress() + return TypeHandleFromAddress(typeArgPointer); + } + } + throw new ArgumentException(nameof(typeHandle)); } - public ushort GetNumMethods(MethodTableHandle methodTableHandle) => GetClassData(methodTableHandle).NumMethods; + public bool IsGenericVariable(TypeHandle typeHandle, out TargetPointer module, out uint token) + { + module = TargetPointer.Null; + token = 0; + + if (!typeHandle.IsTypeDesc()) + return false; - public ushort GetNumInterfaces(MethodTableHandle methodTableHandle) => _methodTables[methodTableHandle.Address].NumInterfaces; + int TypeAndFlags = // Read TypeAndFlags field from TypeDesc contract using address typeHandle.TypeDescAddress() + CorElementType elemType = (CorElementType)(typeDesc.TypeAndFlags & 0xFF); - public uint GetTypeDefTypeAttributes(MethodTableHandle methodTableHandle) => GetClassData(methodTableHandle).CorTypeAttr; + switch (elemType) + { + case CorElementType.MVar: + case CorElementType.Var: + module = // Read Module field from TypeVarTypeDesc contract using address typeHandle.TypeDescAddress() + token = // Read Module field from TypeVarTypeDesc contract using address typeHandle.TypeDescAddress() + return true; + } + return false; + } + + public bool IsFunctionPointer(TypeHandle typeHandle, out ReadOnlySpan retAndArgTypes, out byte callConv) + { + retAndArgTypes = default; + callConv = default; - public bool IsDynamicStatics(MethodTableHandle methodTableHandle) => _methodTables[methodTableHandle.Address].Flags.IsDynamicStatics; + if (!typeHandle.IsTypeDesc()) + return false; + + int TypeAndFlags = // Read TypeAndFlags field from TypeDesc contract using address typeHandle.TypeDescAddress() + CorElementType elemType = (CorElementType)(typeDesc.TypeAndFlags & 0xFF); + + if (elemType != CorElementType.FnPtr) + return false; + + int NumArgs = // Read NumArgs field from FnPtrTypeDesc contract using address typeHandle.TypeDescAddress() + TargetPointer RetAndArgTypes = // Read NumArgs field from FnPtrTypeDesc contract using address typeHandle.TypeDescAddress() + + TypeHandle[] retAndArgTypesArray = new TypeHandle[NumTypeArgs + 1]; + for (int i = 0; i <= NumTypeArgs; i++) + retAndArgTypesArray[i] = GetTypeHandle(_target.ReadPointer(RetAndArgTypes + _target.PointerSize * i)); + + retAndArgTypes = retAndArgTypesArray; + callConv = (byte) // Read CallConv field from FnPtrTypeDesc contract using address typeHandle.TypeDescAddress(), and then ignore all but the low 8 bits. + return true; + } ``` diff --git a/src/coreclr/debug/daccess/daccess.cpp b/src/coreclr/debug/daccess/daccess.cpp index 5b3310cfe56489..5e74d592ac83c7 100644 --- a/src/coreclr/debug/daccess/daccess.cpp +++ b/src/coreclr/debug/daccess/daccess.cpp @@ -6718,11 +6718,11 @@ ClrDataAccess::GetMDImport(const PEAssembly* pPEAssembly, const ReflectionModule else if (reflectionModule != NULL) { // Get the metadata - PTR_SBuffer metadataBuffer = reflectionModule->GetDynamicMetadataBuffer(); + TADDR metadataBuffer = reflectionModule->GetDynamicMetadataBuffer(); if (metadataBuffer != PTR_NULL) { - mdBaseTarget = dac_cast((metadataBuffer->DacGetRawBuffer()).StartAddress()); - mdSize = metadataBuffer->GetSize(); + mdBaseTarget = dac_cast(metadataBuffer + offsetof(DynamicMetadata, Data)); + mdSize = dac_cast(metadataBuffer)->Size; } else { diff --git a/src/coreclr/debug/daccess/dacdbiimpl.cpp b/src/coreclr/debug/daccess/dacdbiimpl.cpp index a6dda591278557..8dbd2cf2ca0588 100644 --- a/src/coreclr/debug/daccess/dacdbiimpl.cpp +++ b/src/coreclr/debug/daccess/dacdbiimpl.cpp @@ -4285,7 +4285,10 @@ void DacDbiInterfaceImpl::GetMetadata(VMPTR_Module vmModule, TargetBuffer * pTar { // Here is the fetch. ReflectionModule * pReflectionModule = pModule->GetReflectionModule(); - InitTargetBufferFromTargetSBuffer(pReflectionModule->GetDynamicMetadataBuffer(), pTargetBuffer); + + TADDR metadataBuffer = pReflectionModule->GetDynamicMetadataBuffer(); + CORDB_ADDRESS addr = PTR_TO_CORDB_ADDRESS(metadataBuffer + offsetof(DynamicMetadata, Data)); + pTargetBuffer->Init(addr, dac_cast(metadataBuffer)->Size); } else { diff --git a/src/coreclr/debug/daccess/dacimpl.h b/src/coreclr/debug/daccess/dacimpl.h index 3493125334bd07..f3d12c3427263c 100644 --- a/src/coreclr/debug/daccess/dacimpl.h +++ b/src/coreclr/debug/daccess/dacimpl.h @@ -1239,6 +1239,7 @@ class ClrDataAccess HRESULT GetNestedExceptionDataImpl(CLRDATA_ADDRESS exception, CLRDATA_ADDRESS *exceptionObject, CLRDATA_ADDRESS *nextNestedException); HRESULT GetMethodTableDataImpl(CLRDATA_ADDRESS mt, struct DacpMethodTableData *data); HRESULT GetMethodTableForEEClassImpl (CLRDATA_ADDRESS eeClassReallyMT, CLRDATA_ADDRESS *value); + HRESULT GetMethodTableNameImpl(CLRDATA_ADDRESS mt, unsigned int count, _Inout_updates_z_(count) WCHAR *mtName, unsigned int *pNeeded); HRESULT GetObjectExceptionDataImpl(CLRDATA_ADDRESS objAddr, struct DacpExceptionObjectData *data); BOOL IsExceptionFromManagedCode(EXCEPTION_RECORD * pExceptionRecord); diff --git a/src/coreclr/debug/daccess/request.cpp b/src/coreclr/debug/daccess/request.cpp index 8645398923bdb1..5c759faeac4f93 100644 --- a/src/coreclr/debug/daccess/request.cpp +++ b/src/coreclr/debug/daccess/request.cpp @@ -2015,7 +2015,47 @@ ClrDataAccess::GetMethodTableName(CLRDATA_ADDRESS mt, unsigned int count, _Inout return E_INVALIDARG; SOSDacEnter(); + if (m_cdacSos != NULL) + { + // Try the cDAC first - it will return E_NOTIMPL if it doesn't support this method yet. Fall back to the DAC. + hr = m_cdacSos->GetMethodTableName(mt, count, mtName, pNeeded); + if (FAILED(hr)) + { + hr = GetMethodTableNameImpl(mt, count, mtName, pNeeded); + } +#ifdef _DEBUG + else + { + // Assert that the data is the same as what we get from the DAC. + NewArrayHolder pwszNameLocal(new WCHAR[count]); + unsigned int neededLocal = 0; + HRESULT hrLocal = GetMethodTableNameImpl(mt, count, mtName != NULL ? (WCHAR *)pwszNameLocal : NULL, pNeeded != NULL ? &neededLocal : NULL); + _ASSERTE(hr == hrLocal); + if (mtName != NULL) + { + _ASSERTE(0 == u16_strncmp(mtName, (WCHAR *)pwszNameLocal, count)); + } + if (pNeeded != NULL) + { + _ASSERTE(*pNeeded == neededLocal); + } + } +#endif + } + else + { + hr = GetMethodTableNameImpl(mt, count, mtName, pNeeded); + } + + SOSDacLeave(); + return hr; +} + +HRESULT +ClrDataAccess::GetMethodTableNameImpl(CLRDATA_ADDRESS mt, unsigned int count, _Inout_updates_z_(count) WCHAR *mtName, unsigned int *pNeeded) +{ + HRESULT hr = S_OK; PTR_MethodTable pMT = PTR_MethodTable(TO_TADDR(mt)); BOOL free = FALSE; @@ -2086,7 +2126,6 @@ ClrDataAccess::GetMethodTableName(CLRDATA_ADDRESS mt, unsigned int count, _Inout } } - SOSDacLeave(); return hr; } diff --git a/src/coreclr/debug/runtimeinfo/contracts.jsonc b/src/coreclr/debug/runtimeinfo/contracts.jsonc index f40991da45b674..7d477b00cd858d 100644 --- a/src/coreclr/debug/runtimeinfo/contracts.jsonc +++ b/src/coreclr/debug/runtimeinfo/contracts.jsonc @@ -9,6 +9,7 @@ // cdac-build-tool can take multiple "-c contract_file" arguments // so to conditionally include contracts, put additional contracts in a separate file { + "DacStreams": 1, "Exception": 1, "Loader": 1, "RuntimeTypeSystem": 1, diff --git a/src/coreclr/debug/runtimeinfo/datadescriptor.h b/src/coreclr/debug/runtimeinfo/datadescriptor.h index a0c71736fd6f9a..da83bebcfea39f 100644 --- a/src/coreclr/debug/runtimeinfo/datadescriptor.h +++ b/src/coreclr/debug/runtimeinfo/datadescriptor.h @@ -181,6 +181,7 @@ CDAC_TYPE_FIELD(Module, /*pointer*/, Base, cdac_offsets::Base) CDAC_TYPE_FIELD(Module, /*pointer*/, Flags, cdac_offsets::Flags) CDAC_TYPE_FIELD(Module, /*pointer*/, LoaderAllocator, cdac_offsets::LoaderAllocator) CDAC_TYPE_FIELD(Module, /*pointer*/, ThunkHeap, cdac_offsets::ThunkHeap) +CDAC_TYPE_FIELD(Module, /*pointer*/, DynamicMetadata, cdac_offsets::DynamicMetadata) CDAC_TYPE_FIELD(Module, /*pointer*/, FieldDefToDescMap, cdac_offsets::FieldDefToDescMap) CDAC_TYPE_FIELD(Module, /*pointer*/, ManifestModuleReferencesMap, cdac_offsets::ManifestModuleReferencesMap) @@ -202,6 +203,7 @@ CDAC_TYPE_FIELD(MethodTable, /*pointer*/, Module, cdac_offsets::Mod CDAC_TYPE_FIELD(MethodTable, /*pointer*/, ParentMethodTable, cdac_offsets::ParentMethodTable) CDAC_TYPE_FIELD(MethodTable, /*uint16*/, NumInterfaces, cdac_offsets::NumInterfaces) CDAC_TYPE_FIELD(MethodTable, /*uint16*/, NumVirtuals, cdac_offsets::NumVirtuals) +CDAC_TYPE_FIELD(MethodTable, /*pointer*/, PerInstInfo, cdac_offsets::PerInstInfo) CDAC_TYPE_END(MethodTable) CDAC_TYPE_BEGIN(EEClass) @@ -209,8 +211,47 @@ CDAC_TYPE_INDETERMINATE(EEClass) CDAC_TYPE_FIELD(EEClass, /*pointer*/, MethodTable, cdac_offsets::MethodTable) CDAC_TYPE_FIELD(EEClass, /*uint16*/, NumMethods, cdac_offsets::NumMethods) CDAC_TYPE_FIELD(EEClass, /*uint32*/, CorTypeAttr, cdac_offsets::CorTypeAttr) +CDAC_TYPE_FIELD(EEClass, /*uint8*/, InternalCorElementType, cdac_offsets::InternalCorElementType) CDAC_TYPE_END(EEClass) +CDAC_TYPE_BEGIN(ArrayClass) +CDAC_TYPE_INDETERMINATE(ArrayClass) +CDAC_TYPE_FIELD(ArrayClass, /*uint8*/, Rank, cdac_offsets::Rank) +CDAC_TYPE_END(ArrayClass) + +CDAC_TYPE_BEGIN(GenericsDictInfo) +CDAC_TYPE_INDETERMINATE(GenericsDictInfo) +CDAC_TYPE_FIELD(GenericsDictInfo, /*uint16*/, NumTypeArgs, cdac_offsets::NumTypeArgs) +CDAC_TYPE_END(GenericsDictInfo) + +CDAC_TYPE_BEGIN(TypeDesc) +CDAC_TYPE_INDETERMINATE(TypeDesc) +CDAC_TYPE_FIELD(TypeDesc, /*uint32*/, TypeAndFlags, cdac_offsets::TypeAndFlags) +CDAC_TYPE_END(TypeDesc) + +CDAC_TYPE_BEGIN(ParamTypeDesc) +CDAC_TYPE_INDETERMINATE(ParamTypeDesc) +CDAC_TYPE_FIELD(ParamTypeDesc, /*pointer*/, TypeArg, cdac_offsets::TypeArg) +CDAC_TYPE_END(ParamTypeDesc) + +CDAC_TYPE_BEGIN(TypeVarTypeDesc) +CDAC_TYPE_INDETERMINATE(TypeVarTypeDesc) +CDAC_TYPE_FIELD(TypeVarTypeDesc, /*pointer*/, Module, cdac_offsets::Module) +CDAC_TYPE_FIELD(TypeVarTypeDesc, /*uint32*/, Token, cdac_offsets::Token) +CDAC_TYPE_END(TypeVarTypeDesc) + +CDAC_TYPE_BEGIN(FnPtrTypeDesc) +CDAC_TYPE_INDETERMINATE(FnPtrTypeDesc) +CDAC_TYPE_FIELD(FnPtrTypeDesc, /*uint32*/, NumArgs, cdac_offsets::NumArgs) +CDAC_TYPE_FIELD(FnPtrTypeDesc, /*uint32*/, CallConv, cdac_offsets::CallConv) +CDAC_TYPE_FIELD(FnPtrTypeDesc, /*uint32*/, RetAndArgTypes, cdac_offsets::RetAndArgTypes) +CDAC_TYPE_END(FnPtrTypeDesc) + +CDAC_TYPE_BEGIN(DynamicMetadata) +CDAC_TYPE_FIELD(DynamicMetadata, /*uint32*/, Size, cdac_offsets::Size) +CDAC_TYPE_FIELD(DynamicMetadata, /*inline byte array*/, Data, cdac_offsets::Data) +CDAC_TYPE_END(DynamicMetadata) + CDAC_TYPES_END() CDAC_GLOBALS_BEGIN() @@ -225,6 +266,8 @@ CDAC_GLOBAL(FeatureEHFunclets, uint8, 0) #endif CDAC_GLOBAL(SOSBreakingChangeVersion, uint8, SOS_BREAKING_CHANGE_VERSION) CDAC_GLOBAL_POINTER(FreeObjectMethodTable, &::g_pFreeObjectMethodTable) +CDAC_GLOBAL_POINTER(MiniMetaDataBuffAddress, &::g_MiniMetaDataBuffAddress) +CDAC_GLOBAL_POINTER(MiniMetaDataBuffMaxSize, &::g_MiniMetaDataBuffMaxSize) CDAC_GLOBALS_END() #undef CDAC_BASELINE diff --git a/src/coreclr/jit/compiler.h b/src/coreclr/jit/compiler.h index 68ddba5f2415ff..e7822dc591597b 100644 --- a/src/coreclr/jit/compiler.h +++ b/src/coreclr/jit/compiler.h @@ -7646,6 +7646,8 @@ class Compiler FlowGraphNaturalLoop* loop, BasicBlock* exiting, LoopLocalOccurrences* loopLocals); + bool optCanAndShouldChangeExitTest(GenTree* cond, bool dump); + bool optPrimaryIVHasNonLoopUses(unsigned lclNum, FlowGraphNaturalLoop* loop, LoopLocalOccurrences* loopLocals); bool optWidenPrimaryIV(FlowGraphNaturalLoop* loop, unsigned lclNum, ScevAddRec* addRec, diff --git a/src/coreclr/jit/inductionvariableopts.cpp b/src/coreclr/jit/inductionvariableopts.cpp index 417632ad79f06d..fba8febf0fe7cd 100644 --- a/src/coreclr/jit/inductionvariableopts.cpp +++ b/src/coreclr/jit/inductionvariableopts.cpp @@ -992,6 +992,9 @@ bool Compiler::optMakeExitTestDownwardsCounted(ScalarEvolutionContext& scevConte BasicBlock* exiting, LoopLocalOccurrences* loopLocals) { + // Note: keep the heuristics here in sync with + // `StrengthReductionContext::IsUseExpectedToBeRemoved`. + assert(exiting->KindIs(BBJ_COND)); Statement* jtrueStmt = exiting->lastStmt(); @@ -999,21 +1002,8 @@ bool Compiler::optMakeExitTestDownwardsCounted(ScalarEvolutionContext& scevConte assert(jtrue->OperIs(GT_JTRUE)); GenTree* cond = jtrue->gtGetOp1(); - if ((jtrue->gtFlags & GTF_SIDE_EFFECT) != 0) + if (!optCanAndShouldChangeExitTest(cond, /* dump */ true)) { - // If the IV is used as part of the side effect then we can't - // transform; otherwise we could. TODO-CQ: Make this determination and - // extract side effects from the jtrue to make this work. - JITDUMP(" No; exit node has side effects\n"); - return false; - } - - bool checkProfitability = !compStressCompile(STRESS_DOWNWARDS_COUNTED_LOOPS, 50); - - if (checkProfitability && cond->OperIsCompare() && - (cond->gtGetOp1()->IsIntegralConst(0) || cond->gtGetOp2()->IsIntegralConst(0))) - { - JITDUMP(" No; operand of condition [%06u] is already 0\n", dspTreeID(cond)); return false; } @@ -1029,34 +1019,10 @@ bool Compiler::optMakeExitTestDownwardsCounted(ScalarEvolutionContext& scevConte break; } - unsigned candidateLclNum = stmt->GetRootNode()->AsLclVarCommon()->GetLclNum(); - LclVarDsc* candidateVarDsc = lvaGetDesc(candidateLclNum); - if (candidateVarDsc->lvIsStructField && loopLocals->HasAnyOccurrences(loop, candidateVarDsc->lvParentLcl)) - { - continue; - } - - if (candidateVarDsc->lvDoNotEnregister) - { - // This filters out locals that may be live into exceptional exits. - continue; - } - - BasicBlockVisit visitResult = loop->VisitRegularExitBlocks([=](BasicBlock* block) { - if (VarSetOps::IsMember(this, block->bbLiveIn, candidateVarDsc->lvVarIndex)) - { - return BasicBlockVisit::Abort; - } - - return BasicBlockVisit::Continue; - }); + unsigned candidateLclNum = stmt->GetRootNode()->AsLclVarCommon()->GetLclNum(); - if (visitResult == BasicBlockVisit::Abort) + if (optPrimaryIVHasNonLoopUses(candidateLclNum, loop, loopLocals)) { - // Live into an exit. - // TODO-CQ: In some cases it may be profitable to materialize the final value after the loop. - // This requires analysis on whether the required expressions are available there - // (and whether it doesn't extend their lifetimes too much). continue; } @@ -1065,7 +1031,8 @@ bool Compiler::optMakeExitTestDownwardsCounted(ScalarEvolutionContext& scevConte if (stmt == jtrueStmt) { hasUseInTest = true; - // Use is inside the loop test that has no side effects (as we checked above), can remove + // Use is inside the loop test that we know we can change (from + // calling optCanAndShouldChangeExitTest above) return true; } @@ -1110,15 +1077,13 @@ bool Compiler::optMakeExitTestDownwardsCounted(ScalarEvolutionContext& scevConte removableLocals.Push(candidateLclNum); } + bool checkProfitability = !compStressCompile(STRESS_DOWNWARDS_COUNTED_LOOPS, 50); if (checkProfitability && (removableLocals.Height() <= 0)) { JITDUMP(" Found no potentially removable locals when making this loop downwards counted\n"); return false; } - // At this point we know that the single exit dominates all backedges. - JITDUMP(" All backedges are dominated by exiting block " FMT_BB "\n", exiting->bbNum); - if (loop->MayExecuteBlockMultipleTimesPerIteration(exiting)) { JITDUMP(" Exiting block may be executed multiple times per iteration; cannot place decrement in it\n"); @@ -1210,20 +1175,111 @@ bool Compiler::optMakeExitTestDownwardsCounted(ScalarEvolutionContext& scevConte return true; } +//------------------------------------------------------------------------ +// optCanAndShouldChangeExitTest: +// Check if the exit test can be rephrased to a downwards counted exit test +// (being compared to zero). +// +// Parameters: +// cond - The exit test +// dump - Whether to JITDUMP the reason for the decisions +// +// Returns: +// True if the exit test can be changed. +// +bool Compiler::optCanAndShouldChangeExitTest(GenTree* cond, bool dump) +{ + if ((cond->gtFlags & GTF_SIDE_EFFECT) != 0) + { + // This would be possible if the IV use is not part of the side effect, + // in which case we could extract them. However, these cases turn out + // to be never analyzable for us even if we tried to do that, so just + // do the easy check here. + if (dump) + { + JITDUMP(" No; exit node has side effects\n"); + } + + return false; + } + + bool checkProfitability = !compStressCompile(STRESS_DOWNWARDS_COUNTED_LOOPS, 50); + + if (checkProfitability && cond->OperIsCompare() && + (cond->gtGetOp1()->IsIntegralConst(0) || cond->gtGetOp2()->IsIntegralConst(0))) + { + if (dump) + { + JITDUMP(" No; operand of condition [%06u] is already 0\n", dspTreeID(cond)); + } + + return false; + } + + return true; +} + +//------------------------------------------------------------------------ +// optPrimaryIVHasNonLoopUses: +// Check if a primary IV may have uses of the primary IV that we do not +// reason about. +// +// Parameters: +// lclNum - The primary IV +// loop - The loop +// loopLocals - Data structure tracking local uses +// +// Returns: +// True if the primary IV may have non-loop uses (or if it is a field with +// uses of the parent struct). +// +bool Compiler::optPrimaryIVHasNonLoopUses(unsigned lclNum, FlowGraphNaturalLoop* loop, LoopLocalOccurrences* loopLocals) +{ + LclVarDsc* varDsc = lvaGetDesc(lclNum); + if (varDsc->lvIsStructField && loopLocals->HasAnyOccurrences(loop, varDsc->lvParentLcl)) + { + return true; + } + + if (varDsc->lvDoNotEnregister) + { + // This filters out locals that may be live into exceptional exits. + return true; + } + + BasicBlockVisit visitResult = loop->VisitRegularExitBlocks([=](BasicBlock* block) { + if (VarSetOps::IsMember(this, block->bbLiveIn, varDsc->lvVarIndex)) + { + return BasicBlockVisit::Abort; + } + + return BasicBlockVisit::Continue; + }); + + if (visitResult == BasicBlockVisit::Abort) + { + // Live into an exit. + // TODO-CQ: In some cases it may be profitable to materialize the final value after the loop. + // This requires analysis on whether the required expressions are available there + // (and whether it doesn't extend their lifetimes too much). + return true; + } + + return false; +} + struct CursorInfo { BasicBlock* Block; Statement* Stmt; GenTree* Tree; ScevAddRec* IV; - bool IsInsideExitTest = false; - CursorInfo(BasicBlock* block, Statement* stmt, GenTree* tree, ScevAddRec* iv, bool isInsideExitTest) + CursorInfo(BasicBlock* block, Statement* stmt, GenTree* tree, ScevAddRec* iv) : Block(block) , Stmt(stmt) , Tree(tree) , IV(iv) - , IsInsideExitTest(isInsideExitTest) { } }; @@ -1242,6 +1298,7 @@ class StrengthReductionContext void InitializeSimplificationAssumptions(); bool InitializeCursors(GenTreeLclVarCommon* primaryIVLcl, ScevAddRec* primaryIV); + bool IsUseExpectedToBeRemoved(BasicBlock* block, Statement* stmt, GenTreeLclVarCommon* tree); void AdvanceCursors(ArrayStack* cursors, ArrayStack* nextCursors); bool CheckAdvancedCursors(ArrayStack* cursors, int derivedLevel, ScevAddRec** nextIV); bool StaysWithinManagedObject(ArrayStack* cursors, ScevAddRec* addRec); @@ -1337,9 +1394,19 @@ bool StrengthReductionContext::TryStrengthReduce() continue; } + if (m_comp->optPrimaryIVHasNonLoopUses(primaryIVLcl->GetLclNum(), m_loop, &m_loopLocals)) + { + // We won't be able to remove this primary IV + JITDUMP(" Has non-loop uses\n"); + continue; + } + ScevAddRec* primaryIV = static_cast(candidate); - InitializeCursors(primaryIVLcl, primaryIV); + if (!InitializeCursors(primaryIVLcl, primaryIV)) + { + continue; + } ArrayStack* cursors = &m_cursors1; ArrayStack* nextCursors = &m_cursors2; @@ -1467,16 +1534,11 @@ bool StrengthReductionContext::InitializeCursors(GenTreeLclVarCommon* primaryIVL m_cursors2.Reset(); auto visitor = [=](BasicBlock* block, Statement* stmt, GenTreeLclVarCommon* tree) { - if (stmt->GetRootNode()->OperIsLocalStore()) + if (IsUseExpectedToBeRemoved(block, stmt, tree)) { - GenTreeLclVarCommon* lcl = stmt->GetRootNode()->AsLclVarCommon(); - if ((lcl->GetLclNum() == primaryIVLcl->GetLclNum()) && ((lcl->Data()->gtFlags & GTF_SIDE_EFFECT) == 0)) - { - // Store to the primary IV without side effects; if we end - // up strength reducing, then this store is expected to be - // removed by making the loop downwards counted. - return true; - } + // If we do strength reduction we expect to be able to remove this + // use; do not create a cursor for it. + return true; } if (!tree->OperIs(GT_LCL_VAR)) @@ -1484,16 +1546,12 @@ bool StrengthReductionContext::InitializeCursors(GenTreeLclVarCommon* primaryIVL return false; } - bool isInsideExitTest = - block->KindIs(BBJ_COND) && (stmt == block->lastStmt()) && - (!m_loop->ContainsBlock(block->GetTrueTarget()) || !m_loop->ContainsBlock(block->GetFalseTarget())); - if (tree->GetSsaNum() != primaryIVLcl->GetSsaNum()) { // Most likely a post-incremented use of the primary IV; we // could replace these as well, but currently we only handle // the cases where we expect the use to be removed. - return isInsideExitTest; + return false; } Scev* iv = m_scevContext.Analyze(block, tree); @@ -1510,14 +1568,14 @@ bool StrengthReductionContext::InitializeCursors(GenTreeLclVarCommon* primaryIVL // as the primary IV. assert(Scev::Equals(m_scevContext.Simplify(iv, m_simplAssumptions), primaryIV)); - m_cursors1.Emplace(block, stmt, tree, primaryIV, isInsideExitTest); - m_cursors2.Emplace(block, stmt, tree, primaryIV, isInsideExitTest); + m_cursors1.Emplace(block, stmt, tree, primaryIV); + m_cursors2.Emplace(block, stmt, tree, primaryIV); return true; }; if (!m_loopLocals.VisitOccurrences(m_loop, primaryIVLcl->GetLclNum(), visitor) || (m_cursors1.Height() <= 0)) { - JITDUMP(" Could not create cursors for all loop uses of primary IV"); + JITDUMP(" Could not create cursors for all loop uses of primary IV\n"); return false; } @@ -1529,8 +1587,7 @@ bool StrengthReductionContext::InitializeCursors(GenTreeLclVarCommon* primaryIVL for (int i = 0; i < m_cursors1.Height(); i++) { CursorInfo& cursor = m_cursors1.BottomRef(i); - printf(" [%d] [%06u]%s: ", i, Compiler::dspTreeID(cursor.Tree), - cursor.IsInsideExitTest ? " (in-test)" : ""); + printf(" [%d] [%06u]: ", i, Compiler::dspTreeID(cursor.Tree)); cursor.IV->Dump(m_comp); printf("\n"); } @@ -1540,6 +1597,83 @@ bool StrengthReductionContext::InitializeCursors(GenTreeLclVarCommon* primaryIVL return true; } +//------------------------------------------------------------------------ +// IsUseExpectedToBeRemoved: Check if a use of a primary IV is expected to be +// removed if we strength reduce other uses of the primary IV. +// +// Parameters: +// block - Block containing the use +// stmt - Statement containing the use +// tree - Actual use of the primary IV +// +// Returns: +// True if the use is expected to be removable. +// +bool StrengthReductionContext::IsUseExpectedToBeRemoved(BasicBlock* block, Statement* stmt, GenTreeLclVarCommon* tree) +{ + unsigned primaryIVLclNum = tree->GetLclNum(); + if (stmt->GetRootNode()->OperIsLocalStore()) + { + GenTreeLclVarCommon* lcl = stmt->GetRootNode()->AsLclVarCommon(); + if ((lcl->GetLclNum() == primaryIVLclNum) && ((lcl->Data()->gtFlags & GTF_SIDE_EFFECT) == 0)) + { + // Store to the primary IV without side effects; if we end + // up strength reducing, then this store is expected to be + // removed by making the loop downwards counted. + return true; + } + + return false; + } + + bool isInsideExitTest = + block->KindIs(BBJ_COND) && (stmt == block->lastStmt()) && + (!m_loop->ContainsBlock(block->GetTrueTarget()) || !m_loop->ContainsBlock(block->GetFalseTarget())); + + if (isInsideExitTest) + { + // The downwards loop transformation may be able to remove this use. + // Here we duplicate some of the logic from + // optMakeExitTestDownwardsCounted to predict whether that will happen. + GenTree* jtrue = block->lastStmt()->GetRootNode(); + GenTree* cond = jtrue->gtGetOp1(); + + // Is the exit test changeable? + if (!m_comp->optCanAndShouldChangeExitTest(cond, /* dump */ false)) + { + return false; + } + + // Does the exit dominate all backedges such that we can place IV + // updates before it? + for (FlowEdge* edge : m_loop->BackEdges()) + { + if (!m_comp->m_domTree->Dominates(block, edge->getSourceBlock())) + { + return false; + } + } + + // Will the exit only run once per iteration? + if (m_loop->MayExecuteBlockMultipleTimesPerIteration(block)) + { + return false; + } + + // Can we compute the trip count from the exit test? + if (m_scevContext.ComputeExitNotTakenCount(block) == nullptr) + { + return false; + } + + // If all of those things are true, we are most likely going to be able + // to convert the exit test to a down-counting one after we have removed the other uses of the IV. + return true; + } + + return false; +} + //------------------------------------------------------------------------ // AdvanceCursors: Advance cursors stored in "cursors" and store the advanced // result in "nextCursors". @@ -1557,8 +1691,7 @@ void StrengthReductionContext::AdvanceCursors(ArrayStack* cursors, A CursorInfo& cursor = cursors->BottomRef(i); CursorInfo& nextCursor = nextCursors->BottomRef(i); - assert((nextCursor.Block == cursor.Block) && (nextCursor.Stmt == cursor.Stmt) && - (nextCursor.IsInsideExitTest == cursor.IsInsideExitTest)); + assert((nextCursor.Block == cursor.Block) && (nextCursor.Stmt == cursor.Stmt)); nextCursor.Tree = cursor.Tree; do @@ -1601,8 +1734,7 @@ void StrengthReductionContext::AdvanceCursors(ArrayStack* cursors, A for (int i = 0; i < nextCursors->Height(); i++) { CursorInfo& nextCursor = nextCursors->BottomRef(i); - printf(" [%d] [%06u]%s: ", i, nextCursor.Tree == nullptr ? 0 : Compiler::dspTreeID(nextCursor.Tree), - nextCursor.IsInsideExitTest ? " (in-test)" : ""); + printf(" [%d] [%06u]%s: ", i, nextCursor.Tree == nullptr ? 0 : Compiler::dspTreeID(nextCursor.Tree)); if (nextCursor.IV == nullptr) { printf(""); @@ -1646,13 +1778,6 @@ bool StrengthReductionContext::CheckAdvancedCursors(ArrayStack* curs { CursorInfo& cursor = cursors->BottomRef(i); - // Uses inside the exit test only need to opportunistically - // match. We check these after. - if (cursor.IsInsideExitTest) - { - continue; - } - if ((cursor.IV != nullptr) && ((*nextIV == nullptr) || Scev::Equals(cursor.IV, *nextIV))) { *nextIV = cursor.IV; @@ -1663,50 +1788,6 @@ bool StrengthReductionContext::CheckAdvancedCursors(ArrayStack* curs return false; } - // Now check all exit test uses. - for (int i = 0; i < cursors->Height(); i++) - { - CursorInfo& cursor = cursors->BottomRef(i); - - if (!cursor.IsInsideExitTest) - { - continue; - } - - if ((cursor.IV != nullptr) && ((*nextIV == nullptr) || Scev::Equals(cursor.IV, *nextIV))) - { - *nextIV = cursor.IV; - continue; - } - - // Use inside exit test does not match. - if (derivedLevel <= 1) - { - // We weren't able to advance the match in the exit test at all; in - // this situation we expect the downwards optimization to be able - // to remove the use of the primary IV, so this is ok. Remove the - // cursor pointing to the use inside the test. - JITDUMP(" [%d] does not match, but is inside loop test; ignoring mismatch and removing cursor\n", i); - - std::swap(m_cursors1.BottomRef(i), m_cursors1.TopRef(0)); - std::swap(m_cursors2.BottomRef(i), m_cursors2.TopRef(0)); - - m_cursors1.Pop(); - m_cursors2.Pop(); - - i--; - } - else - { - // We already found a derived IV in the exit test that matches, so - // stop here and allow the replacement to replace the uses of the - // current derived IV, including the one in the exit test - // statement. - JITDUMP(" [%d] does not match; will not advance\n", i); - return false; - } - } - return *nextIV != nullptr; } diff --git a/src/coreclr/vm/ceeload.cpp b/src/coreclr/vm/ceeload.cpp index eb49ed746edc2a..75e93bcdbee395 100644 --- a/src/coreclr/vm/ceeload.cpp +++ b/src/coreclr/vm/ceeload.cpp @@ -344,6 +344,7 @@ Module::Module(Assembly *pAssembly, PEAssembly *pPEAssembly) m_pAssembly = pAssembly; m_pPEAssembly = pPEAssembly; m_dwTransientFlags = CLASSES_FREED; + m_pDynamicMetadata = (TADDR)NULL; pPEAssembly->AddRef(); } @@ -3748,7 +3749,6 @@ ReflectionModule::ReflectionModule(Assembly *pAssembly, PEAssembly *pPEAssembly) m_pInMemoryWriter = NULL; m_sdataSection = NULL; m_pCeeFileGen = NULL; - m_pDynamicMetadata = NULL; } HRESULT STDMETHODCALLTYPE CreateICeeGen(REFIID riid, void **pCeeGen); @@ -3806,8 +3806,8 @@ void ReflectionModule::Destruct() Module::Destruct(); - delete m_pDynamicMetadata; - m_pDynamicMetadata = NULL; + delete (uint32_t*)m_pDynamicMetadata; + m_pDynamicMetadata = (TADDR)NULL; m_CrstLeafLock.Destroy(); } @@ -3923,17 +3923,19 @@ void ReflectionModule::CaptureModuleMetaDataToMemory() IfFailThrow(hr); // Operate on local data, and then persist it into the module once we know it's valid. - NewHolder pBuffer(new SBuffer()); + NewArrayHolder pBuffer(new uint8_t[numBytes + sizeof(DynamicMetadata)]); _ASSERTE(pBuffer != NULL); // allocation would throw first + DynamicMetadata *pDynamicMetadata = (DynamicMetadata*)(uint8_t*)pBuffer; + // ReflectionModule is still in a consistent state, and now we're just operating on local data to // assemble the new metadata buffer. If this fails, then worst case is that metadata does not include // recently generated classes. // Caller ensures serialization that guarantees that the metadata doesn't grow underneath us. - BYTE * pRawData = pBuffer->OpenRawBuffer(numBytes); + BYTE * pRawData = &pDynamicMetadata->Data[0]; hr = pEmitter->SaveToMemory(pRawData, numBytes); - pBuffer->CloseRawBuffer(); + pDynamicMetadata->Size = numBytes; IfFailThrow(hr); @@ -3941,9 +3943,9 @@ void ReflectionModule::CaptureModuleMetaDataToMemory() { CrstHolder ch(&m_CrstLeafLock); - delete m_pDynamicMetadata; + delete (uint32_t*)m_pDynamicMetadata; - m_pDynamicMetadata = pBuffer.Extract(); + m_pDynamicMetadata = (TADDR)pBuffer.Extract(); } // @@ -3965,7 +3967,7 @@ void ReflectionModule::CaptureModuleMetaDataToMemory() // Notes: // Only used by the debugger, so only accessible via DAC. // The buffer is updated via code:ReflectionModule.CaptureModuleMetaDataToMemory -PTR_SBuffer ReflectionModule::GetDynamicMetadataBuffer() const +TADDR ReflectionModule::GetDynamicMetadataBuffer() const { SUPPORTS_DAC; diff --git a/src/coreclr/vm/ceeload.h b/src/coreclr/vm/ceeload.h index cb370bae893e0e..9bcae6e145d843 100644 --- a/src/coreclr/vm/ceeload.h +++ b/src/coreclr/vm/ceeload.h @@ -91,6 +91,20 @@ typedef DPTR(JITInlineTrackingMap) PTR_JITInlineTrackingMap; typedef DPTR(struct LookupMapBase) PTR_LookupMapBase; +struct DynamicMetadata +{ + uint32_t Size; + BYTE Data[0]; + template friend struct ::cdac_offsets; +}; + +template<> +struct cdac_offsets +{ + static constexpr size_t Size = offsetof(DynamicMetadata, Size); + static constexpr size_t Data = offsetof(DynamicMetadata, Data); +}; + struct LookupMapBase { DPTR(LookupMapBase) pNext; @@ -1588,6 +1602,15 @@ class Module : public ModuleBase PTR_Assembly *m_NativeMetadataAssemblyRefMap; + // Buffer of Metadata storage for dynamic modules. May be NULL. This provides a reasonable way for + // the debugger to get metadata of dynamic modules from out of process. + // A dynamic module will eagerly serialize its metadata to this buffer. + // This points at a uint32_t array. + // The first uint32_t is the number of bytes in the saved metadata + // Starting at the address of the second uint32_t value is the saved metadata itself +protected: + TADDR m_pDynamicMetadata; + public: #if !defined(DACCESS_COMPILE) PTR_Assembly GetNativeMetadataAssemblyRefFromCache(DWORD rid) @@ -1617,6 +1640,7 @@ struct cdac_offsets static constexpr size_t Flags = offsetof(Module, m_dwTransientFlags); static constexpr size_t LoaderAllocator = offsetof(Module, m_loaderAllocator); static constexpr size_t ThunkHeap = offsetof(Module, m_pThunkHeap); + static constexpr size_t DynamicMetadata = offsetof(Module, m_pDynamicMetadata); // Lookup map pointers static constexpr size_t FieldDefToDescMap = offsetof(Module, m_FieldDefToDescMap) + offsetof(LookupMap, pTable); @@ -1647,11 +1671,6 @@ class ReflectionModule : public Module // Simple Critical Section used for basic leaf-lock operatons. CrstExplicitInit m_CrstLeafLock; - // Buffer of Metadata storage for dynamic modules. May be NULL. This provides a reasonable way for - // the debugger to get metadata of dynamic modules from out of process. - // A dynamic module will eagerly serialize its metadata to this buffer. - PTR_SBuffer m_pDynamicMetadata; - #if !defined DACCESS_COMPILE ReflectionModule(Assembly *pAssembly, PEAssembly *pPEAssembly); #endif // !DACCESS_COMPILE @@ -1660,7 +1679,7 @@ class ReflectionModule : public Module #ifdef DACCESS_COMPILE // Accessor to expose m_pDynamicMetadata to debugger. - PTR_SBuffer GetDynamicMetadataBuffer() const; + TADDR GetDynamicMetadataBuffer() const; #endif #if !defined DACCESS_COMPILE diff --git a/src/coreclr/vm/class.h b/src/coreclr/vm/class.h index 1eff90b672bb0b..e281b8e365e7ed 100644 --- a/src/coreclr/vm/class.h +++ b/src/coreclr/vm/class.h @@ -1803,6 +1803,7 @@ class EEClass // DO NOT CREATE A NEW EEClass USING NEW! template<> struct cdac_offsets { + static constexpr size_t InternalCorElementType = offsetof(EEClass, m_NormType); static constexpr size_t MethodTable = offsetof(EEClass, m_pMethodTable); static constexpr size_t NumMethods = offsetof(EEClass, m_NumMethods); static constexpr size_t CorTypeAttr = offsetof(EEClass, m_dwAttrClass); @@ -1995,7 +1996,12 @@ class ArrayClass : public EEClass BOOL fForStubAsIL ); + template friend struct ::cdac_offsets; +}; +template<> struct cdac_offsets +{ + static constexpr size_t Rank = offsetof(ArrayClass, m_rank); }; inline EEClassLayoutInfo *EEClass::GetLayoutInfo() diff --git a/src/coreclr/vm/methodtable.cpp b/src/coreclr/vm/methodtable.cpp index 925112aafe107a..9209b3cc56ce7c 100644 --- a/src/coreclr/vm/methodtable.cpp +++ b/src/coreclr/vm/methodtable.cpp @@ -4832,7 +4832,7 @@ CorElementType MethodTable::GetSignatureCorElementType() // common cases of ELEMENT_TYPE_CLASS and ELEMENT_TYPE_VALUETYPE. CorElementType ret; - switch (GetFlag(enum_flag_Category_ElementTypeMask)) + switch (GetFlag(enum_flag_Category_Mask)) { case enum_flag_Category_Array: ret = ELEMENT_TYPE_ARRAY; @@ -4843,17 +4843,13 @@ CorElementType MethodTable::GetSignatureCorElementType() break; case enum_flag_Category_ValueType: + case enum_flag_Category_Nullable: + case enum_flag_Category_PrimitiveValueType: ret = ELEMENT_TYPE_VALUETYPE; break; - case enum_flag_Category_PrimitiveValueType: - // - // This is the only difference from MethodTable::GetInternalCorElementType() - // - if (IsTruePrimitive()) - ret = GetClass()->GetInternalCorElementType(); - else - ret = ELEMENT_TYPE_VALUETYPE; + case enum_flag_Category_TruePrimitive: + ret = GetClass()->GetInternalCorElementType(); break; default: diff --git a/src/coreclr/vm/methodtable.h b/src/coreclr/vm/methodtable.h index 55fb72b2911279..c372ac692f1121 100644 --- a/src/coreclr/vm/methodtable.h +++ b/src/coreclr/vm/methodtable.h @@ -293,9 +293,15 @@ struct GenericsDictInfo // Number of type parameters (NOT including those of superclasses). WORD m_wNumTyPars; + template friend struct ::cdac_offsets; }; // struct GenericsDictInfo typedef DPTR(GenericsDictInfo) PTR_GenericsDictInfo; +template<> +struct cdac_offsets +{ + static constexpr size_t NumTypeArgs = offsetof(GenericsDictInfo, m_wNumTyPars); +}; // These various statics structures exist directly before the MethodTableAuxiliaryData @@ -3943,6 +3949,7 @@ template<> struct cdac_offsets static constexpr size_t ParentMethodTable = offsetof(MethodTable, m_pParentMethodTable); static constexpr size_t NumInterfaces = offsetof(MethodTable, m_wNumInterfaces); static constexpr size_t NumVirtuals = offsetof(MethodTable, m_wNumVirtuals); + static constexpr size_t PerInstInfo = offsetof(MethodTable, m_pPerInstInfo); }; #ifndef CROSSBITNESS_COMPILE diff --git a/src/coreclr/vm/typedesc.h b/src/coreclr/vm/typedesc.h index e8d7526bd0b16c..2518f86d5adafb 100644 --- a/src/coreclr/vm/typedesc.h +++ b/src/coreclr/vm/typedesc.h @@ -204,8 +204,14 @@ class TypeDesc // internal RuntimeType object handle RUNTIMETYPEHANDLE m_hExposedClassObject; + template friend struct ::cdac_offsets; }; +template<> +struct cdac_offsets +{ + static constexpr size_t TypeAndFlags = offsetof(TypeDesc, m_typeAndFlags); +}; /*************************************************************************/ // This variant is used for parameterized types that have exactly one argument @@ -263,6 +269,13 @@ class ParamTypeDesc : public TypeDesc { // The type that is being modified TypeHandle m_Arg; + template friend struct ::cdac_offsets; +}; + +template<> +struct cdac_offsets +{ + static constexpr size_t TypeArg = offsetof(ParamTypeDesc, m_Arg); }; /*************************************************************************/ @@ -381,6 +394,15 @@ class TypeVarTypeDesc : public TypeDesc // index within declaring type or method, numbered from zero unsigned int m_index; + + template friend struct ::cdac_offsets; +}; + +template<> +struct cdac_offsets +{ + static constexpr size_t Module = offsetof(TypeVarTypeDesc, m_pModule); + static constexpr size_t Token = offsetof(TypeVarTypeDesc, m_token); }; /*************************************************************************/ @@ -467,6 +489,16 @@ class FnPtrTypeDesc : public TypeDesc // Return type first, then argument types TypeHandle m_RetAndArgTypes[1]; + + template friend struct ::cdac_offsets; }; // class FnPtrTypeDesc +template<> +struct cdac_offsets +{ + static constexpr size_t NumArgs = offsetof(FnPtrTypeDesc, m_NumArgs); + static constexpr size_t RetAndArgTypes = offsetof(FnPtrTypeDesc, m_RetAndArgTypes); + static constexpr size_t CallConv = offsetof(FnPtrTypeDesc, m_CallConv); +}; + #endif // TYPEDESC_H diff --git a/src/installer/pkg/sfx/installers/dotnet-runtime-deps/dotnet-runtime-deps-debian.proj b/src/installer/pkg/sfx/installers/dotnet-runtime-deps/dotnet-runtime-deps-debian.proj index be0713f05f7507..f48ee38a1d78c0 100644 --- a/src/installer/pkg/sfx/installers/dotnet-runtime-deps/dotnet-runtime-deps-debian.proj +++ b/src/installer/pkg/sfx/installers/dotnet-runtime-deps/dotnet-runtime-deps-debian.proj @@ -6,9 +6,9 @@ - + - \ No newline at end of file + diff --git a/src/libraries/Common/tests/System/Net/Configuration.Certificates.Dynamic.cs b/src/libraries/Common/tests/System/Net/Configuration.Certificates.Dynamic.cs index 4f1e13ede0eca1..d89f914191f0ee 100644 --- a/src/libraries/Common/tests/System/Net/Configuration.Certificates.Dynamic.cs +++ b/src/libraries/Common/tests/System/Net/Configuration.Certificates.Dynamic.cs @@ -6,7 +6,6 @@ using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; using System.Security.Cryptography.X509Certificates.Tests.Common; -using Test.Cryptography; namespace System.Net.Test.Common { diff --git a/src/libraries/Common/tests/System/Net/Configuration.Certificates.cs b/src/libraries/Common/tests/System/Net/Configuration.Certificates.cs index d3b0cf224dae30..34599f1644bf1f 100644 --- a/src/libraries/Common/tests/System/Net/Configuration.Certificates.cs +++ b/src/libraries/Common/tests/System/Net/Configuration.Certificates.cs @@ -7,7 +7,6 @@ using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; using System.Threading; -using Test.Cryptography; using Xunit; namespace System.Net.Test.Common diff --git a/src/libraries/Common/tests/System/Security/Cryptography/CngKeyWrapper.cs b/src/libraries/Common/tests/System/Security/Cryptography/CngKeyWrapper.cs new file mode 100644 index 00000000000000..cba4da4a192770 --- /dev/null +++ b/src/libraries/Common/tests/System/Security/Cryptography/CngKeyWrapper.cs @@ -0,0 +1,75 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#nullable enable + +using Xunit; +using System; +using System.Runtime.CompilerServices; +using System.Security.Cryptography; + +namespace Test.Cryptography +{ + internal sealed class CngKeyWrapper : IDisposable + { + private CngKeyWrapper( + CngAlgorithm algorithm, + CngKeyCreationParameters cngCreationParameters, + string? keySuffix = null, + [CallerMemberName] string? testName = null) + { + Key = CngKey.Create(algorithm, $"{testName}{algorithm.Algorithm}{keySuffix}", cngCreationParameters); + } + + public static CngKeyWrapper CreateMicrosoftPlatformCryptoProvider( + CngAlgorithm algorithm, + string? keySuffix = null, + [CallerMemberName] string? testName = null, + CngKeyCreationOptions creationOption = CngKeyCreationOptions.None, + params CngProperty[] additionalParameters) + { + const string MicrosoftPlatformCryptoProvider = "Microsoft Platform Crypto Provider"; + +#if NETFRAMEWORK + CngProvider cngProvider = new(MicrosoftPlatformCryptoProvider); +#else + Assert.Equal(MicrosoftPlatformCryptoProvider, CngProvider.MicrosoftPlatformCryptoProvider.Provider); + CngProvider cngProvider = CngProvider.MicrosoftPlatformCryptoProvider; +#endif + CngKeyCreationParameters cngCreationParameters = new() + { + Provider = cngProvider, + KeyCreationOptions = creationOption | CngKeyCreationOptions.OverwriteExistingKey, + }; + + foreach (CngProperty parameter in additionalParameters) + { + cngCreationParameters.Parameters.Add(parameter); + } + + return new CngKeyWrapper(algorithm, cngCreationParameters, keySuffix, testName); + } + + public static CngKeyWrapper CreateMicrosoftSoftwareKeyStorageProvider( + CngAlgorithm algorithm, + CngKeyCreationOptions creationOption, + string? keySuffix = null, + [CallerMemberName] string? testName = null) + { + CngKeyCreationParameters cngCreationParameters = new() + { + Provider = CngProvider.MicrosoftSoftwareKeyStorageProvider, + KeyCreationOptions = creationOption | CngKeyCreationOptions.OverwriteExistingKey, + }; + + return new CngKeyWrapper(algorithm, cngCreationParameters, keySuffix, testName); + } + + public CngKey Key { get; } + + public void Dispose() + { + Key.Delete(); + } + } +} diff --git a/src/libraries/Common/tests/System/Security/Cryptography/CngPlatformProviderKey.cs b/src/libraries/Common/tests/System/Security/Cryptography/CngPlatformProviderKey.cs deleted file mode 100644 index b33636ae957855..00000000000000 --- a/src/libraries/Common/tests/System/Security/Cryptography/CngPlatformProviderKey.cs +++ /dev/null @@ -1,39 +0,0 @@ -// 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.Runtime.CompilerServices; -using System.Security.Cryptography; - -namespace Test.Cryptography -{ - internal sealed class CngPlatformProviderKey : IDisposable - { - public CngPlatformProviderKey( - CngAlgorithm algorithm, - string keySuffix = null, - [CallerMemberName] string testName = null, - params CngProperty[] additionalParameters) - { - CngKeyCreationParameters cngCreationParameters = new CngKeyCreationParameters - { - Provider = CngProvider.MicrosoftPlatformCryptoProvider, - KeyCreationOptions = CngKeyCreationOptions.OverwriteExistingKey, - }; - - foreach (CngProperty parameter in additionalParameters) - { - cngCreationParameters.Parameters.Add(parameter); - } - - Key = CngKey.Create(algorithm, $"{testName}{algorithm.Algorithm}{keySuffix}", cngCreationParameters); - } - - internal CngKey Key { get; } - - public void Dispose() - { - Key.Delete(); - } - } -} diff --git a/src/libraries/Common/tests/System/Security/Cryptography/PlatformSupport.cs b/src/libraries/Common/tests/System/Security/Cryptography/PlatformSupport.cs index 7be1321023e2bf..6be4ed60277044 100644 --- a/src/libraries/Common/tests/System/Security/Cryptography/PlatformSupport.cs +++ b/src/libraries/Common/tests/System/Security/Cryptography/PlatformSupport.cs @@ -37,19 +37,11 @@ static bool DetermineAlgorithmFunctional(CngAlgorithm algorithm) return false; } #endif - - CngKey key = null; - try { - key = CngKey.Create( + using CngKeyWrapper key = CngKeyWrapper.CreateMicrosoftPlatformCryptoProvider( algorithm, - $"{nameof(PlatformCryptoProviderFunctional)}{algorithm.Algorithm}Key", - new CngKeyCreationParameters - { - Provider = new CngProvider("Microsoft Platform Crypto Provider"), - KeyCreationOptions = CngKeyCreationOptions.OverwriteExistingKey, - }); + keySuffix: $"{algorithm.Algorithm}Key"); return true; } @@ -57,10 +49,35 @@ static bool DetermineAlgorithmFunctional(CngAlgorithm algorithm) { return false; } - finally - { - key?.Delete(); - } + } + } + + private static bool CheckIfVbsAvailable() + { +#if !NETFRAMEWORK + if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + return false; + } +#endif + + try + { + const CngKeyCreationOptions RequireVbs = (CngKeyCreationOptions)0x00020000; +#if !NETFRAMEWORK + Assert.Equal(CngKeyCreationOptions.RequireVbs, RequireVbs); +#endif + + using CngKeyWrapper key = CngKeyWrapper.CreateMicrosoftSoftwareKeyStorageProvider( + CngAlgorithm.ECDsaP256, + RequireVbs, + keySuffix: $"{CngAlgorithm.ECDsaP256.Algorithm}Key"); + + return true; + } + catch (CryptographicException) + { + return false; } } @@ -83,5 +100,8 @@ static bool DetermineAlgorithmFunctional(CngAlgorithm algorithm) internal static bool PlatformCryptoProviderFunctionalP256 => PlatformCryptoProviderFunctional(CngAlgorithm.ECDsaP256); internal static bool PlatformCryptoProviderFunctionalP384 => PlatformCryptoProviderFunctional(CngAlgorithm.ECDsaP384); internal static bool PlatformCryptoProviderFunctionalRsa => PlatformCryptoProviderFunctional(CngAlgorithm.Rsa); + + private static bool? s_isVbsAvailable; + internal static bool IsVbsAvailable => s_isVbsAvailable ??= CheckIfVbsAvailable(); } } diff --git a/src/libraries/Microsoft.Bcl.Cryptography/tests/Microsoft.Bcl.Cryptography.Tests.csproj b/src/libraries/Microsoft.Bcl.Cryptography/tests/Microsoft.Bcl.Cryptography.Tests.csproj index a08fb7f599bd17..265d2a512e0e96 100644 --- a/src/libraries/Microsoft.Bcl.Cryptography/tests/Microsoft.Bcl.Cryptography.Tests.csproj +++ b/src/libraries/Microsoft.Bcl.Cryptography/tests/Microsoft.Bcl.Cryptography.Tests.csproj @@ -12,6 +12,8 @@ Link="CommonTest\System\Security\Cryptography\ByteUtils.cs" /> + - diff --git a/src/libraries/System.Net.Http.WinHttpHandler/tests/FunctionalTests/System.Net.Http.WinHttpHandler.Functional.Tests.csproj b/src/libraries/System.Net.Http.WinHttpHandler/tests/FunctionalTests/System.Net.Http.WinHttpHandler.Functional.Tests.csproj index 89a7007b2f6f33..7640a70f87b4a6 100644 --- a/src/libraries/System.Net.Http.WinHttpHandler/tests/FunctionalTests/System.Net.Http.WinHttpHandler.Functional.Tests.csproj +++ b/src/libraries/System.Net.Http.WinHttpHandler/tests/FunctionalTests/System.Net.Http.WinHttpHandler.Functional.Tests.csproj @@ -133,8 +133,6 @@ Link="Common\System\Net\Http\SyncBlockingContent.cs" /> - - - diff --git a/src/libraries/System.Net.Requests/tests/System.Net.Requests.Tests.csproj b/src/libraries/System.Net.Requests/tests/System.Net.Requests.Tests.csproj index 35e9a3f81011f0..a1574b47b66267 100644 --- a/src/libraries/System.Net.Requests/tests/System.Net.Requests.Tests.csproj +++ b/src/libraries/System.Net.Requests/tests/System.Net.Requests.Tests.csproj @@ -38,8 +38,6 @@ Link="Common\System\Net\Http\GenericLoopbackServer.cs" /> - diff --git a/src/libraries/System.Net.Security/tests/FunctionalTests/System.Net.Security.Tests.csproj b/src/libraries/System.Net.Security/tests/FunctionalTests/System.Net.Security.Tests.csproj index 3d55a2c888a263..41577cfe2496cb 100644 --- a/src/libraries/System.Net.Security/tests/FunctionalTests/System.Net.Security.Tests.csproj +++ b/src/libraries/System.Net.Security/tests/FunctionalTests/System.Net.Security.Tests.csproj @@ -91,8 +91,6 @@ Link="Common\System\Net\EventSourceTestLogging.cs" /> - - diff --git a/src/libraries/System.Net.WebSockets.Client/tests/System.Net.WebSockets.Client.Tests.csproj b/src/libraries/System.Net.WebSockets.Client/tests/System.Net.WebSockets.Client.Tests.csproj index 35780f9d7497c4..0c07922eb10ec9 100644 --- a/src/libraries/System.Net.WebSockets.Client/tests/System.Net.WebSockets.Client.Tests.csproj +++ b/src/libraries/System.Net.WebSockets.Client/tests/System.Net.WebSockets.Client.Tests.csproj @@ -47,7 +47,6 @@ - diff --git a/src/libraries/System.Net.WebSockets.Client/tests/wasm/System.Net.WebSockets.Client.Wasm.Tests.csproj b/src/libraries/System.Net.WebSockets.Client/tests/wasm/System.Net.WebSockets.Client.Wasm.Tests.csproj index c4b8a5a4545e83..296136cf1c27cd 100644 --- a/src/libraries/System.Net.WebSockets.Client/tests/wasm/System.Net.WebSockets.Client.Wasm.Tests.csproj +++ b/src/libraries/System.Net.WebSockets.Client/tests/wasm/System.Net.WebSockets.Client.Wasm.Tests.csproj @@ -40,8 +40,6 @@ Link="Common\System\Net\Http\LoopbackServer.cs" /> - diff --git a/src/libraries/System.Security.Cryptography.Cng/tests/ECDiffieHellmanCngTests.cs b/src/libraries/System.Security.Cryptography.Cng/tests/ECDiffieHellmanCngTests.cs index 488043a01ea44f..3ca8e6d9912ddf 100644 --- a/src/libraries/System.Security.Cryptography.Cng/tests/ECDiffieHellmanCngTests.cs +++ b/src/libraries/System.Security.Cryptography.Cng/tests/ECDiffieHellmanCngTests.cs @@ -193,8 +193,8 @@ public static void HashAlgorithm_SupportsOtherECDHImplementations() [OuterLoop("Hardware backed key generation takes several seconds.")] public static void PlatformCryptoProvider_DeriveKeyMaterial() { - using (CngPlatformProviderKey platformKey1 = new CngPlatformProviderKey(CngAlgorithm.ECDiffieHellmanP256, "key1")) - using (CngPlatformProviderKey platformKey2 = new CngPlatformProviderKey(CngAlgorithm.ECDiffieHellmanP256, "key2")) + using (CngKeyWrapper platformKey1 = CngKeyWrapper.CreateMicrosoftPlatformCryptoProvider(CngAlgorithm.ECDiffieHellmanP256, "key1")) + using (CngKeyWrapper platformKey2 = CngKeyWrapper.CreateMicrosoftPlatformCryptoProvider(CngAlgorithm.ECDiffieHellmanP256, "key2")) using (ECDiffieHellmanCng ecdhCng1 = new ECDiffieHellmanCng(platformKey1.Key)) using (ECDiffieHellmanCng ecdhCng2 = new ECDiffieHellmanCng(platformKey2.Key)) { diff --git a/src/libraries/System.Security.Cryptography.Cng/tests/PropertyTests.cs b/src/libraries/System.Security.Cryptography.Cng/tests/PropertyTests.cs index eb3db7e26de787..41414361805964 100644 --- a/src/libraries/System.Security.Cryptography.Cng/tests/PropertyTests.cs +++ b/src/libraries/System.Security.Cryptography.Cng/tests/PropertyTests.cs @@ -17,7 +17,7 @@ public static void CreatePersisted_PlatformEccKeyHasKeySize_P256(string algorith { CngAlgorithm cngAlgorithm = new CngAlgorithm(algorithm); - using (CngPlatformProviderKey platformKey = new CngPlatformProviderKey(cngAlgorithm)) + using (CngKeyWrapper platformKey = CngKeyWrapper.CreateMicrosoftPlatformCryptoProvider(cngAlgorithm)) { Assert.Equal(256, platformKey.Key.KeySize); } @@ -31,7 +31,7 @@ public static void CreatePersisted_PlatformEccKeyHasKeySize_P384(string algorith { CngAlgorithm cngAlgorithm = new CngAlgorithm(algorithm); - using (CngPlatformProviderKey platformKey = new CngPlatformProviderKey(cngAlgorithm)) + using (CngKeyWrapper platformKey = CngKeyWrapper.CreateMicrosoftPlatformCryptoProvider(cngAlgorithm)) { Assert.Equal(384, platformKey.Key.KeySize); } @@ -44,7 +44,7 @@ public static void CreatePersisted_PlatformEccKeyHasKeySize_P384(string algorith public static void CreatePersisted_PlatformRsaKeyHasKeySize(int keySize) { CngProperty keyLengthProperty = new CngProperty("Length", BitConverter.GetBytes(keySize), CngPropertyOptions.None); - CngPlatformProviderKey platformKey = new CngPlatformProviderKey( + CngKeyWrapper platformKey = CngKeyWrapper.CreateMicrosoftPlatformCryptoProvider( CngAlgorithm.Rsa, keySuffix: keySize.ToString(), additionalParameters: keyLengthProperty); diff --git a/src/libraries/System.Security.Cryptography.Cng/tests/System.Security.Cryptography.Cng.Tests.csproj b/src/libraries/System.Security.Cryptography.Cng/tests/System.Security.Cryptography.Cng.Tests.csproj index 18e8462a5c6eff..045b1640017a19 100644 --- a/src/libraries/System.Security.Cryptography.Cng/tests/System.Security.Cryptography.Cng.Tests.csproj +++ b/src/libraries/System.Security.Cryptography.Cng/tests/System.Security.Cryptography.Cng.Tests.csproj @@ -39,8 +39,8 @@ Link="CommonTest\System\Security\Cryptography\AlgorithmImplementations\ECDiffieHellman\ECDiffieHellmanFactory.cs" /> - + + + + diff --git a/src/libraries/System.Security.Cryptography.Xml/tests/System.Security.Cryptography.Xml.Tests.csproj b/src/libraries/System.Security.Cryptography.Xml/tests/System.Security.Cryptography.Xml.Tests.csproj index b8ad085cbadc1f..865209228719e6 100644 --- a/src/libraries/System.Security.Cryptography.Xml/tests/System.Security.Cryptography.Xml.Tests.csproj +++ b/src/libraries/System.Security.Cryptography.Xml/tests/System.Security.Cryptography.Xml.Tests.csproj @@ -7,6 +7,8 @@ + diff --git a/src/libraries/System.Security.Cryptography/ref/System.Security.Cryptography.cs b/src/libraries/System.Security.Cryptography/ref/System.Security.Cryptography.cs index c19cedc9ff4519..11409ac9857c14 100644 --- a/src/libraries/System.Security.Cryptography/ref/System.Security.Cryptography.cs +++ b/src/libraries/System.Security.Cryptography/ref/System.Security.Cryptography.cs @@ -427,6 +427,9 @@ public enum CngKeyCreationOptions None = 0, MachineKey = 32, OverwriteExistingKey = 128, + PreferVbs = 65536, + RequireVbs = 131072, + UsePerBootKey = 262144, } public sealed partial class CngKeyCreationParameters { diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/CngKeyCreationOptions.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/CngKeyCreationOptions.cs index 338076d1dbc057..f3f3e7fb423219 100644 --- a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/CngKeyCreationOptions.cs +++ b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/CngKeyCreationOptions.cs @@ -15,5 +15,8 @@ public enum CngKeyCreationOptions : int None = 0x00000000, MachineKey = 0x00000020, // NCRYPT_MACHINE_KEY_FLAG OverwriteExistingKey = 0x00000080, // NCRYPT_OVERWRITE_KEY_FLAG + PreferVbs = 0x00010000, // NCRYPT_PREFER_VBS_FLAG + RequireVbs = 0x00020000, // NCRYPT_REQUIRE_VBS_FLAG + UsePerBootKey = 0x00040000, // NCRYPT_USE_PER_BOOT_KEY_FLAG } } diff --git a/src/libraries/System.Security.Cryptography/tests/CngKeyTests.cs b/src/libraries/System.Security.Cryptography/tests/CngKeyTests.cs new file mode 100644 index 00000000000000..1fd515beac292b --- /dev/null +++ b/src/libraries/System.Security.Cryptography/tests/CngKeyTests.cs @@ -0,0 +1,118 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections.Generic; +using System.Linq; +using System.Security.Cryptography.X509Certificates; +using Test.Cryptography; +using Xunit; + +namespace System.Security.Cryptography.Tests +{ + public class CngKeyTests + { + [ConditionalTheory(typeof(PlatformSupport), nameof(PlatformSupport.IsVbsAvailable))] + [InlineData(CngKeyCreationOptions.PreferVbs)] + [InlineData(CngKeyCreationOptions.RequireVbs)] + [InlineData(CngKeyCreationOptions.UsePerBootKey)] + public void CreateVbsKey_SignAndVerify(CngKeyCreationOptions creationOption) + { + using (CngKeyWrapper key = CngKeyWrapper.CreateMicrosoftSoftwareKeyStorageProvider( + CngAlgorithm.ECDsaP256, + creationOption, + keySuffix: creationOption.ToString())) + { + SignAndVerifyECDsa(key.Key); + } + } + + [ConditionalTheory(typeof(PlatformSupport), nameof(PlatformSupport.IsVbsAvailable))] + [InlineData(CngKeyCreationOptions.PreferVbs)] + [InlineData(CngKeyCreationOptions.RequireVbs)] + [InlineData(CngKeyCreationOptions.UsePerBootKey)] + public void CreateVbsKey_KeyIsNotExportable(CngKeyCreationOptions creationOption) + { + using (CngKeyWrapper key = CngKeyWrapper.CreateMicrosoftSoftwareKeyStorageProvider( + CngAlgorithm.ECDsaP256, + creationOption, + keySuffix: creationOption.ToString())) + { + using (ECDsaCng ecdsa = new ECDsaCng(key.Key)) + { + Assert.ThrowsAny(() => ecdsa.ExportExplicitParameters(includePrivateParameters: true)); + } + } + } + + [ConditionalTheory(typeof(PlatformSupport), nameof(PlatformSupport.IsVbsAvailable))] + [InlineData(CngKeyCreationOptions.PreferVbs)] + [InlineData(CngKeyCreationOptions.RequireVbs)] + [InlineData(CngKeyCreationOptions.UsePerBootKey)] + [InlineData(CngKeyCreationOptions.PreferVbs | CngKeyCreationOptions.UsePerBootKey)] + [InlineData(CngKeyCreationOptions.RequireVbs | CngKeyCreationOptions.UsePerBootKey)] + public void CreateVbsKey_SoftwareKeyStorageProviderFlagsOnWrongProvider(CngKeyCreationOptions creationOption) + { + Assert.ThrowsAny(() => CngKeyWrapper.CreateMicrosoftPlatformCryptoProvider( + CngAlgorithm.ECDsaP256, + creationOption: creationOption, + keySuffix: creationOption.ToString())); + } + + private static void SignAndVerifyECDsa(CngKey key) + { + using (ECDsaCng ecdsa = new ECDsaCng(key)) + { + byte[] data = { 12, 11, 02, 08, 25, 14, 11, 18, 16 }; + + // using key directly + byte[] signature = ecdsa.SignData(data, HashAlgorithmName.SHA256); + VerifyTests(ecdsa, data, signature); + + // through cert + CertificateRequest req = new CertificateRequest("CN=potato", ecdsa, HashAlgorithmName.SHA256); + DateTimeOffset now = DateTimeOffset.UtcNow; + using (X509Certificate2 cert = req.CreateSelfSigned(now, now.AddHours(1))) + using (ECDsa certKey = cert.GetECDsaPrivateKey()) + using (ECDsa certPubKey = cert.GetECDsaPublicKey()) + { + Assert.NotNull(certKey); + Assert.NotNull(certPubKey); + + VerifyTests(certPubKey, data, signature); + VerifyTests(certKey, data, signature); + + Assert.ThrowsAny(() => certPubKey.SignData(data, HashAlgorithmName.SHA256)); + signature = certKey.SignData(data, HashAlgorithmName.SHA256); + + VerifyTests(ecdsa, data, signature); + VerifyTests(certPubKey, data, signature); + VerifyTests(certKey, data, signature); + } + + // we can still sign/verify after disposing the cert + signature = ecdsa.SignData(data, HashAlgorithmName.SHA256); + VerifyTests(ecdsa, data, signature); + } + } + + private static void VerifyTests(ECDsa ecdsa, byte[] data, byte[] signature) + { + bool valid = ecdsa.VerifyData(data, signature, HashAlgorithmName.SHA256); + Assert.True(valid, "signature is not valid"); + + signature[0] ^= 0xFF; + valid = ecdsa.VerifyData(data, signature, HashAlgorithmName.SHA256); + Assert.False(valid, "tampered signature is valid"); + signature[0] ^= 0xFF; + + data[0] ^= 0xFF; + valid = ecdsa.VerifyData(data, signature, HashAlgorithmName.SHA256); + Assert.False(valid, "tampered data is verified as valid"); + data[0] ^= 0xFF; + + // we call it second time and expect no issues with validation + valid = ecdsa.VerifyData(data, signature, HashAlgorithmName.SHA256); + Assert.True(valid, "signature is not valid"); + } + } +} diff --git a/src/libraries/System.Security.Cryptography/tests/System.Security.Cryptography.Tests.csproj b/src/libraries/System.Security.Cryptography/tests/System.Security.Cryptography.Tests.csproj index 84f0fa2947c49c..0dea3f2b5f48b3 100644 --- a/src/libraries/System.Security.Cryptography/tests/System.Security.Cryptography.Tests.csproj +++ b/src/libraries/System.Security.Cryptography/tests/System.Security.Cryptography.Tests.csproj @@ -219,8 +219,8 @@ Link="CommonTest\System\Security\Cryptography\509Certificates\X509CertificateLoaderTests.cs" /> - + + diff --git a/src/libraries/System.Security.Cryptography/tests/X509Certificates/CertTests.cs b/src/libraries/System.Security.Cryptography/tests/X509Certificates/CertTests.cs index 6f8a0b6c0bf871..64ded87aa4e041 100644 --- a/src/libraries/System.Security.Cryptography/tests/X509Certificates/CertTests.cs +++ b/src/libraries/System.Security.Cryptography/tests/X509Certificates/CertTests.cs @@ -864,7 +864,7 @@ public static void CertificateSha3Signed() [OuterLoop("Hardware backed key generation takes several seconds.", ~TestPlatforms.Browser)] public static void CreateCertificate_MicrosoftPlatformCryptoProvider_EcdsaKey() { - using (CngPlatformProviderKey platformKey = new CngPlatformProviderKey(CngAlgorithm.ECDsaP256)) + using (CngKeyWrapper platformKey = CngKeyWrapper.CreateMicrosoftPlatformCryptoProvider(CngAlgorithm.ECDsaP256)) using (ECDsaCng ecdsa = new ECDsaCng(platformKey.Key)) { CertificateRequest req = new CertificateRequest("CN=potato", ecdsa, HashAlgorithmName.SHA256); @@ -885,7 +885,7 @@ public static void CreateCertificate_MicrosoftPlatformCryptoProvider_EcdsaKey() [OuterLoop("Hardware backed key generation takes several seconds.", ~TestPlatforms.Browser)] public static void CreateCertificate_MicrosoftPlatformCryptoProvider_RsaKey() { - using (CngPlatformProviderKey platformKey = new CngPlatformProviderKey(CngAlgorithm.Rsa)) + using (CngKeyWrapper platformKey = CngKeyWrapper.CreateMicrosoftPlatformCryptoProvider(CngAlgorithm.Rsa)) using (RSACng rsa = new RSACng(platformKey.Key)) { CertificateRequest req = new CertificateRequest("CN=potato", rsa, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1); diff --git a/src/native/managed/cdacreader/src/Constants.cs b/src/native/managed/cdacreader/src/Constants.cs index 5c95db68d227f9..6ca10e46a949bb 100644 --- a/src/native/managed/cdacreader/src/Constants.cs +++ b/src/native/managed/cdacreader/src/Constants.cs @@ -17,5 +17,8 @@ internal static class Globals internal const string SOSBreakingChangeVersion = nameof(SOSBreakingChangeVersion); internal const string FreeObjectMethodTable = nameof(FreeObjectMethodTable); + + internal const string MiniMetaDataBuffAddress = nameof(MiniMetaDataBuffAddress); + internal const string MiniMetaDataBuffMaxSize = nameof(MiniMetaDataBuffMaxSize); } } diff --git a/src/native/managed/cdacreader/src/Contracts/DacStreams.cs b/src/native/managed/cdacreader/src/Contracts/DacStreams.cs new file mode 100644 index 00000000000000..167c7f89ad2bfd --- /dev/null +++ b/src/native/managed/cdacreader/src/Contracts/DacStreams.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 System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Microsoft.Diagnostics.DataContractReader.Contracts; + +internal interface IDacStreams : IContract +{ + static string IContract.Name { get; } = nameof(DacStreams); + static IContract IContract.Create(Target target, int version) + { + return version switch + { + 1 => new DacStreams_1(target), + _ => default(DacStreams), + }; + } + + public virtual string? StringFromEEAddress(TargetPointer address) => throw new NotImplementedException(); +} + +internal readonly struct DacStreams : IDacStreams +{ + // Everything throws NotImplementedException +} diff --git a/src/native/managed/cdacreader/src/Contracts/DacStreams_1.cs b/src/native/managed/cdacreader/src/Contracts/DacStreams_1.cs new file mode 100644 index 00000000000000..688ec1d5b89844 --- /dev/null +++ b/src/native/managed/cdacreader/src/Contracts/DacStreams_1.cs @@ -0,0 +1,127 @@ +// 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.Linq; +using System.Text; +using System.Threading.Tasks; +using Microsoft.Diagnostics.DataContractReader.Data; + +namespace Microsoft.Diagnostics.DataContractReader.Contracts; + +internal class DacStreams_1 : IDacStreams +{ + private readonly Target _target; + + private const uint MiniMetadataSignature = 0x6d727473; + private const uint EENameStreamSignature = 0x614e4545; + + private const uint MiniMetaDataStreamsHeaderSize = 12; + private const uint MiniMetadataStream_MiniMetadataSignature_Offset = 0; + private const uint MiniMetadataStream_TotalSize_Offset = 4; + private const uint MiniMetadataStream_CountOfStreams_Offset = 8; + + private const uint EENameStreamHeaderSize = 8; + private const uint EENameStream_EENameStreamSignature_Offset = 0; + private const uint EENameStream_CountOfNames_Offset = 4; + + internal DacStreams_1(Target target) + { + _target = target; + } + + public virtual string? StringFromEEAddress(TargetPointer address) + { + // We use the data subsystem to handle caching results from processing this data + var dictionary = _target.ProcessedData.GetOrAdd(0).EEObjectToString; + + dictionary.TryGetValue(address, out string? result); + return result; + } + + internal class DacStreams_1_Data : IData + { + static DacStreams_1_Data IData.Create(Target target, TargetPointer address) => new DacStreams_1_Data(target); + + public DacStreams_1_Data(Target target) + { + EEObjectToString = GetEEAddressToStringMap(target); + } + + public readonly Dictionary EEObjectToString; + + internal static Dictionary GetEEAddressToStringMap(Target target) + { + TargetPointer miniMetaDataBuffAddress = target.ReadPointer(target.ReadGlobalPointer(Constants.Globals.MiniMetaDataBuffAddress)); + uint miniMetaDataBuffMaxSize = target.Read(target.ReadGlobalPointer(Constants.Globals.MiniMetaDataBuffMaxSize)); + ulong miniMetaDataBuffEnd = miniMetaDataBuffAddress + miniMetaDataBuffMaxSize; + + Dictionary stringToAddress = new(); + if (miniMetaDataBuffMaxSize < 20) + { + // buffer isn't long enough to hold required headers + return stringToAddress; + } + + if (target.Read(miniMetaDataBuffAddress + MiniMetadataStream_MiniMetadataSignature_Offset) != MiniMetadataSignature) + { + // Magic number is incorrect + return stringToAddress; + } + + + uint totalSize = target.Read(miniMetaDataBuffAddress + MiniMetadataStream_TotalSize_Offset); + if (totalSize > miniMetaDataBuffMaxSize) + { + // totalSize is inconsistent with miniMetaDataBuffMaxSize + return stringToAddress; + } + + byte[] bytes = new byte[totalSize]; + ReadOnlySpan miniMdBuffer = bytes.AsSpan(); + target.ReadBuffer(miniMetaDataBuffAddress, bytes); + uint countStreams = target.Read(miniMetaDataBuffAddress + MiniMetadataStream_CountOfStreams_Offset); + if (countStreams != 1) + { + // This implementation is only aware of 1 possible stream type, so only 1 can exist + return stringToAddress; + } + ulong eeNameStreamAddress = miniMetaDataBuffAddress + MiniMetaDataStreamsHeaderSize; + uint eeNameSig = target.Read(eeNameStreamAddress + EENameStream_EENameStreamSignature_Offset); + if (eeNameSig != EENameStreamSignature) + { + // name of first stream is not 0x614e4545 == "EENa" + return stringToAddress; + } + uint countNames = target.Read(eeNameStreamAddress + EENameStream_CountOfNames_Offset); + + ulong currentAddress = eeNameStreamAddress + EENameStreamHeaderSize; + + for (int i = 0; i < countNames; i++) + { + if (currentAddress >= miniMetaDataBuffEnd) + break; + TargetPointer eeObjectPointer = target.ReadPointer(currentAddress); + currentAddress += (uint)target.PointerSize; + int stringLen = miniMdBuffer.Slice((int)(currentAddress - miniMetaDataBuffAddress)).IndexOf((byte)0); + if (stringLen == -1) + break; + + try + { + string name = Encoding.UTF8.GetString(miniMdBuffer.Slice((int)(currentAddress - miniMetaDataBuffAddress), stringLen)); + stringToAddress.Add(eeObjectPointer, name); + } + catch + { + // Tolerate malformed strings without causing all lookups to fail + } + + currentAddress += (uint)stringLen + 1; + } + + return stringToAddress; + } + } +} diff --git a/src/native/managed/cdacreader/src/Contracts/Loader.cs b/src/native/managed/cdacreader/src/Contracts/Loader.cs index e30633fdf2a25a..508fd8e56ad836 100644 --- a/src/native/managed/cdacreader/src/Contracts/Loader.cs +++ b/src/native/managed/cdacreader/src/Contracts/Loader.cs @@ -31,6 +31,75 @@ internal record struct ModuleLookupTables( TargetPointer TypeDefToMethodTable, TargetPointer TypeRefToMethodTable); +internal struct EcmaMetadataSchema +{ + public EcmaMetadataSchema(string metadataVersion, bool largeStringHeap, bool largeBlobHeap, bool largeGuidHeap, int[] rowCount, bool[] isSorted, bool variableSizedColumnsAre4BytesLong) + { + MetadataVersion = metadataVersion; + LargeStringHeap = largeStringHeap; + LargeBlobHeap = largeBlobHeap; + LargeGuidHeap = largeGuidHeap; + + _rowCount = rowCount; + _isSorted = isSorted; + + VariableSizedColumnsAreAll4BytesLong = variableSizedColumnsAre4BytesLong; + } + + public readonly string MetadataVersion; + + public readonly bool LargeStringHeap; + public readonly bool LargeBlobHeap; + public readonly bool LargeGuidHeap; + + // Table data, these structures hold MetadataTable.Count entries + private readonly int[] _rowCount; + public readonly ReadOnlySpan RowCount => _rowCount; + + private readonly bool[] _isSorted; + public readonly ReadOnlySpan IsSorted => _isSorted; + + // In certain scenarios the size of the tables is forced to be the maximum size + // Otherwise the size of columns should be computed based on RowSize/the various heap flags + public readonly bool VariableSizedColumnsAreAll4BytesLong; +} + +internal class TargetEcmaMetadata +{ + public TargetEcmaMetadata(EcmaMetadataSchema schema, + TargetSpan[] tables, + TargetSpan stringHeap, + TargetSpan userStringHeap, + TargetSpan blobHeap, + TargetSpan guidHeap) + { + Schema = schema; + _tables = tables; + StringHeap = stringHeap; + UserStringHeap = userStringHeap; + BlobHeap = blobHeap; + GuidHeap = guidHeap; + } + + public EcmaMetadataSchema Schema { get; init; } + + private TargetSpan[] _tables; + public ReadOnlySpan Tables => _tables; + public TargetSpan StringHeap { get; init; } + public TargetSpan UserStringHeap { get; init; } + public TargetSpan BlobHeap { get; init; } + public TargetSpan GuidHeap { get; init; } +} + +[Flags] +internal enum AvailableMetadataType +{ + None = 0, + ReadOnly = 1, + ReadWriteSavedCopy = 2, + ReadWrite = 4 +} + internal interface ILoader : IContract { static string IContract.Name => nameof(Loader); @@ -51,8 +120,15 @@ static IContract IContract.Create(Target target, int version) public virtual TargetPointer GetThunkHeap(ModuleHandle handle) => throw new NotImplementedException(); public virtual TargetPointer GetILBase(ModuleHandle handle) => throw new NotImplementedException(); + public virtual TargetPointer GetMetadataAddress(ModuleHandle handle, out ulong size) => throw new NotImplementedException(); + public virtual AvailableMetadataType GetAvailableMetadataType(ModuleHandle handle) => throw new NotImplementedException(); + + public virtual TargetPointer GetReadWriteSavedMetadataAddress(ModuleHandle handle, out ulong size) => throw new NotImplementedException(); + + public virtual TargetEcmaMetadata GetReadWriteMetadata(ModuleHandle handle) => throw new NotImplementedException(); + public virtual ModuleLookupTables GetLookupTables(ModuleHandle handle) => throw new NotImplementedException(); } diff --git a/src/native/managed/cdacreader/src/Contracts/Loader_1.cs b/src/native/managed/cdacreader/src/Contracts/Loader_1.cs index 533dbc467d34e1..2c4aa6677f85c0 100644 --- a/src/native/managed/cdacreader/src/Contracts/Loader_1.cs +++ b/src/native/managed/cdacreader/src/Contracts/Loader_1.cs @@ -59,6 +59,33 @@ TargetPointer ILoader.GetMetadataAddress(ModuleHandle handle, out ulong size) return module.GetLoadedMetadata(out size); } + AvailableMetadataType ILoader.GetAvailableMetadataType(ModuleHandle handle) + { + Data.Module module = _target.ProcessedData.GetOrAdd(handle.Address); + + AvailableMetadataType flags = AvailableMetadataType.None; + + if (module.DynamicMetadata != TargetPointer.Null) + flags |= AvailableMetadataType.ReadWriteSavedCopy; + else + flags |= AvailableMetadataType.ReadOnly; + + // TODO(cdac) implement direct reading of unsaved ReadWrite metadata + return flags; + } + + TargetPointer ILoader.GetReadWriteSavedMetadataAddress(ModuleHandle handle, out ulong size) + { + Data.Module module = _target.ProcessedData.GetOrAdd(handle.Address); + Data.DynamicMetadata dynamicMetadata = _target.ProcessedData.GetOrAdd(module.DynamicMetadata); + TargetPointer result = dynamicMetadata.Data; + size = dynamicMetadata.Size; + return result; + } + + TargetEcmaMetadata ILoader.GetReadWriteMetadata(ModuleHandle handle) => throw new NotImplementedException(); + + ModuleLookupTables ILoader.GetLookupTables(ModuleHandle handle) { Data.Module module = _target.ProcessedData.GetOrAdd(handle.Address); diff --git a/src/native/managed/cdacreader/src/Contracts/Registry.cs b/src/native/managed/cdacreader/src/Contracts/Registry.cs index 21f46e481ecd97..6a5bf35f64d648 100644 --- a/src/native/managed/cdacreader/src/Contracts/Registry.cs +++ b/src/native/managed/cdacreader/src/Contracts/Registry.cs @@ -22,6 +22,7 @@ public Registry(Target target) public ILoader Loader => GetContract(); public IThread Thread => GetContract(); public IRuntimeTypeSystem RuntimeTypeSystem => GetContract(); + public IDacStreams DacStreams => GetContract(); private T GetContract() where T : IContract { diff --git a/src/native/managed/cdacreader/src/Contracts/RuntimeTypeSystem.cs b/src/native/managed/cdacreader/src/Contracts/RuntimeTypeSystem.cs index 8d37ca5441fcd3..b7267b5f8c23cb 100644 --- a/src/native/managed/cdacreader/src/Contracts/RuntimeTypeSystem.cs +++ b/src/native/managed/cdacreader/src/Contracts/RuntimeTypeSystem.cs @@ -6,15 +6,53 @@ namespace Microsoft.Diagnostics.DataContractReader.Contracts; -// an opaque handle to a method table. See IMetadata.GetMethodTableData -internal readonly struct MethodTableHandle +// an opaque handle to a type handle. See IMetadata.GetMethodTableData +internal readonly struct TypeHandle { - internal MethodTableHandle(TargetPointer address) + internal TypeHandle(TargetPointer address) { Address = address; } internal TargetPointer Address { get; } + + internal bool IsNull => Address == 0; +} + +internal enum CorElementType +{ + Void = 1, + Boolean = 2, + Char = 3, + I1 = 4, + U1 = 5, + I2 = 6, + U2 = 7, + I4 = 8, + U4 = 9, + I8 = 0xa, + U8 = 0xb, + R4 = 0xc, + R8 = 0xd, + String = 0xe, + Ptr = 0xf, + Byref = 0x10, + ValueType = 0x11, + Class = 0x12, + Var = 0x13, + Array = 0x14, + GenericInst = 0x15, + TypedByRef = 0x16, + I = 0x18, + U = 0x19, + FnPtr = 0x1b, + Object = 0x1c, + SzArray = 0x1d, + MVar = 0x1e, + CModReqd = 0x1f, + CModOpt = 0x20, + Internal = 0x21, + Sentinel = 0x41, } internal interface IRuntimeTypeSystem : IContract @@ -31,34 +69,50 @@ static IContract IContract.Create(Target target, int version) }; } - #region MethodTable inspection APIs - public virtual MethodTableHandle GetMethodTableHandle(TargetPointer targetPointer) => throw new NotImplementedException(); - - public virtual TargetPointer GetModule(MethodTableHandle methodTable) => throw new NotImplementedException(); + #region TypeHandle inspection APIs + public virtual TypeHandle GetTypeHandle(TargetPointer address) => throw new NotImplementedException(); + public virtual TargetPointer GetModule(TypeHandle typeHandle) => throw new NotImplementedException(); // A canonical method table is either the MethodTable itself, or in the case of a generic instantiation, it is the // MethodTable of the prototypical instance. - public virtual TargetPointer GetCanonicalMethodTable(MethodTableHandle methodTable) => throw new NotImplementedException(); - public virtual TargetPointer GetParentMethodTable(MethodTableHandle methodTable) => throw new NotImplementedException(); + public virtual TargetPointer GetCanonicalMethodTable(TypeHandle typeHandle) => throw new NotImplementedException(); + public virtual TargetPointer GetParentMethodTable(TypeHandle typeHandle) => throw new NotImplementedException(); - public virtual uint GetBaseSize(MethodTableHandle methodTable) => throw new NotImplementedException(); + public virtual uint GetBaseSize(TypeHandle typeHandle) => throw new NotImplementedException(); // The component size is only available for strings and arrays. It is the size of the element type of the array, or the size of an ECMA 335 character (2 bytes) - public virtual uint GetComponentSize(MethodTableHandle methodTable) => throw new NotImplementedException(); + public virtual uint GetComponentSize(TypeHandle typeHandle) => throw new NotImplementedException(); // True if the MethodTable is the sentinel value associated with unallocated space in the managed heap - public virtual bool IsFreeObjectMethodTable(MethodTableHandle methodTable) => throw new NotImplementedException(); - public virtual bool IsString(MethodTableHandle methodTable) => throw new NotImplementedException(); + public virtual bool IsFreeObjectMethodTable(TypeHandle typeHandle) => throw new NotImplementedException(); + public virtual bool IsString(TypeHandle typeHandle) => throw new NotImplementedException(); // True if the MethodTable represents a type that contains managed references - public virtual bool ContainsGCPointers(MethodTableHandle methodTable) => throw new NotImplementedException(); - public virtual bool IsDynamicStatics(MethodTableHandle methodTable) => throw new NotImplementedException(); - public virtual ushort GetNumMethods(MethodTableHandle methodTable) => throw new NotImplementedException(); - public virtual ushort GetNumInterfaces(MethodTableHandle methodTable) => throw new NotImplementedException(); + public virtual bool ContainsGCPointers(TypeHandle typeHandle) => throw new NotImplementedException(); + public virtual bool IsDynamicStatics(TypeHandle typeHandle) => throw new NotImplementedException(); + public virtual ushort GetNumMethods(TypeHandle typeHandle) => throw new NotImplementedException(); + public virtual ushort GetNumInterfaces(TypeHandle typeHandle) => throw new NotImplementedException(); // Returns an ECMA-335 TypeDef table token for this type, or for its generic type definition if it is a generic instantiation - public virtual uint GetTypeDefToken(MethodTableHandle methodTable) => throw new NotImplementedException(); + public virtual uint GetTypeDefToken(TypeHandle typeHandle) => throw new NotImplementedException(); // Returns the ECMA 335 TypeDef table Flags value (a bitmask of TypeAttributes) for this type, // or for its generic type definition if it is a generic instantiation - public virtual uint GetTypeDefTypeAttributes(MethodTableHandle methodTable) => throw new NotImplementedException(); - #endregion MethodTable inspection APIs + public virtual uint GetTypeDefTypeAttributes(TypeHandle typeHandle) => throw new NotImplementedException(); + + public virtual ReadOnlySpan GetInstantiation(TypeHandle typeHandle) => throw new NotImplementedException(); + public virtual bool IsGenericTypeDefinition(TypeHandle typeHandle) => throw new NotImplementedException(); + + public virtual bool HasTypeParam(TypeHandle typeHandle) => throw new NotImplementedException(); + + // Element type of the type. NOTE: this drops the CorElementType.GenericInst, and CorElementType.String is returned as CorElementType.Class. + // If this returns CorElementType.ValueType it may be a normal valuetype or a "NATIVE" valuetype used to represent an interop view on a structure + // HasTypeParam will return true for cases where this is the interop view + public virtual CorElementType GetSignatureCorElementType(TypeHandle typeHandle) => throw new NotImplementedException(); + + // return true if the TypeHandle represents an array, and set the rank to either 0 (if the type is not an array), or the rank number if it is. + public virtual bool IsArray(TypeHandle typeHandle, out uint rank) => throw new NotImplementedException(); + public virtual TypeHandle GetTypeParam(TypeHandle typeHandle) => throw new NotImplementedException(); + public virtual bool IsGenericVariable(TypeHandle typeHandle, out TargetPointer module, out uint token) => throw new NotImplementedException(); + public virtual bool IsFunctionPointer(TypeHandle typeHandle, out ReadOnlySpan retAndArgTypes, out byte callConv) => throw new NotImplementedException(); + // Returns null if the TypeHandle is not a class/struct/generic variable + #endregion TypeHandle inspection APIs } internal struct RuntimeTypeSystem : IRuntimeTypeSystem diff --git a/src/native/managed/cdacreader/src/Contracts/RuntimeTypeSystem_1.MethodTableFlags.cs b/src/native/managed/cdacreader/src/Contracts/RuntimeTypeSystem_1.MethodTableFlags.cs index e6f98f0d8e2f08..6e02d241b994c4 100644 --- a/src/native/managed/cdacreader/src/Contracts/RuntimeTypeSystem_1.MethodTableFlags.cs +++ b/src/native/managed/cdacreader/src/Contracts/RuntimeTypeSystem_1.MethodTableFlags.cs @@ -14,6 +14,7 @@ internal enum WFLAGS_LOW : uint { GenericsMask = 0x00000030, GenericsMask_NonGeneric = 0x00000000, // no instantiation + GenericsMask_TypicalInstantiation = 0x00000030, // the type instantiated at its formal parameters, e.g. List StringArrayValues = GenericsMask_NonGeneric | @@ -26,7 +27,13 @@ internal enum WFLAGS_HIGH : uint { Category_Mask = 0x000F0000, Category_Array = 0x00080000, + Category_IfArrayThenSzArray = 0x00020000, Category_Array_Mask = 0x000C0000, + Category_ElementType_Mask = 0x000E0000, + Category_ValueType = 0x00040000, + Category_Nullable = 0x00050000, + Category_PrimitiveValueType = 0x00060000, + Category_TruePrimitive = 0x00070000, Category_Interface = 0x000C0000, ContainsGCPointers = 0x01000000, HasComponentSize = 0x80000000, // This is set if lower 16 bits is used for the component size, @@ -83,5 +90,6 @@ private bool TestFlagWithMask(WFLAGS2_ENUM mask, WFLAGS2_ENUM flag) public bool HasInstantiation => !TestFlagWithMask(WFLAGS_LOW.GenericsMask, WFLAGS_LOW.GenericsMask_NonGeneric); public bool ContainsGCPointers => GetFlag(WFLAGS_HIGH.ContainsGCPointers) != 0; public bool IsDynamicStatics => GetFlag(WFLAGS2_ENUM.DynamicStatics) != 0; + public bool IsGenericTypeDefinition => TestFlagWithMask(WFLAGS_LOW.GenericsMask, WFLAGS_LOW.GenericsMask_TypicalInstantiation); } } diff --git a/src/native/managed/cdacreader/src/Contracts/RuntimeTypeSystem_1.cs b/src/native/managed/cdacreader/src/Contracts/RuntimeTypeSystem_1.cs index da2ea6c8b01b3e..9c3c3bc3f4974d 100644 --- a/src/native/managed/cdacreader/src/Contracts/RuntimeTypeSystem_1.cs +++ b/src/native/managed/cdacreader/src/Contracts/RuntimeTypeSystem_1.cs @@ -4,11 +4,13 @@ using System; using System.Collections.Generic; using System.Reflection.Metadata.Ecma335; +using Microsoft.Diagnostics.DataContractReader.Data; +using Microsoft.Diagnostics.DataContractReader.Contracts.RuntimeTypeSystem_1_NS; +using System.Security.Cryptography.X509Certificates; +using System.Diagnostics; namespace Microsoft.Diagnostics.DataContractReader.Contracts; - - internal partial struct RuntimeTypeSystem_1 : IRuntimeTypeSystem { private readonly Target _target; @@ -27,6 +29,7 @@ internal struct MethodTable internal TargetPointer ParentMethodTable { get; } internal TargetPointer Module { get; } internal TargetPointer EEClassOrCanonMT { get; } + internal TargetPointer PerInstInfo { get; } internal MethodTable(Data.MethodTable data) { Flags = new MethodTableFlags @@ -40,6 +43,7 @@ internal MethodTable(Data.MethodTable data) EEClassOrCanonMT = data.EEClassOrCanonMT; Module = data.Module; ParentMethodTable = data.ParentMethodTable; + PerInstInfo = data.PerInstInfo; } } @@ -53,6 +57,16 @@ internal enum EEClassOrCanonMTBits Mask = 1, } + // Low order bits of TypeHandle address. + // If the low bits contain a 2, then it is a TypeDesc + [Flags] + internal enum TypeHandleBits + { + MethodTable = 0, + TypeDesc = 2, + ValidMask = 2, + } + internal RuntimeTypeSystem_1(Target target, TargetPointer freeObjectMethodTablePointer) { _target = target; @@ -61,21 +75,37 @@ internal RuntimeTypeSystem_1(Target target, TargetPointer freeObjectMethodTableP internal TargetPointer FreeObjectMethodTablePointer => _freeObjectMethodTablePointer; - - public MethodTableHandle GetMethodTableHandle(TargetPointer methodTablePointer) + public TypeHandle GetTypeHandle(TargetPointer typeHandlePointer) { + TypeHandleBits addressLowBits = (TypeHandleBits)((ulong)typeHandlePointer & ((ulong)_target.PointerSize - 1)); + + if ((addressLowBits != TypeHandleBits.MethodTable) && (addressLowBits != TypeHandleBits.TypeDesc)) + { + throw new InvalidOperationException("Invalid type handle pointer"); + } + // if we already validated this address, return a handle - if (_methodTables.ContainsKey(methodTablePointer)) + if (_methodTables.ContainsKey(typeHandlePointer)) + { + return new TypeHandle(typeHandlePointer); + } + + // Check for a TypeDesc + if (addressLowBits == TypeHandleBits.TypeDesc) { - return new MethodTableHandle(methodTablePointer); + // This is a TypeDesc + return new TypeHandle(typeHandlePointer); } + + TargetPointer methodTablePointer = typeHandlePointer; + // Check if we cached the underlying data already if (_target.ProcessedData.TryGet(methodTablePointer, out Data.MethodTable? methodTableData)) { // we already cached the data, we must have validated the address, create the representation struct for our use MethodTable trustedMethodTable = new MethodTable(methodTableData); _ = _methodTables.TryAdd(methodTablePointer, trustedMethodTable); - return new MethodTableHandle(methodTablePointer); + return new TypeHandle(methodTablePointer); } // If it's the free object method table, we trust it to be valid @@ -84,7 +114,7 @@ public MethodTableHandle GetMethodTableHandle(TargetPointer methodTablePointer) Data.MethodTable freeObjectMethodTableData = _target.ProcessedData.GetOrAdd(methodTablePointer); MethodTable trustedMethodTable = new MethodTable(freeObjectMethodTableData); _ = _methodTables.TryAdd(methodTablePointer, trustedMethodTable); - return new MethodTableHandle(methodTablePointer); + return new TypeHandle(methodTablePointer); } // Otherwse, get ready to validate @@ -98,24 +128,23 @@ public MethodTableHandle GetMethodTableHandle(TargetPointer methodTablePointer) Data.MethodTable trustedMethodTableData = _target.ProcessedData.GetOrAdd(methodTablePointer); MethodTable trustedMethodTableF = new MethodTable(trustedMethodTableData); _ = _methodTables.TryAdd(methodTablePointer, trustedMethodTableF); - return new MethodTableHandle(methodTablePointer); + return new TypeHandle(methodTablePointer); } + public uint GetBaseSize(TypeHandle typeHandle) => !typeHandle.IsMethodTable() ? (uint)0 : _methodTables[typeHandle.Address].Flags.BaseSize; - public uint GetBaseSize(MethodTableHandle methodTableHandle) => _methodTables[methodTableHandle.Address].Flags.BaseSize; - - public uint GetComponentSize(MethodTableHandle methodTableHandle) => _methodTables[methodTableHandle.Address].Flags.ComponentSize; + public uint GetComponentSize(TypeHandle typeHandle) => !typeHandle.IsMethodTable() ? (uint)0 : _methodTables[typeHandle.Address].Flags.ComponentSize; - private TargetPointer GetClassPointer(MethodTableHandle methodTableHandle) + private TargetPointer GetClassPointer(TypeHandle typeHandle) { - MethodTable methodTable = _methodTables[methodTableHandle.Address]; + MethodTable methodTable = _methodTables[typeHandle.Address]; switch (GetEEClassOrCanonMTBits(methodTable.EEClassOrCanonMT)) { case EEClassOrCanonMTBits.EEClass: return methodTable.EEClassOrCanonMT; case EEClassOrCanonMTBits.CanonMT: TargetPointer canonMTPtr = new TargetPointer((ulong)methodTable.EEClassOrCanonMT & ~(ulong)RuntimeTypeSystem_1.EEClassOrCanonMTBits.Mask); - MethodTableHandle canonMTHandle = GetMethodTableHandle(canonMTPtr); + TypeHandle canonMTHandle = GetTypeHandle(canonMTPtr); MethodTable canonMT = _methodTables[canonMTHandle.Address]; return canonMT.EEClassOrCanonMT; // canonical method table EEClassOrCanonMT is always EEClass default: @@ -124,34 +153,268 @@ private TargetPointer GetClassPointer(MethodTableHandle methodTableHandle) } // only called on validated method tables, so we don't need to re-validate the EEClass - private Data.EEClass GetClassData(MethodTableHandle methodTableHandle) + private Data.EEClass GetClassData(TypeHandle typeHandle) { - TargetPointer clsPtr = GetClassPointer(methodTableHandle); + TargetPointer clsPtr = GetClassPointer(typeHandle); return _target.ProcessedData.GetOrAdd(clsPtr); } - public TargetPointer GetCanonicalMethodTable(MethodTableHandle methodTableHandle) => GetClassData(methodTableHandle).MethodTable; + public TargetPointer GetCanonicalMethodTable(TypeHandle typeHandle) => !typeHandle.IsMethodTable() ? TargetPointer.Null : GetClassData(typeHandle).MethodTable; + + public TargetPointer GetModule(TypeHandle typeHandle) + { + if (typeHandle.IsMethodTable()) + { + return _methodTables[typeHandle.Address].Module; + } + else if (typeHandle.IsTypeDesc()) + { + if (HasTypeParam(typeHandle)) + { + return GetModule(GetTypeParam(typeHandle)); + } + else if (IsGenericVariable(typeHandle, out TargetPointer genericParamModule, out _)) + { + return genericParamModule; + } + else + { + System.Diagnostics.Debug.Assert(IsFunctionPointer(typeHandle, out _, out _)); + return TargetPointer.Null; + } + } + else + { + return TargetPointer.Null; + } + } - public TargetPointer GetModule(MethodTableHandle methodTableHandle) => _methodTables[methodTableHandle.Address].Module; - public TargetPointer GetParentMethodTable(MethodTableHandle methodTableHandle) => _methodTables[methodTableHandle.Address].ParentMethodTable; + public TargetPointer GetParentMethodTable(TypeHandle typeHandle) => !typeHandle.IsMethodTable() ? TargetPointer.Null : _methodTables[typeHandle.Address].ParentMethodTable; - public bool IsFreeObjectMethodTable(MethodTableHandle methodTableHandle) => FreeObjectMethodTablePointer == methodTableHandle.Address; + public bool IsFreeObjectMethodTable(TypeHandle typeHandle) => FreeObjectMethodTablePointer == typeHandle.Address; - public bool IsString(MethodTableHandle methodTableHandle) => _methodTables[methodTableHandle.Address].Flags.IsString; - public bool ContainsGCPointers(MethodTableHandle methodTableHandle) => _methodTables[methodTableHandle.Address].Flags.ContainsGCPointers; + public bool IsString(TypeHandle typeHandle) => !typeHandle.IsMethodTable() ? false : _methodTables[typeHandle.Address].Flags.IsString; + public bool ContainsGCPointers(TypeHandle typeHandle) => !typeHandle.IsMethodTable() ? false : _methodTables[typeHandle.Address].Flags.ContainsGCPointers; - public uint GetTypeDefToken(MethodTableHandle methodTableHandle) + public uint GetTypeDefToken(TypeHandle typeHandle) { - MethodTable methodTable = _methodTables[methodTableHandle.Address]; + if (!typeHandle.IsMethodTable()) + return 0; + MethodTable methodTable = _methodTables[typeHandle.Address]; return (uint)(methodTable.Flags.GetTypeDefRid() | ((int)TableIndex.TypeDef << 24)); } - public ushort GetNumMethods(MethodTableHandle methodTableHandle) => GetClassData(methodTableHandle).NumMethods; + public ushort GetNumMethods(TypeHandle typeHandle) => !typeHandle.IsMethodTable() ? (ushort)0 : GetClassData(typeHandle).NumMethods; + + public ushort GetNumInterfaces(TypeHandle typeHandle) => !typeHandle.IsMethodTable() ? (ushort)0 : _methodTables[typeHandle.Address].NumInterfaces; + + public uint GetTypeDefTypeAttributes(TypeHandle typeHandle) => !typeHandle.IsMethodTable() ? (uint)0 : GetClassData(typeHandle).CorTypeAttr; + + public bool IsDynamicStatics(TypeHandle typeHandle) => !typeHandle.IsMethodTable() ? false : _methodTables[typeHandle.Address].Flags.IsDynamicStatics; + + public ReadOnlySpan GetInstantiation(TypeHandle typeHandle) + { + if (!typeHandle.IsMethodTable()) + return default; + + MethodTable methodTable = _methodTables[typeHandle.Address]; + if (!methodTable.Flags.HasInstantiation) + return default; + + return _target.ProcessedData.GetOrAdd(typeHandle.Address).TypeHandles; + } + + private class TypeInstantiation : IData + { + public static TypeInstantiation Create(Target target, TargetPointer address) => new TypeInstantiation(target, address); + + public TypeHandle[] TypeHandles { get; } + private TypeInstantiation(Target target, TargetPointer typePointer) + { + RuntimeTypeSystem_1 rts = (RuntimeTypeSystem_1)target.Contracts.RuntimeTypeSystem; + MethodTable methodTable = rts._methodTables[typePointer]; + Debug.Assert(methodTable.Flags.HasInstantiation); + + TargetPointer perInstInfo = methodTable.PerInstInfo; + TargetPointer genericsDictInfo = perInstInfo - (ulong)target.PointerSize; + + TargetPointer dictionaryPointer = target.ReadPointer(perInstInfo); + + + int numberOfGenericArgs = target.ProcessedData.GetOrAdd(genericsDictInfo).NumTypeArgs; + + TypeHandles = new TypeHandle[numberOfGenericArgs]; + for (int i = 0; i < numberOfGenericArgs; i++) + { + TypeHandles[i] = rts.GetTypeHandle(target.ReadPointer(dictionaryPointer + (ulong)target.PointerSize * (ulong)i)); + } + } + } + + public bool IsGenericTypeDefinition(TypeHandle typeHandle) => !typeHandle.IsMethodTable() ? false : _methodTables[typeHandle.Address].Flags.IsGenericTypeDefinition; + + public bool HasTypeParam(TypeHandle typeHandle) + { + if (typeHandle.IsMethodTable()) + { + MethodTable methodTable = _methodTables[typeHandle.Address]; + return methodTable.Flags.IsArray; + } + else if (typeHandle.IsTypeDesc()) + { + var typeDesc = _target.ProcessedData.GetOrAdd(typeHandle.TypeDescAddress()); + CorElementType elemType = (CorElementType)(typeDesc.TypeAndFlags & 0xFF); + switch (elemType) + { + case CorElementType.ValueType: + case CorElementType.Byref: + case CorElementType.Ptr: + return true; + } + } + return false; + } + + public CorElementType GetSignatureCorElementType(TypeHandle typeHandle) + { + if (typeHandle.IsMethodTable()) + { + MethodTable methodTable = _methodTables[typeHandle.Address]; + + switch (methodTable.Flags.GetFlag(WFLAGS_HIGH.Category_Mask)) + { + case WFLAGS_HIGH.Category_Array: + return CorElementType.Array; + case WFLAGS_HIGH.Category_Array | WFLAGS_HIGH.Category_IfArrayThenSzArray: + return CorElementType.SzArray; + case WFLAGS_HIGH.Category_ValueType: + case WFLAGS_HIGH.Category_Nullable: + case WFLAGS_HIGH.Category_PrimitiveValueType: + return CorElementType.ValueType; + case WFLAGS_HIGH.Category_TruePrimitive: + return (CorElementType)GetClassData(typeHandle).InternalCorElementType; + default: + return CorElementType.Class; + } + } + else if (typeHandle.IsTypeDesc()) + { + var typeDesc = _target.ProcessedData.GetOrAdd(typeHandle.TypeDescAddress()); + return (CorElementType)(typeDesc.TypeAndFlags & 0xFF); + } + + return default; + } + + // 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 bool IsArray(TypeHandle typeHandle, out uint rank) + { + if (typeHandle.IsMethodTable()) + { + MethodTable methodTable = _methodTables[typeHandle.Address]; + + switch (methodTable.Flags.GetFlag(WFLAGS_HIGH.Category_Mask)) + { + case WFLAGS_HIGH.Category_Array: + TargetPointer clsPtr = GetClassPointer(typeHandle); + rank = _target.ProcessedData.GetOrAdd(clsPtr).Rank; + return true; + + case WFLAGS_HIGH.Category_Array | WFLAGS_HIGH.Category_IfArrayThenSzArray: + rank = 1; + return true; + } + } + + rank = 0; + return false; + } + + public TypeHandle GetTypeParam(TypeHandle typeHandle) + { + if (typeHandle.IsMethodTable()) + { + MethodTable methodTable = _methodTables[typeHandle.Address]; + if (!methodTable.Flags.IsArray) + throw new ArgumentException(nameof(typeHandle)); + + return GetTypeHandle(methodTable.PerInstInfo); + } + else if (typeHandle.IsTypeDesc()) + { + var typeDesc = _target.ProcessedData.GetOrAdd(typeHandle.TypeDescAddress()); + CorElementType elemType = (CorElementType)(typeDesc.TypeAndFlags & 0xFF); + switch (elemType) + { + case CorElementType.ValueType: + case CorElementType.Byref: + case CorElementType.Ptr: + ParamTypeDesc paramTypeDesc = _target.ProcessedData.GetOrAdd(typeHandle.TypeDescAddress()); + return GetTypeHandle(paramTypeDesc.TypeArg); + } + } + throw new ArgumentException(nameof(typeHandle)); + } + + public bool IsGenericVariable(TypeHandle typeHandle, out TargetPointer module, out uint token) + { + module = TargetPointer.Null; + token = 0; + + if (!typeHandle.IsTypeDesc()) + return false; + + var typeDesc = _target.ProcessedData.GetOrAdd(typeHandle.TypeDescAddress()); + CorElementType elemType = (CorElementType)(typeDesc.TypeAndFlags & 0xFF); + switch (elemType) + { + case CorElementType.MVar: + case CorElementType.Var: + TypeVarTypeDesc typeVarTypeDesc = _target.ProcessedData.GetOrAdd(typeHandle.TypeDescAddress()); + module = typeVarTypeDesc.Module; + token = typeVarTypeDesc.Token; + return true; + } + return false; + } + + public bool IsFunctionPointer(TypeHandle typeHandle, out ReadOnlySpan retAndArgTypes, out byte callConv) + { + retAndArgTypes = default; + callConv = default; - public ushort GetNumInterfaces(MethodTableHandle methodTableHandle) => _methodTables[methodTableHandle.Address].NumInterfaces; + if (!typeHandle.IsTypeDesc()) + return false; - public uint GetTypeDefTypeAttributes(MethodTableHandle methodTableHandle) => GetClassData(methodTableHandle).CorTypeAttr; + var typeDesc = _target.ProcessedData.GetOrAdd(typeHandle.TypeDescAddress()); + CorElementType elemType = (CorElementType)(typeDesc.TypeAndFlags & 0xFF); + if (elemType != CorElementType.FnPtr) + return false; - public bool IsDynamicStatics(MethodTableHandle methodTableHandle) => _methodTables[methodTableHandle.Address].Flags.IsDynamicStatics; + FnPtrTypeDesc fnPtrTypeDesc = _target.ProcessedData.GetOrAdd(typeHandle.TypeDescAddress()); + retAndArgTypes = _target.ProcessedData.GetOrAdd(typeHandle.TypeDescAddress()).TypeHandles; + callConv = (byte)fnPtrTypeDesc.CallConv; + return true; + } + + private class FunctionPointerRetAndArgs : IData + { + public static FunctionPointerRetAndArgs Create(Target target, TargetPointer address) => new FunctionPointerRetAndArgs(target, address); + public TypeHandle[] TypeHandles { get; } + private FunctionPointerRetAndArgs(Target target, TargetPointer typePointer) + { + RuntimeTypeSystem_1 rts = (RuntimeTypeSystem_1)target.Contracts.RuntimeTypeSystem; + FnPtrTypeDesc fnPtrTypeDesc = target.ProcessedData.GetOrAdd(typePointer); + + TargetPointer retAndArgs = fnPtrTypeDesc.RetAndArgTypes; + int numberOfRetAndArgTypes = checked((int)fnPtrTypeDesc.NumArgs + 1); + + TypeHandles = new TypeHandle[numberOfRetAndArgTypes]; + for (int i = 0; i < numberOfRetAndArgTypes; i++) + { + TypeHandles[i] = rts.GetTypeHandle(target.ReadPointer(retAndArgs + (ulong)target.PointerSize * (ulong)i)); + } + } + } } diff --git a/src/native/managed/cdacreader/src/Contracts/RuntimeTypeSystem_1_Helpers.cs b/src/native/managed/cdacreader/src/Contracts/RuntimeTypeSystem_1_Helpers.cs new file mode 100644 index 00000000000000..af1e23df461b91 --- /dev/null +++ b/src/native/managed/cdacreader/src/Contracts/RuntimeTypeSystem_1_Helpers.cs @@ -0,0 +1,25 @@ +// 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.Contracts.RuntimeTypeSystem_1_NS; + +internal static class RuntimeTypeSystem_1_Helpers +{ + public static bool IsTypeDesc(this TypeHandle type) + { + return type.Address != 0 && ((ulong)type.Address & (ulong)RuntimeTypeSystem_1.TypeHandleBits.ValidMask) == (ulong)RuntimeTypeSystem_1.TypeHandleBits.TypeDesc; + } + + public static bool IsMethodTable(this TypeHandle type) + { + return type.Address != 0 && ((ulong)type.Address & (ulong)RuntimeTypeSystem_1.TypeHandleBits.ValidMask) == (ulong)RuntimeTypeSystem_1.TypeHandleBits.MethodTable; + } + + public static TargetPointer TypeDescAddress(this TypeHandle type) + { + if (!type.IsTypeDesc()) + return 0; + + return (ulong)type.Address & ~(ulong)RuntimeTypeSystem_1.TypeHandleBits.ValidMask; + } +} diff --git a/src/native/managed/cdacreader/src/Data/DynamicMetadata.cs b/src/native/managed/cdacreader/src/Data/DynamicMetadata.cs new file mode 100644 index 00000000000000..fc1ac8c90963f8 --- /dev/null +++ b/src/native/managed/cdacreader/src/Data/DynamicMetadata.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. + +namespace Microsoft.Diagnostics.DataContractReader.Data; + +internal class DynamicMetadata : IData +{ + static DynamicMetadata IData.Create(Target target, TargetPointer address) => new DynamicMetadata(target, address); + public DynamicMetadata(Target target, TargetPointer address) + { + Target.TypeInfo type = target.GetTypeInfo(DataType.DynamicMetadata); + + Size = target.Read(address + (ulong)type.Fields[nameof(Size)].Offset); + Data = address + (ulong)type.Fields[nameof(Data)].Offset; + } + + public uint Size { get; init; } + public TargetPointer Data { get; init; } +} diff --git a/src/native/managed/cdacreader/src/Data/EEClass.cs b/src/native/managed/cdacreader/src/Data/EEClass.cs index e697b3f40756c8..ea863ce178e518 100644 --- a/src/native/managed/cdacreader/src/Data/EEClass.cs +++ b/src/native/managed/cdacreader/src/Data/EEClass.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; - namespace Microsoft.Diagnostics.DataContractReader.Data; public sealed class EEClass : IData @@ -15,9 +13,32 @@ public EEClass(Target target, TargetPointer address) MethodTable = target.ReadPointer(address + (ulong)type.Fields[nameof(MethodTable)].Offset); NumMethods = target.Read(address + (ulong)type.Fields[nameof(NumMethods)].Offset); CorTypeAttr = target.Read(address + (ulong)type.Fields[nameof(CorTypeAttr)].Offset); + InternalCorElementType = target.Read(address + (ulong)type.Fields[nameof(InternalCorElementType)].Offset); } public TargetPointer MethodTable { get; init; } public ushort NumMethods { get; init; } public uint CorTypeAttr { get; init; } + + // An InternalCorElementType uses the enum values of a CorElementType to + // indicate some of the information about the type of the type which uses + // the EEClass + // + // In particular. All reference types are ELEMENT_TYPE_CLASS + // Enums are the element type of their underlying type + // ValueTypes which can exactly be represented as an element type are represented as such + public byte InternalCorElementType { get; init; } +} + +public sealed class ArrayClass : IData +{ + static ArrayClass IData.Create(Target target, TargetPointer address) => new ArrayClass(target, address); + public ArrayClass(Target target, TargetPointer address) + { + Target.TypeInfo type = target.GetTypeInfo(DataType.ArrayClass); + + Rank = target.Read(address + (ulong)type.Fields[nameof(Rank)].Offset); + } + + public byte Rank { get; init; } } diff --git a/src/native/managed/cdacreader/src/Data/GenericsDictInfo.cs b/src/native/managed/cdacreader/src/Data/GenericsDictInfo.cs new file mode 100644 index 00000000000000..7aad8f95301d76 --- /dev/null +++ b/src/native/managed/cdacreader/src/Data/GenericsDictInfo.cs @@ -0,0 +1,17 @@ +// 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 class GenericsDictInfo : IData +{ + static GenericsDictInfo IData.Create(Target target, TargetPointer address) => new GenericsDictInfo(target, address); + public GenericsDictInfo(Target target, TargetPointer address) + { + Target.TypeInfo type = target.GetTypeInfo(DataType.GenericsDictInfo); + + NumTypeArgs = target.Read(address + (ulong)type.Fields[nameof(NumTypeArgs)].Offset); + } + + public ushort NumTypeArgs { get; init; } +} diff --git a/src/native/managed/cdacreader/src/Data/MethodTable.cs b/src/native/managed/cdacreader/src/Data/MethodTable.cs index 3319c6547f0568..6461a9521f4721 100644 --- a/src/native/managed/cdacreader/src/Data/MethodTable.cs +++ b/src/native/managed/cdacreader/src/Data/MethodTable.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; - namespace Microsoft.Diagnostics.DataContractReader.Data; internal sealed class MethodTable : IData @@ -20,6 +18,7 @@ public MethodTable(Target target, TargetPointer address) ParentMethodTable = target.ReadPointer(address + (ulong)type.Fields[nameof(ParentMethodTable)].Offset); NumInterfaces = target.Read(address + (ulong)type.Fields[nameof(NumInterfaces)].Offset); NumVirtuals = target.Read(address + (ulong)type.Fields[nameof(NumVirtuals)].Offset); + PerInstInfo = target.ReadPointer(address + (ulong)type.Fields[nameof(PerInstInfo)].Offset); } public uint MTFlags { get; init; } @@ -28,6 +27,7 @@ public MethodTable(Target target, TargetPointer address) public TargetPointer EEClassOrCanonMT { get; init; } public TargetPointer Module { get; init; } public TargetPointer ParentMethodTable { get; init; } + public TargetPointer PerInstInfo { get; init; } public ushort NumInterfaces { get; init; } public ushort NumVirtuals { get; init; } } diff --git a/src/native/managed/cdacreader/src/Data/MethodTableAuxiliaryData.cs b/src/native/managed/cdacreader/src/Data/MethodTableAuxiliaryData.cs index bdc711a5104735..e04e5b56b0c547 100644 --- a/src/native/managed/cdacreader/src/Data/MethodTableAuxiliaryData.cs +++ b/src/native/managed/cdacreader/src/Data/MethodTableAuxiliaryData.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; - namespace Microsoft.Diagnostics.DataContractReader.Data; internal sealed class MethodTableAuxiliaryData : IData diff --git a/src/native/managed/cdacreader/src/Data/Module.cs b/src/native/managed/cdacreader/src/Data/Module.cs index e462a201241ce7..eb8746ea64c8eb 100644 --- a/src/native/managed/cdacreader/src/Data/Module.cs +++ b/src/native/managed/cdacreader/src/Data/Module.cs @@ -22,6 +22,7 @@ public Module(Target target, TargetPointer address) Base = target.ReadPointer(address + (ulong)type.Fields[nameof(Base)].Offset); LoaderAllocator = target.ReadPointer(address + (ulong)type.Fields[nameof(LoaderAllocator)].Offset); ThunkHeap = target.ReadPointer(address + (ulong)type.Fields[nameof(ThunkHeap)].Offset); + DynamicMetadata = target.ReadPointer(address + (ulong)type.Fields[nameof(DynamicMetadata)].Offset); FieldDefToDescMap = target.ReadPointer(address + (ulong)type.Fields[nameof(FieldDefToDescMap)].Offset); ManifestModuleReferencesMap = target.ReadPointer(address + (ulong)type.Fields[nameof(ManifestModuleReferencesMap)].Offset); @@ -36,6 +37,7 @@ public Module(Target target, TargetPointer address) public TargetPointer Base { get; init; } public TargetPointer LoaderAllocator { get; init; } public TargetPointer ThunkHeap { get; init; } + public TargetPointer DynamicMetadata { get; init; } public TargetPointer FieldDefToDescMap { get; init; } public TargetPointer ManifestModuleReferencesMap { get; init; } diff --git a/src/native/managed/cdacreader/src/Data/TypeDesc.cs b/src/native/managed/cdacreader/src/Data/TypeDesc.cs new file mode 100644 index 00000000000000..d807f91a365008 --- /dev/null +++ b/src/native/managed/cdacreader/src/Data/TypeDesc.cs @@ -0,0 +1,72 @@ +// 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 class TypeDesc : IData +{ + static TypeDesc IData.Create(Target target, TargetPointer address) => new TypeDesc(target, address); + public TypeDesc(Target target, TargetPointer address) + { + Target.TypeInfo type = target.GetTypeInfo(DataType.TypeDesc); + TypeAndFlags = target.Read(address + (ulong)type.Fields[nameof(TypeAndFlags)].Offset); + } + + public uint TypeAndFlags { get; init; } +} + +internal class ParamTypeDesc : IData +{ + static ParamTypeDesc IData.Create(Target target, TargetPointer address) => new ParamTypeDesc(target, address); + public ParamTypeDesc(Target target, TargetPointer address) + { + Target.TypeInfo type = target.GetTypeInfo(DataType.TypeDesc); + TypeAndFlags = target.Read(address + (ulong)type.Fields[nameof(TypeAndFlags)].Offset); + + type = target.GetTypeInfo(DataType.ParamTypeDesc); + TypeArg = target.Read(address + (ulong)type.Fields[nameof(TypeArg)].Offset); + } + + public uint TypeAndFlags { get; init; } + public TargetPointer TypeArg { get; init; } +} + +internal class TypeVarTypeDesc : IData +{ + static TypeVarTypeDesc IData.Create(Target target, TargetPointer address) => new TypeVarTypeDesc(target, address); + public TypeVarTypeDesc(Target target, TargetPointer address) + { + Target.TypeInfo type = target.GetTypeInfo(DataType.TypeDesc); + TypeAndFlags = target.Read(address + (ulong)type.Fields[nameof(TypeAndFlags)].Offset); + + type = target.GetTypeInfo(DataType.TypeVarTypeDesc); + + Module = target.ReadPointer(address + (ulong)type.Fields[nameof(Module)].Offset); + Token = target.Read(address + (ulong)type.Fields[nameof(Token)].Offset); + } + + public uint TypeAndFlags { get; init; } + public TargetPointer Module { get; init; } + public uint Token { get; init; } +} + +internal class FnPtrTypeDesc : IData +{ + static FnPtrTypeDesc IData.Create(Target target, TargetPointer address) => new FnPtrTypeDesc(target, address); + public FnPtrTypeDesc(Target target, TargetPointer address) + { + Target.TypeInfo type = target.GetTypeInfo(DataType.TypeDesc); + TypeAndFlags = target.Read(address + (ulong)type.Fields[nameof(TypeAndFlags)].Offset); + + type = target.GetTypeInfo(DataType.FnPtrTypeDesc); + + NumArgs = target.Read(address + (ulong)type.Fields[nameof(NumArgs)].Offset); + CallConv = target.Read(address + (ulong)type.Fields[nameof(CallConv)].Offset); + RetAndArgTypes = (TargetPointer)(address + (ulong)type.Fields[nameof(RetAndArgTypes)].Offset); + } + + public uint TypeAndFlags { get; init; } + public uint NumArgs { get; init; } + public uint CallConv { get; init; } + public TargetPointer RetAndArgTypes { get; init; } +} diff --git a/src/native/managed/cdacreader/src/DataType.cs b/src/native/managed/cdacreader/src/DataType.cs index 3f20bdf7b095bc..ef037be8ed1c8c 100644 --- a/src/native/managed/cdacreader/src/DataType.cs +++ b/src/native/managed/cdacreader/src/DataType.cs @@ -29,5 +29,12 @@ public enum DataType Module, MethodTable, EEClass, + ArrayClass, MethodTableAuxiliaryData, + GenericsDictInfo, + TypeDesc, + ParamTypeDesc, + TypeVarTypeDesc, + FnPtrTypeDesc, + DynamicMetadata, } diff --git a/src/native/managed/cdacreader/src/Helpers/EcmaMetadataReader.InternalHelpers.cs b/src/native/managed/cdacreader/src/Helpers/EcmaMetadataReader.InternalHelpers.cs new file mode 100644 index 00000000000000..1fe072ce1e926d --- /dev/null +++ b/src/native/managed/cdacreader/src/Helpers/EcmaMetadataReader.InternalHelpers.cs @@ -0,0 +1,106 @@ +// 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; +using System.Linq; +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Text; +using System.Threading.Tasks; +using Microsoft.Diagnostics.DataContractReader.Contracts; + +namespace Microsoft.Diagnostics.DataContractReader.Helpers; + +internal partial class EcmaMetadataReader +{ + private int ColumnSize(MetadataColumnIndex column) + { + return columnSize[(int)column]; + } + + private int ColumnOffset(MetadataColumnIndex column) + { + return columnOffset[(int)column]; + } + + private int RowCount(MetadataTable table) + { + return _ecmaMetadata.Schema.RowCount[(int)table]; + } + + private bool TryReadTableEntry(ReadOnlySpan bytes, MetadataColumnIndex column, out uint value) + { + int size = ColumnSize(column); + ReadOnlySpan singleColumn = bytes.Slice(ColumnOffset(column), size); + if (size == 2) + { + if (TryReadCore(singleColumn, out ushort valueAsShort)) + { + value = valueAsShort; + return true; + } + value = 0; + return false; + } + if (size != 4) + throw new ArgumentOutOfRangeException(nameof(column)); + + return TryReadCore(singleColumn, out value); + } + + private uint GetColumnRaw(EcmaMetadataCursor c, MetadataColumnIndex col_idx) + { + if (c.Table != ColumnTable(col_idx)) + throw new ArgumentOutOfRangeException(nameof(col_idx)); + + if (!TryReadTableEntry(c.Row, col_idx, out uint rawResult)) + { + throw new ArgumentOutOfRangeException(nameof(col_idx)); + } + return rawResult; + } + + private int RidEncodingBits(MetadataTable table) + { + if (table == MetadataTable.Unused) + return 0; + + int countInTable = RowCount(table); + + // Tables start at 1 + countInTable++; + return 32 - BitOperations.LeadingZeroCount((uint)countInTable); + } + + private int RidEncodingBytes(MetadataTable table) + { + if (RidEncodingBits(table) > 16) + return 4; + else + return 2; + } + + private int CodedIndexEncodingBytes(ReadOnlySpan tablesEncoded) + { + uint encodingMask = BitOperations.RoundUpToPowerOf2((uint)tablesEncoded.Length) - 1; + int bitsForTableEncoding = 32 - BitOperations.LeadingZeroCount(encodingMask); + if (tablesEncoded.Length == 1) + { + Debug.Assert(bitsForTableEncoding == 0); // This is just a rid to token conversion, no extra bits. + } + if (tablesEncoded.Length == 3 && tablesEncoded[0] == (MetadataTable)(-2)) + { + // Ptr scenario + return RidEncodingBytes(tablesEncoded[2]); + } + + foreach (MetadataTable table in tablesEncoded) + { + if ((RidEncodingBits(table) + bitsForTableEncoding) > 16) + return 4; + } + return 2; + } +} diff --git a/src/native/managed/cdacreader/src/Helpers/EcmaMetadataReader.StaticData.cs b/src/native/managed/cdacreader/src/Helpers/EcmaMetadataReader.StaticData.cs new file mode 100644 index 00000000000000..851a10d0f78346 --- /dev/null +++ b/src/native/managed/cdacreader/src/Helpers/EcmaMetadataReader.StaticData.cs @@ -0,0 +1,594 @@ +// 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; +using System.Linq; +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Text; +using System.Threading.Tasks; +using Microsoft.Diagnostics.DataContractReader.Contracts; + +namespace Microsoft.Diagnostics.DataContractReader.Helpers; + +internal partial class EcmaMetadataReader +{ + private enum ColumnType + { + Unknown, + TwoByteConstant, + FourByteConstant, + Utf8String, + Blob, + Guid, + Token + } + + [Flags] + private enum PtrTablesPresent + { + None = 0, + Method = 1, + Field = 2, + Param = 4, + Property = 8, + Event = 16 + } + + private static readonly MetadataTable[] columnTable = GetColumnTables(); + private static readonly ColumnType[] columnTypes = GetColumnTypes(); + private static readonly Func[][] columnTokenDecoders = GetColumnTokenDecoders(); + private static readonly MetadataTable[][] codedIndexDecoderRing = ColumnDecodeData.GetCodedIndexDecoderRing(); + + private static ColumnType[] GetColumnTypes() + { + ColumnType[] columnTypes = new ColumnType[(int)MetadataColumnIndex.Count]; + + columnTypes[(int)MetadataColumnIndex.Module_Generation] = ColumnType.TwoByteConstant; + columnTypes[(int)MetadataColumnIndex.Module_Name] = ColumnType.Utf8String; + columnTypes[(int)MetadataColumnIndex.Module_Mvid] = ColumnType.Guid; + columnTypes[(int)MetadataColumnIndex.Module_EncId] = ColumnType.Guid; + columnTypes[(int)MetadataColumnIndex.Module_EncBaseId] = ColumnType.Guid; + + columnTypes[(int)MetadataColumnIndex.TypeRef_ResolutionScope] = ColumnType.Token; + columnTypes[(int)MetadataColumnIndex.TypeRef_TypeName] = ColumnType.Utf8String; + columnTypes[(int)MetadataColumnIndex.TypeRef_TypeNamespace] = ColumnType.Utf8String; + + columnTypes[(int)MetadataColumnIndex.TypeDef_Flags] = ColumnType.FourByteConstant; + columnTypes[(int)MetadataColumnIndex.TypeDef_TypeName] = ColumnType.Utf8String; + columnTypes[(int)MetadataColumnIndex.TypeDef_TypeNamespace] = ColumnType.Utf8String; + columnTypes[(int)MetadataColumnIndex.TypeDef_Extends] = ColumnType.Token; + columnTypes[(int)MetadataColumnIndex.TypeDef_FieldList] = ColumnType.Token; + columnTypes[(int)MetadataColumnIndex.TypeDef_MethodList] = ColumnType.Token; + + columnTypes[(int)MetadataColumnIndex.FieldPtr_Field] = ColumnType.Token; + + columnTypes[(int)MetadataColumnIndex.Field_Flags] = ColumnType.TwoByteConstant; + columnTypes[(int)MetadataColumnIndex.Field_Name] = ColumnType.Utf8String; + columnTypes[(int)MetadataColumnIndex.Field_Signature] = ColumnType.Blob; + + columnTypes[(int)MetadataColumnIndex.MethodPtr_Method] = ColumnType.Token; + + columnTypes[(int)MetadataColumnIndex.MethodDef_Rva] = ColumnType.FourByteConstant; + columnTypes[(int)MetadataColumnIndex.MethodDef_ImplFlags] = ColumnType.TwoByteConstant; + columnTypes[(int)MetadataColumnIndex.MethodDef_Flags] = ColumnType.TwoByteConstant; + columnTypes[(int)MetadataColumnIndex.MethodDef_Name] = ColumnType.Utf8String; + columnTypes[(int)MetadataColumnIndex.MethodDef_Signature] = ColumnType.Blob; + columnTypes[(int)MetadataColumnIndex.MethodDef_ParamList] = ColumnType.Token; + + columnTypes[(int)MetadataColumnIndex.ParamPtr_Param] = ColumnType.Token; + + columnTypes[(int)MetadataColumnIndex.Param_Flags] = ColumnType.TwoByteConstant; + columnTypes[(int)MetadataColumnIndex.Param_Sequence] = ColumnType.TwoByteConstant; + columnTypes[(int)MetadataColumnIndex.Param_Name] = ColumnType.Utf8String; + + columnTypes[(int)MetadataColumnIndex.InterfaceImpl_Class] = ColumnType.Token; + columnTypes[(int)MetadataColumnIndex.InterfaceImpl_Interface] = ColumnType.Token; + + columnTypes[(int)MetadataColumnIndex.MemberRef_Class] = ColumnType.Token; + columnTypes[(int)MetadataColumnIndex.MemberRef_Name] = ColumnType.Utf8String; + columnTypes[(int)MetadataColumnIndex.MemberRef_Signature] = ColumnType.Blob; + + columnTypes[(int)MetadataColumnIndex.Constant_Type] = ColumnType.TwoByteConstant; + columnTypes[(int)MetadataColumnIndex.Constant_Parent] = ColumnType.Token; + columnTypes[(int)MetadataColumnIndex.Constant_Value] = ColumnType.Blob; + + columnTypes[(int)MetadataColumnIndex.CustomAttribute_Parent] = ColumnType.Token; + columnTypes[(int)MetadataColumnIndex.CustomAttribute_Type] = ColumnType.Token; + columnTypes[(int)MetadataColumnIndex.CustomAttribute_Value] = ColumnType.Blob; + + columnTypes[(int)MetadataColumnIndex.FieldMarshal_Parent] = ColumnType.Token; + columnTypes[(int)MetadataColumnIndex.FieldMarshal_NativeType] = ColumnType.Blob; + + columnTypes[(int)MetadataColumnIndex.DeclSecurity_Action] = ColumnType.TwoByteConstant; + columnTypes[(int)MetadataColumnIndex.DeclSecurity_Parent] = ColumnType.Token; + columnTypes[(int)MetadataColumnIndex.DeclSecurity_PermissionSet] = ColumnType.Blob; + + columnTypes[(int)MetadataColumnIndex.ClassLayout_PackingSize] = ColumnType.TwoByteConstant; + columnTypes[(int)MetadataColumnIndex.ClassLayout_ClassSize] = ColumnType.FourByteConstant; + columnTypes[(int)MetadataColumnIndex.ClassLayout_Parent] = ColumnType.Token; + + columnTypes[(int)MetadataColumnIndex.FieldLayout_Offset] = ColumnType.FourByteConstant; + columnTypes[(int)MetadataColumnIndex.FieldLayout_Field] = ColumnType.Token; + + columnTypes[(int)MetadataColumnIndex.StandAloneSig_Signature] = ColumnType.Blob; + + columnTypes[(int)MetadataColumnIndex.EventMap_Parent] = ColumnType.Token; + columnTypes[(int)MetadataColumnIndex.EventMap_EventList] = ColumnType.Token; + + columnTypes[(int)MetadataColumnIndex.EventPtr_Event] = ColumnType.Token; + + columnTypes[(int)MetadataColumnIndex.Event_EventFlags] = ColumnType.TwoByteConstant; + columnTypes[(int)MetadataColumnIndex.Event_Name] = ColumnType.Utf8String; + columnTypes[(int)MetadataColumnIndex.Event_EventType] = ColumnType.Token; + + columnTypes[(int)MetadataColumnIndex.PropertyMap_Parent] = ColumnType.Token; + columnTypes[(int)MetadataColumnIndex.PropertyMap_PropertyList] = ColumnType.Token; + + columnTypes[(int)MetadataColumnIndex.PropertyPtr_Property] = ColumnType.Token; + + columnTypes[(int)MetadataColumnIndex.Property_Flags] = ColumnType.TwoByteConstant; + columnTypes[(int)MetadataColumnIndex.Property_Name] = ColumnType.Utf8String; + columnTypes[(int)MetadataColumnIndex.Property_Type] = ColumnType.Blob; + + columnTypes[(int)MetadataColumnIndex.MethodSemantics_Semantics] = ColumnType.TwoByteConstant; + columnTypes[(int)MetadataColumnIndex.MethodSemantics_Method] = ColumnType.Token; + columnTypes[(int)MetadataColumnIndex.MethodSemantics_Association] = ColumnType.Token; + + columnTypes[(int)MetadataColumnIndex.MethodImpl_Class] = ColumnType.Token; + columnTypes[(int)MetadataColumnIndex.MethodImpl_MethodBody] = ColumnType.Token; + columnTypes[(int)MetadataColumnIndex.MethodImpl_MethodDeclaration] = ColumnType.Token; + + columnTypes[(int)MetadataColumnIndex.ModuleRef_Name] = ColumnType.Utf8String; + + columnTypes[(int)MetadataColumnIndex.TypeSpec_Signature] = ColumnType.Blob; + + columnTypes[(int)MetadataColumnIndex.ImplMap_MappingFlags] = ColumnType.TwoByteConstant; + columnTypes[(int)MetadataColumnIndex.ImplMap_MemberForwarded] = ColumnType.Token; + columnTypes[(int)MetadataColumnIndex.ImplMap_ImportName] = ColumnType.Utf8String; + columnTypes[(int)MetadataColumnIndex.ImplMap_ImportScope] = ColumnType.Token; + + columnTypes[(int)MetadataColumnIndex.FieldRva_Rva] = ColumnType.FourByteConstant; + columnTypes[(int)MetadataColumnIndex.FieldRva_Field] = ColumnType.Token; + + columnTypes[(int)MetadataColumnIndex.ENCLog_Token] = ColumnType.FourByteConstant; + columnTypes[(int)MetadataColumnIndex.ENCLog_Op] = ColumnType.FourByteConstant; + + columnTypes[(int)MetadataColumnIndex.ENCMap_Token] = ColumnType.FourByteConstant; + + columnTypes[(int)MetadataColumnIndex.Assembly_HashAlgId] = ColumnType.FourByteConstant; + columnTypes[(int)MetadataColumnIndex.Assembly_MajorVersion] = ColumnType.TwoByteConstant; + columnTypes[(int)MetadataColumnIndex.Assembly_MinorVersion] = ColumnType.TwoByteConstant; + columnTypes[(int)MetadataColumnIndex.Assembly_BuildNumber] = ColumnType.TwoByteConstant; + columnTypes[(int)MetadataColumnIndex.Assembly_RevisionNumber] = ColumnType.TwoByteConstant; + columnTypes[(int)MetadataColumnIndex.Assembly_Flags] = ColumnType.FourByteConstant; + columnTypes[(int)MetadataColumnIndex.Assembly_PublicKey] = ColumnType.Blob; + columnTypes[(int)MetadataColumnIndex.Assembly_Name] = ColumnType.Utf8String; + columnTypes[(int)MetadataColumnIndex.Assembly_Culture] = ColumnType.Utf8String; + + columnTypes[(int)MetadataColumnIndex.AssemblyRef_MajorVersion] = ColumnType.TwoByteConstant; + columnTypes[(int)MetadataColumnIndex.AssemblyRef_MinorVersion] = ColumnType.TwoByteConstant; + columnTypes[(int)MetadataColumnIndex.AssemblyRef_BuildNumber] = ColumnType.TwoByteConstant; + columnTypes[(int)MetadataColumnIndex.AssemblyRef_RevisionNumber] = ColumnType.TwoByteConstant; + columnTypes[(int)MetadataColumnIndex.AssemblyRef_Flags] = ColumnType.FourByteConstant; + columnTypes[(int)MetadataColumnIndex.AssemblyRef_PublicKeyOrToken] = ColumnType.Blob; + columnTypes[(int)MetadataColumnIndex.AssemblyRef_Name] = ColumnType.Utf8String; + columnTypes[(int)MetadataColumnIndex.AssemblyRef_Culture] = ColumnType.Utf8String; + columnTypes[(int)MetadataColumnIndex.AssemblyRef_HashValue] = ColumnType.Blob; + + columnTypes[(int)MetadataColumnIndex.File_Flags] = ColumnType.FourByteConstant; + columnTypes[(int)MetadataColumnIndex.File_Name] = ColumnType.Utf8String; + columnTypes[(int)MetadataColumnIndex.File_HashValue] = ColumnType.Blob; + + columnTypes[(int)MetadataColumnIndex.ExportedType_Flags] = ColumnType.FourByteConstant; + columnTypes[(int)MetadataColumnIndex.ExportedType_TypeDefId] = ColumnType.FourByteConstant; + columnTypes[(int)MetadataColumnIndex.ExportedType_TypeName] = ColumnType.Utf8String; + columnTypes[(int)MetadataColumnIndex.ExportedType_TypeNamespace] = ColumnType.Utf8String; + columnTypes[(int)MetadataColumnIndex.ExportedType_Implementation] = ColumnType.Token; + + columnTypes[(int)MetadataColumnIndex.ManifestResource_Offset] = ColumnType.FourByteConstant; + columnTypes[(int)MetadataColumnIndex.ManifestResource_Flags] = ColumnType.FourByteConstant; + columnTypes[(int)MetadataColumnIndex.ManifestResource_Name] = ColumnType.Utf8String; + columnTypes[(int)MetadataColumnIndex.ManifestResource_Implementation] = ColumnType.Token; + + columnTypes[(int)MetadataColumnIndex.NestedClass_NestedClass] = ColumnType.Token; + columnTypes[(int)MetadataColumnIndex.NestedClass_EnclosingClass] = ColumnType.Token; + + columnTypes[(int)MetadataColumnIndex.GenericParam_Number] = ColumnType.TwoByteConstant; + columnTypes[(int)MetadataColumnIndex.GenericParam_Flags] = ColumnType.TwoByteConstant; + columnTypes[(int)MetadataColumnIndex.GenericParam_Owner] = ColumnType.Token; + columnTypes[(int)MetadataColumnIndex.GenericParam_Name] = ColumnType.Utf8String; + + columnTypes[(int)MetadataColumnIndex.MethodSpec_Method] = ColumnType.Token; + columnTypes[(int)MetadataColumnIndex.MethodSpec_Instantiation] = ColumnType.Blob; + + columnTypes[(int)MetadataColumnIndex.GenericParamConstraint_Owner] = ColumnType.Token; + columnTypes[(int)MetadataColumnIndex.GenericParamConstraint_Constraint] = ColumnType.Token; + + return columnTypes; + } + + private static MetadataTable[] GetColumnTables() + { + MetadataTable[] metadataTables = new MetadataTable[(int)MetadataColumnIndex.Count]; + + metadataTables[(int)MetadataColumnIndex.Module_Generation] = MetadataTable.Module; + metadataTables[(int)MetadataColumnIndex.Module_Name] = MetadataTable.Module; + metadataTables[(int)MetadataColumnIndex.Module_Mvid] = MetadataTable.Module; + metadataTables[(int)MetadataColumnIndex.Module_EncId] = MetadataTable.Module; + metadataTables[(int)MetadataColumnIndex.Module_EncBaseId] = MetadataTable.Module; + + metadataTables[(int)MetadataColumnIndex.TypeRef_ResolutionScope] = MetadataTable.TypeRef; + metadataTables[(int)MetadataColumnIndex.TypeRef_TypeName] = MetadataTable.TypeRef; + metadataTables[(int)MetadataColumnIndex.TypeRef_TypeNamespace] = MetadataTable.TypeRef; + + metadataTables[(int)MetadataColumnIndex.TypeDef_Flags] = MetadataTable.TypeDef; + metadataTables[(int)MetadataColumnIndex.TypeDef_TypeName] = MetadataTable.TypeDef; + metadataTables[(int)MetadataColumnIndex.TypeDef_TypeNamespace] = MetadataTable.TypeDef; + metadataTables[(int)MetadataColumnIndex.TypeDef_Extends] = MetadataTable.TypeDef; + metadataTables[(int)MetadataColumnIndex.TypeDef_FieldList] = MetadataTable.TypeDef; + metadataTables[(int)MetadataColumnIndex.TypeDef_MethodList] = MetadataTable.TypeDef; + + metadataTables[(int)MetadataColumnIndex.FieldPtr_Field] = MetadataTable.FieldPtr; + + metadataTables[(int)MetadataColumnIndex.Field_Flags] = MetadataTable.Field; + metadataTables[(int)MetadataColumnIndex.Field_Name] = MetadataTable.Field; + metadataTables[(int)MetadataColumnIndex.Field_Signature] = MetadataTable.Field; + + metadataTables[(int)MetadataColumnIndex.MethodPtr_Method] = MetadataTable.MethodPtr; + + metadataTables[(int)MetadataColumnIndex.MethodDef_Rva] = MetadataTable.MethodDef; + metadataTables[(int)MetadataColumnIndex.MethodDef_ImplFlags] = MetadataTable.MethodDef; + metadataTables[(int)MetadataColumnIndex.MethodDef_Flags] = MetadataTable.MethodDef; + metadataTables[(int)MetadataColumnIndex.MethodDef_Name] = MetadataTable.MethodDef; + metadataTables[(int)MetadataColumnIndex.MethodDef_Signature] = MetadataTable.MethodDef; + metadataTables[(int)MetadataColumnIndex.MethodDef_ParamList] = MetadataTable.MethodDef; + + metadataTables[(int)MetadataColumnIndex.ParamPtr_Param] = MetadataTable.ParamPtr; + + metadataTables[(int)MetadataColumnIndex.Param_Flags] = MetadataTable.Param; + metadataTables[(int)MetadataColumnIndex.Param_Sequence] = MetadataTable.Param; + metadataTables[(int)MetadataColumnIndex.Param_Name] = MetadataTable.Param; + + metadataTables[(int)MetadataColumnIndex.InterfaceImpl_Class] = MetadataTable.InterfaceImpl; + metadataTables[(int)MetadataColumnIndex.InterfaceImpl_Interface] = MetadataTable.InterfaceImpl; + + metadataTables[(int)MetadataColumnIndex.MemberRef_Class] = MetadataTable.MemberRef; + metadataTables[(int)MetadataColumnIndex.MemberRef_Name] = MetadataTable.MemberRef; + metadataTables[(int)MetadataColumnIndex.MemberRef_Signature] = MetadataTable.MemberRef; + + metadataTables[(int)MetadataColumnIndex.Constant_Type] = MetadataTable.Constant; + metadataTables[(int)MetadataColumnIndex.Constant_Parent] = MetadataTable.Constant; + metadataTables[(int)MetadataColumnIndex.Constant_Value] = MetadataTable.Constant; + + metadataTables[(int)MetadataColumnIndex.CustomAttribute_Parent] = MetadataTable.CustomAttribute; + metadataTables[(int)MetadataColumnIndex.CustomAttribute_Type] = MetadataTable.CustomAttribute; + metadataTables[(int)MetadataColumnIndex.CustomAttribute_Value] = MetadataTable.CustomAttribute; + + metadataTables[(int)MetadataColumnIndex.FieldMarshal_Parent] = MetadataTable.FieldMarshal; + metadataTables[(int)MetadataColumnIndex.FieldMarshal_NativeType] = MetadataTable.FieldMarshal; + + metadataTables[(int)MetadataColumnIndex.DeclSecurity_Action] = MetadataTable.DeclSecurity; + metadataTables[(int)MetadataColumnIndex.DeclSecurity_Parent] = MetadataTable.DeclSecurity; + metadataTables[(int)MetadataColumnIndex.DeclSecurity_PermissionSet] = MetadataTable.DeclSecurity; + + metadataTables[(int)MetadataColumnIndex.ClassLayout_PackingSize] = MetadataTable.ClassLayout; + metadataTables[(int)MetadataColumnIndex.ClassLayout_ClassSize] = MetadataTable.ClassLayout; + metadataTables[(int)MetadataColumnIndex.ClassLayout_Parent] = MetadataTable.ClassLayout; + + metadataTables[(int)MetadataColumnIndex.FieldLayout_Offset] = MetadataTable.FieldLayout; + metadataTables[(int)MetadataColumnIndex.FieldLayout_Field] = MetadataTable.FieldLayout; + + metadataTables[(int)MetadataColumnIndex.StandAloneSig_Signature] = MetadataTable.StandAloneSig; + + metadataTables[(int)MetadataColumnIndex.EventMap_Parent] = MetadataTable.EventMap; + metadataTables[(int)MetadataColumnIndex.EventMap_EventList] = MetadataTable.EventMap; + + metadataTables[(int)MetadataColumnIndex.EventPtr_Event] = MetadataTable.EventPtr; + + metadataTables[(int)MetadataColumnIndex.Event_EventFlags] = MetadataTable.Event; + metadataTables[(int)MetadataColumnIndex.Event_Name] = MetadataTable.Event; + metadataTables[(int)MetadataColumnIndex.Event_EventType] = MetadataTable.Event; + + metadataTables[(int)MetadataColumnIndex.PropertyMap_Parent] = MetadataTable.PropertyMap; + metadataTables[(int)MetadataColumnIndex.PropertyMap_PropertyList] = MetadataTable.PropertyMap; + + metadataTables[(int)MetadataColumnIndex.PropertyPtr_Property] = MetadataTable.PropertyPtr; + + metadataTables[(int)MetadataColumnIndex.Property_Flags] = MetadataTable.Property; + metadataTables[(int)MetadataColumnIndex.Property_Name] = MetadataTable.Property; + metadataTables[(int)MetadataColumnIndex.Property_Type] = MetadataTable.Property; + + metadataTables[(int)MetadataColumnIndex.MethodSemantics_Semantics] = MetadataTable.MethodSemantics; + metadataTables[(int)MetadataColumnIndex.MethodSemantics_Method] = MetadataTable.MethodSemantics; + metadataTables[(int)MetadataColumnIndex.MethodSemantics_Association] = MetadataTable.MethodSemantics; + + metadataTables[(int)MetadataColumnIndex.MethodImpl_Class] = MetadataTable.MethodImpl; + metadataTables[(int)MetadataColumnIndex.MethodImpl_MethodBody] = MetadataTable.MethodImpl; + metadataTables[(int)MetadataColumnIndex.MethodImpl_MethodDeclaration] = MetadataTable.MethodImpl; + + metadataTables[(int)MetadataColumnIndex.ModuleRef_Name] = MetadataTable.ModuleRef; + + metadataTables[(int)MetadataColumnIndex.TypeSpec_Signature] = MetadataTable.TypeSpec; + + metadataTables[(int)MetadataColumnIndex.ImplMap_MappingFlags] = MetadataTable.ImplMap; + metadataTables[(int)MetadataColumnIndex.ImplMap_MemberForwarded] = MetadataTable.ImplMap; + metadataTables[(int)MetadataColumnIndex.ImplMap_ImportName] = MetadataTable.ImplMap; + metadataTables[(int)MetadataColumnIndex.ImplMap_ImportScope] = MetadataTable.ImplMap; + + metadataTables[(int)MetadataColumnIndex.FieldRva_Rva] = MetadataTable.FieldRva; + metadataTables[(int)MetadataColumnIndex.FieldRva_Field] = MetadataTable.FieldRva; + + metadataTables[(int)MetadataColumnIndex.ENCLog_Token] = MetadataTable.ENCLog; + metadataTables[(int)MetadataColumnIndex.ENCLog_Op] = MetadataTable.ENCLog; + + metadataTables[(int)MetadataColumnIndex.ENCMap_Token] = MetadataTable.ENCMap; + + metadataTables[(int)MetadataColumnIndex.Assembly_HashAlgId] = MetadataTable.Assembly; + metadataTables[(int)MetadataColumnIndex.Assembly_MajorVersion] = MetadataTable.Assembly; + metadataTables[(int)MetadataColumnIndex.Assembly_MinorVersion] = MetadataTable.Assembly; + metadataTables[(int)MetadataColumnIndex.Assembly_BuildNumber] = MetadataTable.Assembly; + metadataTables[(int)MetadataColumnIndex.Assembly_RevisionNumber] = MetadataTable.Assembly; + metadataTables[(int)MetadataColumnIndex.Assembly_Flags] = MetadataTable.Assembly; + metadataTables[(int)MetadataColumnIndex.Assembly_PublicKey] = MetadataTable.Assembly; + metadataTables[(int)MetadataColumnIndex.Assembly_Name] = MetadataTable.Assembly; + metadataTables[(int)MetadataColumnIndex.Assembly_Culture] = MetadataTable.Assembly; + + metadataTables[(int)MetadataColumnIndex.AssemblyRef_MajorVersion] = MetadataTable.AssemblyRef; + metadataTables[(int)MetadataColumnIndex.AssemblyRef_MinorVersion] = MetadataTable.AssemblyRef; + metadataTables[(int)MetadataColumnIndex.AssemblyRef_BuildNumber] = MetadataTable.AssemblyRef; + metadataTables[(int)MetadataColumnIndex.AssemblyRef_RevisionNumber] = MetadataTable.AssemblyRef; + metadataTables[(int)MetadataColumnIndex.AssemblyRef_Flags] = MetadataTable.AssemblyRef; + metadataTables[(int)MetadataColumnIndex.AssemblyRef_PublicKeyOrToken] = MetadataTable.AssemblyRef; + metadataTables[(int)MetadataColumnIndex.AssemblyRef_Name] = MetadataTable.AssemblyRef; + metadataTables[(int)MetadataColumnIndex.AssemblyRef_Culture] = MetadataTable.AssemblyRef; + metadataTables[(int)MetadataColumnIndex.AssemblyRef_HashValue] = MetadataTable.AssemblyRef; + + metadataTables[(int)MetadataColumnIndex.File_Flags] = MetadataTable.File; + metadataTables[(int)MetadataColumnIndex.File_Name] = MetadataTable.File; + metadataTables[(int)MetadataColumnIndex.File_HashValue] = MetadataTable.File; + + metadataTables[(int)MetadataColumnIndex.ExportedType_Flags] = MetadataTable.ExportedType; + metadataTables[(int)MetadataColumnIndex.ExportedType_TypeDefId] = MetadataTable.ExportedType; + metadataTables[(int)MetadataColumnIndex.ExportedType_TypeName] = MetadataTable.ExportedType; + metadataTables[(int)MetadataColumnIndex.ExportedType_TypeNamespace] = MetadataTable.ExportedType; + metadataTables[(int)MetadataColumnIndex.ExportedType_Implementation] = MetadataTable.ExportedType; + + metadataTables[(int)MetadataColumnIndex.ManifestResource_Offset] = MetadataTable.ManifestResource; + metadataTables[(int)MetadataColumnIndex.ManifestResource_Flags] = MetadataTable.ManifestResource; + metadataTables[(int)MetadataColumnIndex.ManifestResource_Name] = MetadataTable.ManifestResource; + metadataTables[(int)MetadataColumnIndex.ManifestResource_Implementation] = MetadataTable.ManifestResource; + + metadataTables[(int)MetadataColumnIndex.NestedClass_NestedClass] = MetadataTable.NestedClass; + metadataTables[(int)MetadataColumnIndex.NestedClass_EnclosingClass] = MetadataTable.NestedClass; + + metadataTables[(int)MetadataColumnIndex.GenericParam_Number] = MetadataTable.GenericParam; + metadataTables[(int)MetadataColumnIndex.GenericParam_Flags] = MetadataTable.GenericParam; + metadataTables[(int)MetadataColumnIndex.GenericParam_Owner] = MetadataTable.GenericParam; + metadataTables[(int)MetadataColumnIndex.GenericParam_Name] = MetadataTable.GenericParam; + + metadataTables[(int)MetadataColumnIndex.MethodSpec_Method] = MetadataTable.MethodSpec; + metadataTables[(int)MetadataColumnIndex.MethodSpec_Instantiation] = MetadataTable.MethodSpec; + + metadataTables[(int)MetadataColumnIndex.GenericParamConstraint_Owner] = MetadataTable.GenericParamConstraint; + metadataTables[(int)MetadataColumnIndex.GenericParamConstraint_Constraint] = MetadataTable.GenericParamConstraint; + + return metadataTables; + } + + private static Func[][] GetColumnTokenDecoders() + { + Func[][] decoders = new Func[32][]; + for (int i = 0; i < 32; i++) + { + List ptrTablesPresent = new(); + PtrTablesPresent tablesPresent = (PtrTablesPresent)i; + if (tablesPresent.HasFlag(PtrTablesPresent.Field)) + { + ptrTablesPresent.Add(MetadataTable.FieldPtr); + } + if (tablesPresent.HasFlag(PtrTablesPresent.Param)) + { + ptrTablesPresent.Add(MetadataTable.ParamPtr); + } + if (tablesPresent.HasFlag(PtrTablesPresent.Param)) + { + ptrTablesPresent.Add(MetadataTable.ParamPtr); + } + if (tablesPresent.HasFlag(PtrTablesPresent.Property)) + { + ptrTablesPresent.Add(MetadataTable.PropertyPtr); + } + if (tablesPresent.HasFlag(PtrTablesPresent.Event)) + { + ptrTablesPresent.Add(MetadataTable.EventPtr); + } + + decoders[i] = GetColumnTokenDecode(ptrTablesPresent); + } + return decoders; + } + + private static class ColumnDecodeData + { + private static readonly MetadataTable[] TypeDefOrRef = { MetadataTable.TypeDef, MetadataTable.TypeRef, MetadataTable.TypeSpec }; + private static readonly MetadataTable[] HasConstant = { MetadataTable.Field, MetadataTable.Param, MetadataTable.Property }; + private static readonly MetadataTable[] HasCustomAttribute = + { + MetadataTable.MethodDef, + MetadataTable.Field, + MetadataTable.TypeRef, + MetadataTable.TypeDef, + MetadataTable.Param, + MetadataTable.InterfaceImpl, + MetadataTable.MemberRef, + MetadataTable.Module, + MetadataTable.DeclSecurity, + MetadataTable.Property, + MetadataTable.Event, + MetadataTable.StandAloneSig, + MetadataTable.ModuleRef, + MetadataTable.TypeSpec, + MetadataTable.Assembly, + MetadataTable.AssemblyRef, + MetadataTable.File, + MetadataTable.ExportedType, + MetadataTable.ManifestResource, + MetadataTable.GenericParam, + MetadataTable.GenericParamConstraint, + MetadataTable.MethodSpec }; + + private static readonly MetadataTable[] HasFieldMarshal = { MetadataTable.Field, MetadataTable.Param }; + private static readonly MetadataTable[] HasDeclSecurity = { MetadataTable.TypeDef, MetadataTable.MethodDef, MetadataTable.Assembly }; + private static readonly MetadataTable[] MemberRefParent = { MetadataTable.TypeDef, MetadataTable.TypeRef, MetadataTable.ModuleRef, MetadataTable.MethodDef, MetadataTable.TypeSpec }; + private static readonly MetadataTable[] HasSemantics = { MetadataTable.Event, MetadataTable.Property }; + private static readonly MetadataTable[] MethodDefOrRef = { MetadataTable.MethodDef, MetadataTable.MemberRef }; + private static readonly MetadataTable[] MemberForwarded = { MetadataTable.Field, MetadataTable.MethodDef }; + private static readonly MetadataTable[] Implementation = { MetadataTable.File, MetadataTable.AssemblyRef, MetadataTable.ExportedType }; + private static readonly MetadataTable[] CustomAttributeType = { MetadataTable.Unused, MetadataTable.Unused, MetadataTable.MethodDef, MetadataTable.MemberRef, MetadataTable.Unused }; + private static readonly MetadataTable[] ResolutionScope = { MetadataTable.Module, MetadataTable.ModuleRef, MetadataTable.AssemblyRef, MetadataTable.TypeRef }; + private static readonly MetadataTable[] TypeOrMethodDef = { MetadataTable.TypeDef, MetadataTable.MethodDef }; + + private static readonly MetadataTable[] FieldOrFieldPtr = { (MetadataTable)(-2), MetadataTable.Field, MetadataTable.FieldPtr }; + private static readonly MetadataTable[] MethodDefOrMethodPtr = { (MetadataTable)(-2), MetadataTable.MethodDef, MetadataTable.MethodPtr }; + private static readonly MetadataTable[] ParamOrParamPtr = { (MetadataTable)(-2), MetadataTable.Param, MetadataTable.ParamPtr }; + private static readonly MetadataTable[] EventOrEventPtr = { (MetadataTable)(-2), MetadataTable.Event, MetadataTable.EventPtr }; + private static readonly MetadataTable[] PropertyOrPropertyPtr = { (MetadataTable)(-2), MetadataTable.Property, MetadataTable.PropertyPtr }; + + public static MetadataTable[][] GetCodedIndexDecoderRing() + { + MetadataTable[][] decoderRing = new MetadataTable[(int)MetadataColumnIndex.Count][]; + + decoderRing[(int)MetadataColumnIndex.TypeRef_ResolutionScope] = ResolutionScope; + + decoderRing[(int)MetadataColumnIndex.TypeDef_Extends] = TypeDefOrRef; + decoderRing[(int)MetadataColumnIndex.TypeDef_FieldList] = FieldOrFieldPtr; + decoderRing[(int)MetadataColumnIndex.TypeDef_MethodList] = MethodDefOrMethodPtr; + + decoderRing[(int)MetadataColumnIndex.FieldPtr_Field] = new[] { MetadataTable.Field }; + + decoderRing[(int)MetadataColumnIndex.MethodPtr_Method] = new[] { MetadataTable.MethodDef }; + + decoderRing[(int)MetadataColumnIndex.MethodDef_ParamList] = ParamOrParamPtr; + + decoderRing[(int)MetadataColumnIndex.ParamPtr_Param] = new[] { MetadataTable.Param }; + + decoderRing[(int)MetadataColumnIndex.InterfaceImpl_Class] = new[] { MetadataTable.TypeDef }; + decoderRing[(int)MetadataColumnIndex.InterfaceImpl_Interface] = TypeDefOrRef; + + decoderRing[(int)MetadataColumnIndex.MemberRef_Class] = MemberRefParent; + + decoderRing[(int)MetadataColumnIndex.Constant_Parent] = HasConstant; + + decoderRing[(int)MetadataColumnIndex.CustomAttribute_Parent] = HasCustomAttribute; + decoderRing[(int)MetadataColumnIndex.CustomAttribute_Type] = CustomAttributeType; + + decoderRing[(int)MetadataColumnIndex.FieldMarshal_Parent] = HasFieldMarshal; + + decoderRing[(int)MetadataColumnIndex.DeclSecurity_Parent] = HasDeclSecurity; + + decoderRing[(int)MetadataColumnIndex.ClassLayout_Parent] = new[] { MetadataTable.TypeDef }; + + decoderRing[(int)MetadataColumnIndex.FieldLayout_Field] = new[] { MetadataTable.Field }; + + decoderRing[(int)MetadataColumnIndex.EventMap_Parent] = new[] { MetadataTable.TypeDef }; + decoderRing[(int)MetadataColumnIndex.EventMap_EventList] = EventOrEventPtr; + + decoderRing[(int)MetadataColumnIndex.EventPtr_Event] = new[] { MetadataTable.Event }; + + decoderRing[(int)MetadataColumnIndex.Event_EventType] = TypeDefOrRef; + + decoderRing[(int)MetadataColumnIndex.PropertyMap_Parent] = new[] { MetadataTable.TypeDef }; + decoderRing[(int)MetadataColumnIndex.PropertyMap_PropertyList] = PropertyOrPropertyPtr; + + decoderRing[(int)MetadataColumnIndex.PropertyPtr_Property] = new[] { MetadataTable.Property }; + + decoderRing[(int)MetadataColumnIndex.MethodSemantics_Method] = new[] { MetadataTable.MethodDef }; + decoderRing[(int)MetadataColumnIndex.MethodSemantics_Association] = HasSemantics; + + decoderRing[(int)MetadataColumnIndex.MethodImpl_Class] = new[] { MetadataTable.TypeDef }; + decoderRing[(int)MetadataColumnIndex.MethodImpl_MethodBody] = MethodDefOrRef; + decoderRing[(int)MetadataColumnIndex.MethodImpl_MethodDeclaration] = MethodDefOrRef; + + decoderRing[(int)MetadataColumnIndex.ImplMap_MemberForwarded] = MemberForwarded; + decoderRing[(int)MetadataColumnIndex.ImplMap_ImportScope] = new[] { MetadataTable.ModuleRef }; + + decoderRing[(int)MetadataColumnIndex.FieldRva_Field] = new[] { MetadataTable.ModuleRef }; + + decoderRing[(int)MetadataColumnIndex.ExportedType_Implementation] = Implementation; + + decoderRing[(int)MetadataColumnIndex.ManifestResource_Implementation] = Implementation; + + decoderRing[(int)MetadataColumnIndex.NestedClass_NestedClass] = new[] { MetadataTable.TypeDef }; + decoderRing[(int)MetadataColumnIndex.NestedClass_EnclosingClass] = new[] { MetadataTable.TypeDef }; + + decoderRing[(int)MetadataColumnIndex.GenericParam_Owner] = TypeOrMethodDef; + + decoderRing[(int)MetadataColumnIndex.MethodSpec_Method] = MethodDefOrRef; + + decoderRing[(int)MetadataColumnIndex.GenericParamConstraint_Owner] = new[] { MetadataTable.GenericParam }; + decoderRing[(int)MetadataColumnIndex.GenericParamConstraint_Constraint] = TypeDefOrRef; + + return decoderRing; + } + } + + private static uint DecodeCodedIndex(uint input, ReadOnlySpan tablesEncoded) + { + uint encodingMask = BitOperations.RoundUpToPowerOf2((uint)tablesEncoded.Length) - 1; + int bitsForTableEncoding = 32 - BitOperations.LeadingZeroCount(BitOperations.RoundUpToPowerOf2((uint)tablesEncoded.Length) - 1); + MetadataTable table = tablesEncoded[(int)(input & encodingMask)]; + uint rid = input >> bitsForTableEncoding; + return CreateToken(table, rid); + } + + private static Func[] GetColumnTokenDecode(List ptrTablesPresent) + { + Func[] columnTokenDecode = new Func[(int)MetadataColumnIndex.Count]; + MetadataTable[][] decoderRing = ColumnDecodeData.GetCodedIndexDecoderRing(); + for (int i = 0; i < decoderRing.Length; i++) + { + if (decoderRing[i] != null) + { + columnTokenDecode[i] = ComputeDecoder(decoderRing[i]); + } + } + + return columnTokenDecode; + + Func ComputeDecoder(MetadataTable[] decoderData) + { + Func result; + + if (decoderData.Length == 1) + { + MetadataTable metadataTable = decoderData[0]; + result = delegate (uint input) { return CreateToken(metadataTable, input); }; + } + else + { + if ((decoderData.Length == 1) && decoderData[0] == (MetadataTable)(-2)) + { + MetadataTable metadataTable = decoderData[0]; + if (!ptrTablesPresent.Contains(decoderData[2])) + { + metadataTable = decoderData[1]; + } + else + { + metadataTable = decoderData[2]; + } + result = delegate (uint input) { return CreateToken(metadataTable, input); }; + } + else + { + result = delegate (uint input) { return DecodeCodedIndex(input, decoderData); }; + } + } + + return result; + } + } +} diff --git a/src/native/managed/cdacreader/src/Helpers/EcmaMetadataReader.StaticHelpers.cs b/src/native/managed/cdacreader/src/Helpers/EcmaMetadataReader.StaticHelpers.cs new file mode 100644 index 00000000000000..07c8ef21fea9ad --- /dev/null +++ b/src/native/managed/cdacreader/src/Helpers/EcmaMetadataReader.StaticHelpers.cs @@ -0,0 +1,59 @@ +// 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; +using System.Linq; +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Text; +using System.Threading.Tasks; +using Microsoft.Diagnostics.DataContractReader.Contracts; + +namespace Microsoft.Diagnostics.DataContractReader.Helpers; + +internal partial class EcmaMetadataReader +{ + public static MetadataTable TokenToTable(uint token) + { + byte tableIndex = (byte)(token >> 24); + if (tableIndex > (uint)MetadataTable.GenericParamConstraint) + { + return MetadataTable.Unused; + } + else + { + return (MetadataTable)tableIndex; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool IsSigned() where T : struct, INumberBase, IMinMaxValue + { + return T.IsNegative(T.MinValue); + } + private static bool TryReadCore(ReadOnlySpan bytes, out T value) where T : struct, IBinaryInteger, IMinMaxValue + { + return T.TryReadLittleEndian(bytes.Slice(0, Unsafe.SizeOf()), IsSigned(), out value); + } + + private static T ReadLittleEndian(ReadOnlySpan bytes) where T : struct, IBinaryInteger, IMinMaxValue + { + if (!TryReadCore(bytes, out T value)) + throw new ArgumentOutOfRangeException(nameof(value)); + return value; + } + + public static uint RidFromToken(uint token) + { + return token & 0xFFFFFF; + } + public static uint CreateToken(MetadataTable table, uint rid) + { + ArgumentOutOfRangeException.ThrowIfGreaterThan(rid, 0xFFFFFF, nameof(rid)); + ArgumentOutOfRangeException.ThrowIfGreaterThan((int)table, (int)MetadataTable.GenericParamConstraint, nameof(table)); + return ((uint)table << 24) | rid; + } + +} diff --git a/src/native/managed/cdacreader/src/Helpers/EcmaMetadataReader.cs b/src/native/managed/cdacreader/src/Helpers/EcmaMetadataReader.cs new file mode 100644 index 00000000000000..d358ccc15d5240 --- /dev/null +++ b/src/native/managed/cdacreader/src/Helpers/EcmaMetadataReader.cs @@ -0,0 +1,399 @@ +// 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; +using System.Linq; +using System.Numerics; +using System.Reflection; +using System.Reflection.Metadata.Ecma335; +using System.Runtime.CompilerServices; +using System.Text; +using System.Threading.Tasks; +using Microsoft.Diagnostics.DataContractReader.Contracts; + +namespace Microsoft.Diagnostics.DataContractReader.Helpers; + +internal struct EcmaMetadataCursor +{ + internal ReadOnlyMemory TableData; + internal MetadataTable Table; + internal uint Rid; + internal int RowSize; + + public ReadOnlySpan Row + { + get + { + return TableData.Span.Slice((int)(RowSize * (Rid - 1)), (int)RowSize); + } + } +} + +internal partial class EcmaMetadataReader +{ + private EcmaMetadata _ecmaMetadata; + private int[] rowSize; + private int[] columnSize; + private int[] columnOffset; + private Func[] columnTokenDecode; + + + public EcmaMetadataReader(ReadOnlyMemory imageMemory) + { + columnSize = new int[(int)MetadataColumnIndex.Count]; + columnOffset = new int[(int)MetadataColumnIndex.Count]; + rowSize = new int[(int)MetadataTable.Count]; + columnTokenDecode = Array.Empty>(); + + ReadOnlySpan image = imageMemory.Span; + int magic = ReadLittleEndian(image); + if (magic != 0x424A5342) + throw new ArgumentOutOfRangeException(nameof(imageMemory)); + + int versionSize = ReadLittleEndian(image.Slice(12, 4)); + versionSize = AlignUp(versionSize, 4); + + ReadOnlySpan versionName = image.Slice(16, versionSize); + int nullTerminatorIndex = versionName.IndexOf((byte)0); + + if ((nullTerminatorIndex == -1) || (nullTerminatorIndex == 0)) + { + // VersionName isn't null terminated + throw new ArgumentException(nameof(imageMemory)); + } + + string metadataVersion = Encoding.UTF8.GetString(versionName.Slice(0, nullTerminatorIndex)); + + int currentOffset = 16 + versionSize; + + currentOffset += 2; // Flags ... unused in this implementation + ushort streams = ReadLittleEndian(image.Slice(currentOffset)); + currentOffset += 2; + + ReadOnlyMemory StringHeap = null; + ReadOnlyMemory UserStringHeap = null; + ReadOnlyMemory BlobHeap = null; + ReadOnlyMemory GuidHeap = null; + ReadOnlyMemory TablesHeap = null; + + for (ushort iStream = 0; iStream < streams; iStream++) + { + var stream = ReadStream(ref image); + if (stream.Name == "#Strings") + { + StringHeap = stream.Data; + } + else if (stream.Name == "#US") + { + UserStringHeap = stream.Data; + } + else if (stream.Name == "#Blob") + { + BlobHeap = stream.Data; + } + else if (stream.Name == "#GUID") + { + GuidHeap = stream.Data; + } + else if (stream.Name == "#~") + { + TablesHeap = stream.Data; + } + else if (stream.Name == "#-") + { + TablesHeap = stream.Data; + } + } + + if (TablesHeap.Length == 0) + { + throw new ArgumentException(nameof(imageMemory)); + } + ReadOnlySpan tables = TablesHeap.Span; + + byte heapSizes = ReadLittleEndian(tables.Slice(6, 1)); + ulong validTables = ReadLittleEndian(tables.Slice(8, 8)); + ulong sortedTables = ReadLittleEndian(tables.Slice(16, 8)); + + int[] tableRowCounts = new int[(int)MetadataTable.Count]; + bool[] isSorted = new bool[(int)MetadataTable.Count]; + int currentTablesOffset = 24; + for (int i = 0; i < (int)MetadataTable.Count; i++) + { + if ((validTables & ((ulong)1 << i)) != 0) + { + tableRowCounts[i] = ReadLittleEndian(tables.Slice(currentTablesOffset)); + currentTablesOffset += 4; + } + if ((sortedTables & ((ulong)1 << i)) != 0) + { + isSorted[i] = true; + } + } + + // There is an undocumented flag "extra_data" which adds a 4 byte pad here. + if ((heapSizes & 0x40) != 0) + { + currentTablesOffset += 4; + } + + EcmaMetadataSchema schema = new EcmaMetadataSchema(metadataVersion, + largeStringHeap: (heapSizes & 1) != 0, + largeGuidHeap: (heapSizes & 2) != 0, + largeBlobHeap: (heapSizes & 4) != 0, + rowCount: tableRowCounts, + isSorted: isSorted, + variableSizedColumnsAre4BytesLong: false + ); + + ReadOnlyMemory[] tableData = new ReadOnlyMemory[(int)MetadataTable.Count]; + + _ecmaMetadata = new EcmaMetadata(schema, tableData, StringHeap, UserStringHeap, BlobHeap, GuidHeap); + + Init(); + + // Init will compute row sizes, which is necessary for actually computing the tableData + + for (int i = 0; i < (int)MetadataTable.Count; i++) + { + checked + { + if ((validTables & ((ulong)1 << i)) != 0) + { + int tableSize = checked(rowSize![i] * _ecmaMetadata.Schema.RowCount[i]); + tableData[i] = TablesHeap.Slice(currentTablesOffset, tableSize); + currentTablesOffset += tableSize; + } + } + } + + (string Name, ReadOnlyMemory Data) ReadStream(ref ReadOnlySpan image) + { + int offset = ReadLittleEndian(image.Slice(currentOffset)); + currentOffset += 4; + int size = ReadLittleEndian(image.Slice(currentOffset)); + currentOffset += 4; + int nameStartOffset = currentOffset; + int nameLen = 0; + while (image[currentOffset++] != 0) + { + nameLen++; + if (nameLen > 31) throw new ArgumentException(nameof(imageMemory)); + } + + if (nameLen == 0) throw new ArgumentException(nameof(imageMemory)); + + currentOffset = AlignUp(currentOffset, 4); + return (Encoding.ASCII.GetString(image.Slice(nameStartOffset, nameLen)), imageMemory.Slice(offset, size)); + } + } + + private static int AlignUp(int input, int alignment) + { + return input + (alignment - 1) & ~(alignment - 1); + } + + public EcmaMetadataReader(EcmaMetadata ecmaMetadata) + { + columnTokenDecode = Array.Empty>(); + columnSize = new int[(int)MetadataColumnIndex.Count]; + columnOffset = new int[(int)MetadataColumnIndex.Count]; + rowSize = new int[(int)MetadataTable.Count]; + + _ecmaMetadata = ecmaMetadata; + Init(); + } + + private void Init() + { + PtrTablesPresent ptrTable = PtrTablesPresent.None; + if (RowCount(MetadataTable.MethodPtr) != 0) + ptrTable |= PtrTablesPresent.Method; + if (RowCount(MetadataTable.FieldPtr) != 0) + ptrTable |= PtrTablesPresent.Field; + if (RowCount(MetadataTable.ParamPtr) != 0) + ptrTable |= PtrTablesPresent.Param; + if (RowCount(MetadataTable.EventPtr) != 0) + ptrTable |= PtrTablesPresent.Event; + if (RowCount(MetadataTable.PropertyPtr) != 0) + ptrTable |= PtrTablesPresent.Property; + + columnTokenDecode = columnTokenDecoders[(int)ptrTable]; + + ComputeColumnSizesAndOffsets(); + + void ComputeColumnSizesAndOffsets() + { + MetadataTable currentTable = MetadataTable.Unused; + MetadataColumnIndex? prevColumn = null; + + for (int i = 0; i < (int)MetadataColumnIndex.Count; i++) + { + MetadataColumnIndex column = (MetadataColumnIndex)i; + MetadataTable newColumnTable = ColumnTable(column); + if (currentTable != newColumnTable) + { + if (prevColumn.HasValue) + rowSize[(int)currentTable] = ComputeColumnEnd(prevColumn.Value); + currentTable = newColumnTable; + columnOffset[i] = 0; + } + else + { + columnOffset[i] = ComputeColumnEnd(prevColumn!.Value); + } + prevColumn = column; + + columnSize[i] = columnTypes[i] switch + { + ColumnType.TwoByteConstant => 2, + ColumnType.FourByteConstant => 4, + ColumnType.Utf8String => _ecmaMetadata.Schema.LargeStringHeap ? 4 : 2, + ColumnType.Blob => _ecmaMetadata.Schema.LargeBlobHeap ? 4 : 2, + ColumnType.Guid => _ecmaMetadata.Schema.LargeGuidHeap ? 4 : 2, + ColumnType.Token => _ecmaMetadata.Schema.VariableSizedColumnsAreAll4BytesLong ? 4 : CodedIndexEncodingBytes(codedIndexDecoderRing[i]), + _ => throw new System.Exception() + }; + } + + rowSize[(int)ColumnTable(prevColumn!.Value)] = ComputeColumnEnd(prevColumn!.Value); + } + + int ComputeColumnEnd(MetadataColumnIndex column) + { + return ColumnOffset(column) + ColumnSize(column); + } + } + + public EcmaMetadata UnderlyingMetadata => _ecmaMetadata; + + public uint GetColumnAsConstant(EcmaMetadataCursor c, MetadataColumnIndex col_idx) + { + if (columnTypes[(int)col_idx] != ColumnType.TwoByteConstant && columnTypes[(int)col_idx] != ColumnType.FourByteConstant) + throw new ArgumentOutOfRangeException(nameof(col_idx)); + return GetColumnRaw(c, col_idx); + } + + public System.ReadOnlySpan GetColumnAsBlob(EcmaMetadataCursor c, MetadataColumnIndex col_idx) + { + throw new NotImplementedException(); + } + + public uint GetColumnAsToken(EcmaMetadataCursor c, MetadataColumnIndex col_idx) + { + Func decoder = columnTokenDecode[(int)col_idx]; + if (decoder == null) + { + throw new ArgumentOutOfRangeException(nameof(col_idx)); + } + uint rawResult = GetColumnRaw(c, col_idx); + uint result = decoder(rawResult); + return result; + } + + public System.ReadOnlySpan GetColumnAsUtf8(EcmaMetadataCursor c, MetadataColumnIndex col_idx) + { + if (columnTypes[(int)col_idx] != ColumnType.Utf8String) + throw new ArgumentOutOfRangeException(nameof(col_idx)); + int initialOffset = (int)GetColumnRaw(c, col_idx); + + if (initialOffset == 0) + return default(ReadOnlySpan); + + checked + { + ReadOnlySpan stringHeap = _ecmaMetadata.StringHeap.Span; + int curOffset = initialOffset; + while (stringHeap[curOffset] != '\0') + { + curOffset++; + } + return stringHeap.Slice(initialOffset, curOffset - initialOffset); + } + } + + public bool TryGetCursor(uint token, out EcmaMetadataCursor cursor) + { + cursor = default; + MetadataTable table = TokenToTable(token); + if (table == MetadataTable.Unused) + return false; + + if (RowCount(table) < RidFromToken(token)) + return false; + + cursor.Rid = RidFromToken(token); + cursor.TableData = _ecmaMetadata.Tables[(int)table]; + cursor.RowSize = rowSize[(int)table]; + cursor.Table = table; + return true; + } + + public bool TryGetCursorToFirstEntryInTable(MetadataTable table, out EcmaMetadataCursor cursor) + { + cursor = default; + if (RowCount(table) > 0) + { + cursor.Rid = 1; + cursor.TableData = _ecmaMetadata.Tables[(int)table]; + cursor.RowSize = rowSize[(int)table]; + cursor.Table = table; + return true; + } + return false; + } + + public bool TryFindRowFromCursor(EcmaMetadataCursor tableCursor, MetadataColumnIndex col_idx, uint searchToken, out EcmaMetadataCursor foundRow) + { + foundRow = tableCursor; + +/* if (_ecmaMetadata.Schema.IsSorted[(int)tableCursor.Table]) + { + // TODO(cdac) implement sorted searching in metadata + } + else*/ + { + while (foundRow.Rid <= RowCount(tableCursor.Table)) + { + if (GetColumnAsToken(foundRow, col_idx) == searchToken) + { + return true; + } + foundRow.Rid += 1; + } + } + return false; + } + + public EcmaMetadataCursor GetCursor(uint token) + { + if (!TryGetCursor(token, out EcmaMetadataCursor cursor)) + { + throw new ArgumentOutOfRangeException(nameof(token)); + } + return cursor; + } + + public static uint GetToken(EcmaMetadataCursor c) + { + return CreateToken(c.Table, c.Rid); + } + + private static MetadataTable ColumnTable(MetadataColumnIndex column) + { + return columnTable[(int)column]; + } + + public virtual string GetColumnAsUtf8String(EcmaMetadataCursor c, MetadataColumnIndex col_idx) + { + ReadOnlySpan utf8Data = GetColumnAsUtf8(c, col_idx); + string str = string.Empty; + if (utf8Data.Length > 0) + { + str = System.Text.Encoding.UTF8.GetString(utf8Data); + } + return str; + } +} diff --git a/src/native/managed/cdacreader/src/Helpers/Metadata.cs b/src/native/managed/cdacreader/src/Helpers/Metadata.cs new file mode 100644 index 00000000000000..5e683d5c72b057 --- /dev/null +++ b/src/native/managed/cdacreader/src/Helpers/Metadata.cs @@ -0,0 +1,334 @@ +// 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 Microsoft.Diagnostics.DataContractReader.Contracts; + +namespace Microsoft.Diagnostics.DataContractReader.Helpers; + +internal enum MetadataTable +{ + Unused = -1, + Module = 0x0, + TypeRef = 0x01, + TypeDef = 0x02, + FieldPtr = 0x03, + Field = 0x04, + MethodPtr = 0x05, + MethodDef = 0x06, + ParamPtr = 0x07, + Param = 0x08, + InterfaceImpl = 0x09, + MemberRef = 0x0a, + Constant = 0x0b, + CustomAttribute = 0x0c, + FieldMarshal = 0x0d, + DeclSecurity = 0x0e, + ClassLayout = 0x0f, + FieldLayout = 0x10, + StandAloneSig = 0x11, + EventMap = 0x12, + EventPtr = 0x13, + Event = 0x14, + PropertyMap = 0x15, + PropertyPtr = 0x16, + Property = 0x17, + MethodSemantics = 0x18, + MethodImpl = 0x19, + ModuleRef = 0x1a, + TypeSpec = 0x1b, + ImplMap = 0x1c, + FieldRva = 0x1d, + ENCLog = 0x1e, + ENCMap = 0x1f, + Assembly = 0x20, + AssemblyProcessor = 0x21, + AssemblyOS = 0x22, + AssemblyRef = 0x23, + AssemblyRefProcessor = 0x24, + AssemblyRefOS = 0x25, + File = 0x26, + ExportedType = 0x27, + ManifestResource = 0x28, + NestedClass = 0x29, + GenericParam = 0x2a, + MethodSpec = 0x2b, + GenericParamConstraint = 0x2c, + Count = 0x2d +} + +internal class EcmaMetadata +{ + public EcmaMetadata(EcmaMetadataSchema schema, + ReadOnlyMemory[] tables, + ReadOnlyMemory stringHeap, + ReadOnlyMemory userStringHeap, + ReadOnlyMemory blobHeap, + ReadOnlyMemory guidHeap) + { + Schema = schema; + _tables = tables; + StringHeap = stringHeap; + UserStringHeap = userStringHeap; + BlobHeap = blobHeap; + GuidHeap = guidHeap; + } + + public EcmaMetadataSchema Schema { get; init; } + + private ReadOnlyMemory[] _tables; + public ReadOnlySpan> Tables => _tables; + public ReadOnlyMemory StringHeap { get; init; } + public ReadOnlyMemory UserStringHeap { get; init; } + public ReadOnlyMemory BlobHeap { get; init; } + public ReadOnlyMemory GuidHeap { get; init; } + + private Microsoft.Diagnostics.DataContractReader.Helpers.EcmaMetadataReader? _ecmaMetadataReader; + public Microsoft.Diagnostics.DataContractReader.Helpers.EcmaMetadataReader EcmaMetadataReader + { + get + { + _ecmaMetadataReader ??= new Helpers.EcmaMetadataReader(this); + return _ecmaMetadataReader; + } + } +} + +internal enum MetadataColumnIndex +{ + Module_Generation, + Module_Name, + Module_Mvid, + Module_EncId, + Module_EncBaseId, + + TypeRef_ResolutionScope, + TypeRef_TypeName, + TypeRef_TypeNamespace, + + TypeDef_Flags, + TypeDef_TypeName, + TypeDef_TypeNamespace, + TypeDef_Extends, + TypeDef_FieldList, + TypeDef_MethodList, + + FieldPtr_Field, + + Field_Flags, + Field_Name, + Field_Signature, + + MethodPtr_Method, + + MethodDef_Rva, + MethodDef_ImplFlags, + MethodDef_Flags, + MethodDef_Name, + MethodDef_Signature, + MethodDef_ParamList, + + ParamPtr_Param, + + Param_Flags, + Param_Sequence, + Param_Name, + + InterfaceImpl_Class, + InterfaceImpl_Interface, + + MemberRef_Class, + MemberRef_Name, + MemberRef_Signature, + + Constant_Type, + Constant_Parent, + Constant_Value, + + CustomAttribute_Parent, + CustomAttribute_Type, + CustomAttribute_Value, + + FieldMarshal_Parent, + FieldMarshal_NativeType, + + DeclSecurity_Action, + DeclSecurity_Parent, + DeclSecurity_PermissionSet, + + ClassLayout_PackingSize, + ClassLayout_ClassSize, + ClassLayout_Parent, + + FieldLayout_Offset, + FieldLayout_Field, + + StandAloneSig_Signature, + + EventMap_Parent, + EventMap_EventList, + + EventPtr_Event, + + Event_EventFlags, + Event_Name, + Event_EventType, + + PropertyMap_Parent, + PropertyMap_PropertyList, + + PropertyPtr_Property, + + Property_Flags, + Property_Name, + Property_Type, + + MethodSemantics_Semantics, + MethodSemantics_Method, + MethodSemantics_Association, + + MethodImpl_Class, + MethodImpl_MethodBody, + MethodImpl_MethodDeclaration, + + ModuleRef_Name, + + TypeSpec_Signature, + + ImplMap_MappingFlags, + ImplMap_MemberForwarded, + ImplMap_ImportName, + ImplMap_ImportScope, + + FieldRva_Rva, + FieldRva_Field, + + ENCLog_Token, + ENCLog_Op, + + ENCMap_Token, + + Assembly_HashAlgId, + Assembly_MajorVersion, + Assembly_MinorVersion, + Assembly_BuildNumber, + Assembly_RevisionNumber, + Assembly_Flags, + Assembly_PublicKey, + Assembly_Name, + Assembly_Culture, + + AssemblyRef_MajorVersion, + AssemblyRef_MinorVersion, + AssemblyRef_BuildNumber, + AssemblyRef_RevisionNumber, + AssemblyRef_Flags, + AssemblyRef_PublicKeyOrToken, + AssemblyRef_Name, + AssemblyRef_Culture, + AssemblyRef_HashValue, + + File_Flags, + File_Name, + File_HashValue, + + ExportedType_Flags, + ExportedType_TypeDefId, + ExportedType_TypeName, + ExportedType_TypeNamespace, + ExportedType_Implementation, + + ManifestResource_Offset, + ManifestResource_Flags, + ManifestResource_Name, + ManifestResource_Implementation, + + NestedClass_NestedClass, + NestedClass_EnclosingClass, + + GenericParam_Number, + GenericParam_Flags, + GenericParam_Owner, + GenericParam_Name, + + MethodSpec_Method, + MethodSpec_Instantiation, + + GenericParamConstraint_Owner, + GenericParamConstraint_Constraint, + + Count +} + +internal class Metadata +{ + private readonly Target _target; + private readonly Dictionary _metadata = []; + + public Metadata(Target target) + { + _target = target; + } + + public virtual EcmaMetadata GetMetadata(Contracts.ModuleHandle module) + { + if (_metadata.TryGetValue(module.Address, out EcmaMetadata? result)) + return result; + + AvailableMetadataType metadataType = _target.Contracts.Loader.GetAvailableMetadataType(module); + + if (metadataType == AvailableMetadataType.ReadOnly) + { + if (this.MetadataProvider != null) + result = this.MetadataProvider(module); + if (result == null) + { + TargetPointer address = _target.Contracts.Loader.GetMetadataAddress(module, out ulong size); + byte[] data = new byte[size]; + _target.ReadBuffer(address, data); + result = (new Helpers.EcmaMetadataReader(new ReadOnlyMemory(data))).UnderlyingMetadata; + } + } + else if (metadataType == AvailableMetadataType.ReadWriteSavedCopy) + { + TargetPointer address = _target.Contracts.Loader.GetReadWriteSavedMetadataAddress(module, out ulong size); + byte[] data = new byte[size]; + _target.ReadBuffer(address, data); + result = (new Helpers.EcmaMetadataReader(new ReadOnlyMemory(data))).UnderlyingMetadata; + } + else + { + var targetEcmaMetadata = _target.Contracts.Loader.GetReadWriteMetadata(module); + result = new EcmaMetadata(targetEcmaMetadata.Schema, + GetReadOnlyMemoryFromTargetSpans(targetEcmaMetadata.Tables), + GetReadOnlyMemoryFromTargetSpan(targetEcmaMetadata.StringHeap), + GetReadOnlyMemoryFromTargetSpan(targetEcmaMetadata.UserStringHeap), + GetReadOnlyMemoryFromTargetSpan(targetEcmaMetadata.BlobHeap), + GetReadOnlyMemoryFromTargetSpan(targetEcmaMetadata.GuidHeap)); + + ReadOnlyMemory GetReadOnlyMemoryFromTargetSpan(TargetSpan span) + { + if (span.Size == 0) + return default; + byte[] data = new byte[span.Size]; + _target.ReadBuffer(span.Address, data); + return new ReadOnlyMemory(data); + } + ReadOnlyMemory[] GetReadOnlyMemoryFromTargetSpans(ReadOnlySpan spans) + { + ReadOnlyMemory[] memories = new ReadOnlyMemory[spans.Length]; + for (int i = 0; i < spans.Length; i++) + { + memories[i] = GetReadOnlyMemoryFromTargetSpan(spans[i]); + } + return memories; + } + } + + _metadata.Add(module.Address, result); + return result; + } + + public Func? MetadataProvider; +} diff --git a/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs b/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs index 5ebbbd7a7471e9..e4f2968f29f797 100644 --- a/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs +++ b/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs @@ -1,7 +1,11 @@ // 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; using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Diagnostics.Contracts; using System.Runtime.InteropServices; using System.Runtime.InteropServices.Marshalling; @@ -89,7 +93,7 @@ public unsafe int GetMethodTableData(ulong mt, DacpMethodTableData* data) try { Contracts.IRuntimeTypeSystem contract = _target.Contracts.RuntimeTypeSystem; - Contracts.MethodTableHandle methodTable = contract.GetMethodTableHandle(mt); + Contracts.TypeHandle methodTable = contract.GetTypeHandle(mt); DacpMethodTableData result = default; result.baseSize = contract.GetBaseSize(methodTable); @@ -123,7 +127,7 @@ public unsafe int GetMethodTableData(ulong mt, DacpMethodTableData* data) *data = result; return HResults.S_OK; } - catch (Exception ex) + catch (System.Exception ex) { return ex.HResult; } @@ -137,16 +141,78 @@ public unsafe int GetMethodTableForEEClass(ulong eeClassReallyCanonMT, ulong* va try { Contracts.IRuntimeTypeSystem contract = _target.Contracts.RuntimeTypeSystem; - Contracts.MethodTableHandle methodTableHandle = contract.GetMethodTableHandle(eeClassReallyCanonMT); + Contracts.TypeHandle methodTableHandle = contract.GetTypeHandle(eeClassReallyCanonMT); *value = methodTableHandle.Address; return HResults.S_OK; } - catch (Exception ex) + catch (global::System.Exception ex) { return ex.HResult; } } - public unsafe int GetMethodTableName(ulong mt, uint count, char* mtName, uint* pNeeded) => HResults.E_NOTIMPL; + + private unsafe void CopyStringToTargetBuffer(char* stringBuf, uint bufferSize, uint* neededBufferSize, string str) + { + ReadOnlySpan strSpan = str.AsSpan(); + if (neededBufferSize != null) + *neededBufferSize = checked((uint)(strSpan.Length + 1)); + + if (stringBuf != null && bufferSize > 0) + { + Span target = new Span(stringBuf, checked((int)bufferSize)); + int nullTerminatorLocation = strSpan.Length > bufferSize - 1 ? checked((int)(bufferSize - 1)) : strSpan.Length; + strSpan = strSpan.Slice(0, nullTerminatorLocation); + strSpan.CopyTo(target); + target[nullTerminatorLocation] = '\0'; + } + } + + public unsafe int GetMethodTableName(ulong mt, uint count, char* mtName, uint* pNeeded) + { + if (mt == 0) + return HResults.E_INVALIDARG; + + try + { + Contracts.IRuntimeTypeSystem typeSystemContract = _target.Contracts.RuntimeTypeSystem; + Contracts.TypeHandle methodTableHandle = typeSystemContract.GetTypeHandle(mt); + if (typeSystemContract.IsFreeObjectMethodTable(methodTableHandle)) + { + CopyStringToTargetBuffer(mtName, count, pNeeded, "Free"); + return HResults.S_OK; + } + + // TODO(cdac) - The original code handles the case of the module being in the process of being unloaded. This is not yet handled + + System.Text.StringBuilder methodTableName = new(); + try + { + TargetPointer modulePointer = typeSystemContract.GetModule(methodTableHandle); + TypeNameBuilder.AppendType(_target, methodTableName, methodTableHandle, TypeNameFormat.FormatNamespace | TypeNameFormat.FormatFullInst); + } + catch + { + try + { + string? fallbackName = _target.Contracts.DacStreams.StringFromEEAddress(mt); + if (fallbackName != null) + { + methodTableName.Clear(); + methodTableName.Append(fallbackName); + } + } + catch + { } + } + CopyStringToTargetBuffer(mtName, count, pNeeded, methodTableName.ToString()); + return HResults.S_OK; + } + catch (global::System.Exception ex) + { + return ex.HResult; + } + } + public unsafe int GetMethodTableSlot(ulong mt, uint slot, ulong* value) => HResults.E_NOTIMPL; public unsafe int GetMethodTableTransparencyData(ulong mt, void* data) => HResults.E_NOTIMPL; public unsafe int GetModule(ulong addr, void** mod) => HResults.E_NOTIMPL; @@ -191,7 +257,7 @@ public unsafe int GetModuleData(ulong moduleAddr, DacpModuleData* data) data->dwBaseClassIndex = 0; data->dwModuleIndex = 0; } - catch (Exception e) + catch (global::System.Exception e) { return e.HResult; } @@ -208,7 +274,7 @@ public unsafe int GetNestedExceptionData(ulong exception, ulong* exceptionObject *exceptionObject = exceptionObjectLocal; *nextNestedException = nextNestedExceptionLocal; } - catch (Exception ex) + catch (global::System.Exception ex) { return ex.HResult; } @@ -234,7 +300,7 @@ public unsafe int GetObjectExceptionData(ulong objectAddress, DacpExceptionObjec data->HResult = exceptionData.HResult; data->XCode = exceptionData.XCode; } - catch (Exception ex) + catch (System.Exception ex) { return ex.HResult; } @@ -284,7 +350,7 @@ public unsafe int GetThreadData(ulong thread, DacpThreadData* data) data->lastThrownObjectHandle = threadData.LastThrownObjectHandle; data->nextThread = threadData.NextThread; } - catch (Exception ex) + catch (global::System.Exception ex) { return ex.HResult; } @@ -314,7 +380,7 @@ public unsafe int GetThreadStoreData(DacpThreadStoreData* data) data->fHostConfig = 0; // Always 0 for non-Framework } - catch (Exception ex) + catch (global::System.Exception ex) { return ex.HResult; } diff --git a/src/native/managed/cdacreader/src/Legacy/TypeNameBuilder.cs b/src/native/managed/cdacreader/src/Legacy/TypeNameBuilder.cs new file mode 100644 index 00000000000000..510367bd12480f --- /dev/null +++ b/src/native/managed/cdacreader/src/Legacy/TypeNameBuilder.cs @@ -0,0 +1,558 @@ +// 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.Text; +using Microsoft.Diagnostics.DataContractReader.Contracts; +using Microsoft.Diagnostics.DataContractReader.Helpers; + +namespace Microsoft.Diagnostics.DataContractReader.Legacy; + +[Flags] +internal enum TypeNameFormat +{ + FormatNamespace = 1, + FormatFullInst = 2, + FormatAngleBrackets = 4, + FormatAssembly = 8, + FormatGenericParam = 16, +} + +internal struct TypeNameBuilder +{ + [Flags] + private enum ParseState + { + Start = 1, + Name = 2, + GenArgs = 4, + PtrArr = 8, + ByRef = 16, + AssemSpec = 32, + Error + } + private StringBuilder TypeString; + private Target Target; + + private ParseState State; + private bool UseAngleBracketsForGenerics { get; init; } + private bool NestedName; + private bool HasAssemblySpec; + private bool FirstInstArg; + private int InstNesting; + private Stack? GenericStartsStack; + + private TypeNameBuilder(StringBuilder typeString, Target target, TypeNameFormat format) + { + TypeString = typeString; + Target = target; + UseAngleBracketsForGenerics = format.HasFlag(TypeNameFormat.FormatAngleBrackets); + State = ParseState.Start; + } + + public static void AppendType(Target target, StringBuilder stringBuilder, Contracts.TypeHandle typeHandle, TypeNameFormat format) + { + AppendType(target, stringBuilder, typeHandle, default, format); + } + public static void AppendType(Target target, StringBuilder stringBuilder, Contracts.TypeHandle typeHandle, ReadOnlySpan typeInstantiation, TypeNameFormat format) + { + TypeNameBuilder builder = new(stringBuilder, target, format); + AppendTypeCore(ref builder, typeHandle, typeInstantiation, format); + } + private static void AppendTypeCore(ref TypeNameBuilder tnb, Contracts.TypeHandle typeHandle, ReadOnlySpan instantiation, TypeNameFormat format) + { + bool toString = format.HasFlag(TypeNameFormat.FormatNamespace) && !format.HasFlag(TypeNameFormat.FormatFullInst) && !format.HasFlag(TypeNameFormat.FormatAssembly); + + if (typeHandle.IsNull) + { + tnb.AddName("(null)"); + } + else + { + var typeSystemContract = tnb.Target.Contracts.RuntimeTypeSystem; + if (typeSystemContract.HasTypeParam(typeHandle)) + { + var elemType = typeSystemContract.GetSignatureCorElementType(typeHandle); + if (elemType != Contracts.CorElementType.ValueType) + { + typeSystemContract.IsArray(typeHandle, out uint rank); + AppendTypeCore(ref tnb, typeSystemContract.GetTypeParam(typeHandle), default(ReadOnlySpan), (TypeNameFormat)(format & ~TypeNameFormat.FormatAssembly)); + AppendParamTypeQualifier(ref tnb, elemType, rank); + } + else + { + tnb.TypeString.Append("VALUETYPE"); + AppendTypeCore(ref tnb, typeSystemContract.GetTypeParam(typeHandle), Array.Empty(), format & ~TypeNameFormat.FormatAssembly); + } + } + else if (typeSystemContract.IsGenericVariable(typeHandle, out TargetPointer modulePointer, out uint genericParamToken)) + { + Contracts.ModuleHandle module = tnb.Target.Contracts.Loader.GetModuleHandle(modulePointer); + EcmaMetadataReader reader = tnb.Target.Metadata.GetMetadata(module).EcmaMetadataReader; + EcmaMetadataCursor cursor = reader.GetCursor(genericParamToken); + if (format.HasFlag(TypeNameFormat.FormatGenericParam)) + { + uint owner = reader.GetColumnAsToken(cursor, MetadataColumnIndex.GenericParam_Owner); + if (EcmaMetadataReader.TokenToTable(owner) == MetadataTable.TypeDef) + { + tnb.TypeString.Append('!'); + } + else + { + tnb.TypeString.Append("!!"); + } + } + tnb.AddName(reader.GetColumnAsUtf8String(cursor, MetadataColumnIndex.GenericParam_Name)); + format &= ~TypeNameFormat.FormatAssembly; + } + else if (typeSystemContract.IsFunctionPointer(typeHandle, out ReadOnlySpan retAndArgTypes, out byte callConv)) + { + if (format.HasFlag(TypeNameFormat.FormatNamespace)) + { + StringBuilder stringBuilder = new(); + AppendType(tnb.Target, stringBuilder, retAndArgTypes[0], format); + stringBuilder.Append('('); + for (int i = 1; i < retAndArgTypes.Length; i++) + { + if (i != 1) + { + stringBuilder.Append(", "); + } + AppendType(tnb.Target, stringBuilder, retAndArgTypes[i], format); + } + + if ((callConv & 0x7) == 0x5) // Is this the VARARG calling convention? + { + if (retAndArgTypes.Length > 2) + { + stringBuilder.Append(", "); + } + stringBuilder.Append("..."); + } + stringBuilder.Append(')'); + tnb.AddNameNoEscaping(stringBuilder); + } + else + { + tnb.AddNameNoEscaping(new StringBuilder()); + } + } + else + { + // ...otherwise it's just a plain type def or an instantiated type + uint typeDefToken = typeSystemContract.GetTypeDefToken(typeHandle); + Contracts.ModuleHandle moduleHandle = tnb.Target.Contracts.Loader.GetModuleHandle(typeSystemContract.GetModule(typeHandle)); + if (EcmaMetadataReader.RidFromToken(typeDefToken) == 0) + { + tnb.AddName("(dynamicClass)"); + } + else + { + EcmaMetadataReader reader = tnb.Target.Metadata.GetMetadata(moduleHandle).EcmaMetadataReader; + AppendNestedTypeDef(ref tnb, reader, typeDefToken, format); + } + + // Append instantiation + + if (format.HasFlag(TypeNameFormat.FormatNamespace) || format.HasFlag(TypeNameFormat.FormatAssembly)) + { + ReadOnlySpan instantiationSpan = typeSystemContract.GetInstantiation(typeHandle); + + if ((instantiationSpan.Length > 0) && (!typeSystemContract.IsGenericTypeDefinition(typeHandle) || toString)) + { + if (instantiation.Length == 0) + { + instantiation = instantiationSpan; + } + AppendInst(ref tnb, instantiation, format); + } + } + } + if (format.HasFlag(TypeNameFormat.FormatAssembly)) + { + TargetPointer modulePtr = typeSystemContract.GetModule(typeHandle); + + Contracts.ModuleHandle module = tnb.Target.Contracts.Loader.GetModuleHandle(modulePtr); + // NOTE: The DAC variant of assembly name generation is different than the runtime version. The DAC variant is simpler, and only uses SimpleName + EcmaMetadataReader mr = tnb.Target.Metadata.GetMetadata(module).EcmaMetadataReader; + EcmaMetadataCursor cursor = mr.GetCursor(0x20000001); + string assemblySimpleName = mr.GetColumnAsUtf8String(cursor, MetadataColumnIndex.Assembly_Name); + + tnb.AddAssemblySpec(assemblySimpleName); + } + } + } + + private static void AppendInst(ref TypeNameBuilder tnb, ReadOnlySpan inst, TypeNameFormat format) + { + tnb.OpenGenericArguments(); + foreach (TypeHandle arg in inst) + { + tnb.OpenGenericArgument(); + if (format.HasFlag(TypeNameFormat.FormatFullInst) && !tnb.Target.Contracts.RuntimeTypeSystem.IsGenericVariable(arg, out _, out _)) + { + AppendTypeCore(ref tnb, arg, default, format | TypeNameFormat.FormatNamespace | TypeNameFormat.FormatAssembly); + } + else + { + AppendTypeCore(ref tnb, arg, default, format & (TypeNameFormat.FormatNamespace | TypeNameFormat.FormatAngleBrackets)); + } + tnb.CloseGenericArgument(); + } + tnb.CloseGenericArguments(); + } + + private void OpenGenericArguments() + { + if (!CheckParseState(ParseState.Name)) + { + Fail(); + return; + } + + State = ParseState.Start; + InstNesting++; + FirstInstArg = true; + + TypeString.Append(UseAngleBracketsForGenerics ? '<' : '['); + } + + private void OpenGenericArgument() + { + if (!CheckParseState(ParseState.Start)) + { + Fail(); + return; + } + if (InstNesting == 0) + { + Fail(); + return; + } + + State = ParseState.Start; + NestedName = false; + if (!FirstInstArg) + { + TypeString.Append(','); + } + else + { + FirstInstArg = false; + } + + TypeString.Append(UseAngleBracketsForGenerics ? '<' : '['); + PushOpenGenericArgument(); + } + + private void CloseGenericArgument() + { + if (!CheckParseState(ParseState.Name | ParseState.GenArgs | ParseState.PtrArr | ParseState.ByRef | ParseState.AssemSpec)) + { + Fail(); + return; + } + if (InstNesting == 0) + { + Fail(); + return; + } + + State = ParseState.Start; + + if (HasAssemblySpec) + { + TypeString.Append(UseAngleBracketsForGenerics ? '>' : ']'); + } + + PopOpenGenericArgument(); + } + + private void CloseGenericArguments() + { + if (InstNesting == 0) + { + Fail(); + return; + } + if (!CheckParseState(ParseState.Start)) + { + Fail(); + } + + State = ParseState.GenArgs; + InstNesting--; + + if (FirstInstArg) + { + if (TypeString.Length > 0) + TypeString.Remove(TypeString.Length - 1, 1); + } + else + { + TypeString.Append(UseAngleBracketsForGenerics ? '>' : ']'); + } + } + private void PushOpenGenericArgument() + { + GenericStartsStack ??= new(); + GenericStartsStack.Push(TypeString.Length); + } + + private void PopOpenGenericArgument() + { + int strIndex = GenericStartsStack!.Pop(); + + if (!HasAssemblySpec) + { + TypeString.Remove(strIndex - 1, 1); + } + HasAssemblySpec = false; + } + + private void AddAssemblySpec(string? assemblySpec) + { + if (!CheckParseState(ParseState.Name | ParseState.GenArgs | ParseState.PtrArr | ParseState.ByRef)) + { + Fail(); + return; + } + + State = ParseState.AssemSpec; + if (!string.IsNullOrEmpty(assemblySpec)) + { + TypeString.Append(", "); + + if (InstNesting > 0) + EscapeEmbeddedAssemblyName(assemblySpec); + else + EscapeAssemblyName(assemblySpec); + + HasAssemblySpec = true; + } + } + + private static void AppendNestedTypeDef(ref TypeNameBuilder tnb, EcmaMetadataReader reader, uint typeDefToken, TypeNameFormat format) + { + EcmaMetadataCursor cursor = reader.GetCursor(typeDefToken); + System.Reflection.TypeAttributes typeDefAttributes = (System.Reflection.TypeAttributes)reader.GetColumnAsConstant(cursor, MetadataColumnIndex.TypeDef_Flags); + if ((int)(typeDefAttributes & System.Reflection.TypeAttributes.VisibilityMask) >= (int)System.Reflection.TypeAttributes.NestedPublic) + { + uint currentTypeDefToken = typeDefToken; + List nestedTokens = new(); + EcmaMetadataCursor nestedTypesCursor = reader.GetCursor(EcmaMetadataReader.CreateToken(MetadataTable.NestedClass, 1)); + while (reader.TryFindRowFromCursor(nestedTypesCursor, MetadataColumnIndex.NestedClass_NestedClass, currentTypeDefToken, out EcmaMetadataCursor foundNestedClassRecord)) + { + currentTypeDefToken = reader.GetColumnAsToken(foundNestedClassRecord, MetadataColumnIndex.NestedClass_EnclosingClass); + nestedTokens.Add(currentTypeDefToken); + } + + for (int i = nestedTokens.Count - 1; i >= 0; i--) + { + AppendTypeDef(ref tnb, reader, nestedTokens[i], format); + } + } + AppendTypeDef(ref tnb, reader, typeDefToken, format); + } + + private static void AppendTypeDef(ref TypeNameBuilder tnb, EcmaMetadataReader reader, uint typeDefToken, TypeNameFormat format) + { + EcmaMetadataCursor cursor = reader.GetCursor(typeDefToken); + string? typeNamespace = null; + if (format.HasFlag(TypeNameFormat.FormatNamespace)) + { + typeNamespace = reader.GetColumnAsUtf8String(cursor, MetadataColumnIndex.TypeDef_TypeNamespace); + } + tnb.AddName(reader.GetColumnAsUtf8String(cursor, MetadataColumnIndex.TypeDef_TypeName), typeNamespace); + } + + private static void AppendParamTypeQualifier(ref TypeNameBuilder tnb, CorElementType kind, uint rank) + { + switch (kind) + { + case CorElementType.Byref: + tnb.AddByRef(); + break; + case CorElementType.Ptr: + tnb.AddPointer(); + break; + case CorElementType.SzArray: + tnb.AddSzArray(); + break; + case CorElementType.Array: + tnb.AddArray(rank); + break; + } + } + + private void AddByRef() + { + if (!CheckParseState(ParseState.Name | ParseState.GenArgs | ParseState.PtrArr)) + { + Fail(); + return; + } + + State = ParseState.ByRef; + TypeString.Append('&'); + } + + private void AddPointer() + { + if (!CheckParseState(ParseState.Name | ParseState.GenArgs | ParseState.PtrArr)) + { + Fail(); + return; + } + + State = ParseState.PtrArr; + TypeString.Append('*'); + } + + private void AddSzArray() + { + if (!CheckParseState(ParseState.Name | ParseState.GenArgs | ParseState.PtrArr)) + { + Fail(); + return; + } + + State = ParseState.PtrArr; + TypeString.Append("[]"); + } + + private void AddArray(uint rank) + { + if (!CheckParseState(ParseState.Name | ParseState.GenArgs | ParseState.PtrArr)) + { + Fail(); + return; + } + + State = ParseState.PtrArr; + if (rank == 0) + return; + + if (rank == 1) + { + TypeString.Append("[*]"); + } + else if (rank > 64) + { + TypeString.Append($"[{rank}]"); + } + else + { + TypeString.Append('['); + for (uint i = 1; i < rank; i++) + { + TypeString.Append(','); + } + TypeString.Append(']'); + } + TypeString.Append("[]"); + } + + private static ReadOnlySpan TypeNameReservedChars() + { + return ",[]&*+\\"; + } + + private static bool IsTypeNameReservedChar(char c) + { + return TypeNameReservedChars().IndexOf(c) != -1; + } + + private void EscapeName(string name) + { + foreach (char c in name) + { + if (IsTypeNameReservedChar(c)) + { + TypeString.Append('\\'); + } + TypeString.Append(c); + } + } + + private void AddName(string name, string? _namespace = null) + { + if (name == null) + { + Fail(); + return; + } + + if (!CheckParseState(ParseState.Start | ParseState.Name)) + { + Fail(); + return; + } + + State = ParseState.Name; + if (NestedName) + TypeString.Append('+'); + + NestedName = true; + if (!string.IsNullOrEmpty(_namespace)) + { + EscapeName(_namespace); + TypeString.Append('.'); + } + + EscapeName(name); + } + + private void EscapeAssemblyName(string assemblyName) + { + TypeString.Append(assemblyName); + } + + private void EscapeEmbeddedAssemblyName(string assemblyName) + { + foreach (char c in assemblyName) + { + if (c == ']') + TypeString.Append('\\'); + TypeString.Append(c); + } + } + + private void AddNameNoEscaping(StringBuilder? name) + { + if (name == null) + { + Fail(); + return; + } + + if (!CheckParseState(ParseState.Start | ParseState.Name)) + { + Fail(); + return; + } + + State = ParseState.Name; + + if (NestedName) + TypeString.Append('+'); + + NestedName = true; + TypeString.Append(name); + } + + private void Fail() + { + State = ParseState.Error; + } + + private bool CheckParseState(ParseState validStates) + { + // Error is always invalid + if (State == ParseState.Error) + return false; + + return (State & validStates) != 0; + } +} diff --git a/src/native/managed/cdacreader/src/Target.cs b/src/native/managed/cdacreader/src/Target.cs index 06b201d3957d55..244b917a508d8c 100644 --- a/src/native/managed/cdacreader/src/Target.cs +++ b/src/native/managed/cdacreader/src/Target.cs @@ -36,6 +36,18 @@ public readonly struct TargetNUInt public TargetNUInt(ulong value) => Value = value; } +public readonly struct TargetSpan +{ + public TargetSpan(TargetPointer address, ulong size) + { + Address = address; + Size = size; + } + + public TargetPointer Address { get; } + public ulong Size { get; } +} + /// /// Representation of the target under inspection /// @@ -80,6 +92,7 @@ private readonly struct Configuration internal Contracts.Registry Contracts { get; } internal DataCache ProcessedData { get; } + internal Helpers.Metadata Metadata { get; } public static bool TryCreate(ulong contractDescriptor, delegate* unmanaged readFromTarget, void* readContext, out Target? target) { @@ -98,6 +111,7 @@ private Target(Configuration config, ContractDescriptorParser.ContractDescriptor { Contracts = new Contracts.Registry(this); ProcessedData = new DataCache(this); + Metadata = new Helpers.Metadata(this); _config = config; _reader = reader; @@ -246,6 +260,8 @@ private static DataType GetDataType(string type) return DataType.Unknown; } + public int PointerSize => _config.PointerSize; + public T Read(ulong address) where T : unmanaged, IBinaryInteger, IMinMaxValue { if (!TryRead(address, _config.IsLittleEndian, _reader, out T value)) @@ -266,6 +282,17 @@ private static bool TryRead(ulong address, bool isLittleEndian, Reader reader : T.TryReadBigEndian(buffer, !IsSigned(), out value); } + public void ReadBuffer(ulong address, Span buffer) + { + if (!TryReadBuffer(address, buffer)) + throw new InvalidOperationException($"Failed to read {buffer.Length} bytes at 0x{address:x8}."); + } + + private bool TryReadBuffer(ulong address, Span buffer) + { + return _reader.ReadFromTarget(address, buffer) >= 0; + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] private static bool IsSigned() where T : struct, INumberBase, IMinMaxValue { @@ -280,6 +307,19 @@ public TargetPointer ReadPointer(ulong address) return pointer; } + public void ReadPointers(ulong address, Span buffer) + { + // TODO(cdac) - This could do a single read, and then swizzle in place if it is useful for performance + for (int i = 0; i < buffer.Length; i++) + { + buffer[i] = ReadPointer(address); + checked + { + address += (ulong)_config.PointerSize; + } + } + } + public TargetNUInt ReadNUInt(ulong address) { if (!TryReadNUInt(address, _config, _reader, out ulong value)) diff --git a/src/native/managed/cdacreader/tests/DacStreamsTests.cs b/src/native/managed/cdacreader/tests/DacStreamsTests.cs new file mode 100644 index 00000000000000..9dd20059a55165 --- /dev/null +++ b/src/native/managed/cdacreader/tests/DacStreamsTests.cs @@ -0,0 +1,244 @@ +// 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.Text; +using Xunit; + +namespace Microsoft.Diagnostics.DataContractReader.UnitTests; + +public class DacStreamsTests +{ + private delegate MockMemorySpace.Builder ConfigureContextBuilder(MockMemorySpace.Builder builder); + + const ulong TestMiniMetaDataBuffGlobalAddress = 0x00000000_200000a0; + const ulong TestMiniMetaDataBuffGlobalMaxSize = 0x00000000_20000000; + const ulong TestMiniMetaDataBuffAddress = 0x00000000_100000a0; + + const uint MiniMetadataSignature = 0x6d727473; + const uint EENameStreamSignature = 0x614e4545; + + const uint MiniMetaDataStreamsHeaderSize = 12; + + private static readonly (DataType Type, Target.TypeInfo Info)[] DacStreamsTypes = + [ + ]; + + private static readonly (string Name, ulong Value, string? Type)[] DacStreamsGlobals = + [ + (nameof(Constants.Globals.MiniMetaDataBuffAddress), TestMiniMetaDataBuffGlobalAddress, null), + (nameof(Constants.Globals.MiniMetaDataBuffMaxSize), TestMiniMetaDataBuffGlobalMaxSize, null), + ]; + + private static unsafe void DacStreamsContractHelper(MockTarget.Architecture arch, ConfigureContextBuilder configure, Action testCase) + { + TargetTestHelpers targetTestHelpers = new(arch); + string metadataTypesJson = TargetTestHelpers.MakeTypesJson(DacStreamsTypes); + string metadataGlobalsJson = TargetTestHelpers.MakeGlobalsJson(DacStreamsGlobals); + byte[] json = Encoding.UTF8.GetBytes($$""" + { + "version": 0, + "baseline": "empty", + "contracts": { + "{{nameof(Contracts.DacStreams)}}": 1 + }, + "types": { {{metadataTypesJson}} }, + "globals": { {{metadataGlobalsJson}} } + } + """); + Span descriptor = stackalloc byte[targetTestHelpers.ContractDescriptorSize]; + targetTestHelpers.ContractDescriptorFill(descriptor, json.Length, DacStreamsGlobals.Length); + + int pointerSize = targetTestHelpers.PointerSize; + Span pointerData = stackalloc byte[DacStreamsGlobals.Length * pointerSize]; + for (int i = 0; i < DacStreamsGlobals.Length; i++) + { + var (_, value, _) = DacStreamsGlobals[i]; + targetTestHelpers.WritePointer(pointerData.Slice(i * pointerSize), value); + } + + fixed (byte* jsonPtr = json) + { + MockMemorySpace.Builder builder = new(); + + builder = builder.SetDescriptor(descriptor) + .SetJson(json) + .SetPointerData(pointerData); + + if (configure != null) + { + builder = configure(builder); + } + + using MockMemorySpace.ReadContext context = builder.Create(); + + bool success = MockMemorySpace.TryCreateTarget(&context, out Target? target); + Assert.True(success); + + testCase(target); + } + GC.KeepAlive(json); + } + + MockMemorySpace.Builder AddMiniMetaDataBuffMaxSize(TargetTestHelpers targetTestHelpers, MockMemorySpace.Builder builder, uint maxSize) + { + MockMemorySpace.HeapFragment globalAddr = new() { Name = "Address of MiniMetaDataBuffMaxSize", Address = TestMiniMetaDataBuffGlobalMaxSize, Data = new byte[4] }; + targetTestHelpers.Write(globalAddr.Data, maxSize); + return builder.AddHeapFragment(globalAddr); + } + + MockMemorySpace.Builder AddMiniMetaDataBuffAddress(TargetTestHelpers targetTestHelpers, MockMemorySpace.Builder builder, ulong pointer) + { + MockMemorySpace.HeapFragment globalAddr = new() { Name = "Address of MiniMetaDataBuffAddress", Address = TestMiniMetaDataBuffGlobalAddress, Data = new byte[targetTestHelpers.PointerSize] }; + targetTestHelpers.WritePointer(globalAddr.Data, pointer); + return builder.AddHeapFragment(globalAddr); + } + + private class CurrentPointer + { + public ulong Pointer; + } + + MockMemorySpace.Builder AddMiniMetaDataStreamsHeader(TargetTestHelpers targetTestHelpers, MockMemorySpace.Builder builder, uint totalSizeOtherThanStreamsHeader, uint countStreams, CurrentPointer currentPointer) + { + MockMemorySpace.HeapFragment globalAddr = new() { Name = "MiniMetaDataStreamsHeader", Address = currentPointer.Pointer, Data = new byte[MiniMetaDataStreamsHeaderSize] }; + targetTestHelpers.Write(globalAddr.Data.AsSpan().Slice(0, 4), MiniMetadataSignature); + targetTestHelpers.Write(globalAddr.Data.AsSpan().Slice(4, 4), totalSizeOtherThanStreamsHeader + MiniMetaDataStreamsHeaderSize); + targetTestHelpers.Write(globalAddr.Data.AsSpan().Slice(8, 4), countStreams); + currentPointer.Pointer += 12; + return builder.AddHeapFragment(globalAddr); + } + + MockMemorySpace.Builder AddEENameStreamHeader(TargetTestHelpers targetTestHelpers, MockMemorySpace.Builder builder, uint countEntries, CurrentPointer currentPointer) + { + MockMemorySpace.HeapFragment globalAddr = new() { Name = "EEStreamHeader", Address = currentPointer.Pointer, Data = new byte[8] }; + targetTestHelpers.Write(globalAddr.Data.AsSpan().Slice(0, 4), EENameStreamSignature); + targetTestHelpers.Write(globalAddr.Data.AsSpan().Slice(4, 4), countEntries); + currentPointer.Pointer += 8; + return builder.AddHeapFragment(globalAddr); + } + + + MockMemorySpace.Builder AddEENameStream(TargetTestHelpers targetTestHelpers, MockMemorySpace.Builder builder, List<(ulong Pointer, string Name)> names, CurrentPointer currentPointer) + { + builder = AddEENameStreamHeader(targetTestHelpers, builder, checked((uint)names.Count), currentPointer); + + for (int i = 0; i < names.Count; i++) + { + int byteCountWithoutNullTerminator = Encoding.UTF8.GetByteCount(names[i].Name); + int entrySize = byteCountWithoutNullTerminator + 1 + targetTestHelpers.PointerSize; + MockMemorySpace.HeapFragment entryAddr = new() + { + Name = $"EEStreamEntry{i}", Address = currentPointer.Pointer, Data = new byte[byteCountWithoutNullTerminator + 1 + targetTestHelpers.PointerSize] + }; + targetTestHelpers.WritePointer(entryAddr.Data.AsSpan().Slice(0, targetTestHelpers.PointerSize), names[i].Pointer); + Encoding.UTF8.TryGetBytes(names[i].Name.AsSpan(), entryAddr.Data.AsSpan().Slice(targetTestHelpers.PointerSize, byteCountWithoutNullTerminator), out _); + targetTestHelpers.Write(entryAddr.Data.AsSpan().Slice(byteCountWithoutNullTerminator + targetTestHelpers.PointerSize, 1), (byte)0); + currentPointer.Pointer += (ulong)entrySize; + builder = builder.AddHeapFragment(entryAddr); + } + return builder; + } + + [Theory] + [ClassData(typeof(MockTarget.StdArch))] + public void DacStreamValues(MockTarget.Architecture arch) + { + TargetTestHelpers targetTestHelpers = new(arch); + + DacStreamsContractHelper(arch, + (builder) => + { + // Test normal non-error behavior + + List<(ulong Pointer, string Name)> values = [((ulong)0x1234, "Type1"), ((ulong)0x1238, "Type2")]; + builder = AddMiniMetaDataBuffAddress(targetTestHelpers, builder, TestMiniMetaDataBuffAddress); + builder = AddMiniMetaDataBuffMaxSize(targetTestHelpers, builder, 0x10000); + CurrentPointer currentPointer = new(); + ulong eeNameStreamStart = TestMiniMetaDataBuffAddress + MiniMetaDataStreamsHeaderSize; + currentPointer.Pointer = TestMiniMetaDataBuffAddress + MiniMetaDataStreamsHeaderSize; + builder = AddEENameStream(targetTestHelpers, builder, values, currentPointer); + uint eeNameStreamSize = checked((uint)(currentPointer.Pointer - eeNameStreamStart)); + + currentPointer.Pointer = TestMiniMetaDataBuffAddress; + builder = AddMiniMetaDataStreamsHeader(targetTestHelpers, builder, eeNameStreamSize, 1, currentPointer); + return builder; + }, + (target) => + { + Contracts.IDacStreams dacStreamsContract = target.Contracts.DacStreams; + Assert.NotNull(dacStreamsContract); + Assert.Null(dacStreamsContract.StringFromEEAddress(0)); + Assert.Equal("Type1", dacStreamsContract.StringFromEEAddress(0x1234)); + Assert.Equal("Type2", dacStreamsContract.StringFromEEAddress(0x1238)); + }); + } + + [Theory] + [ClassData(typeof(MockTarget.StdArch))] + public void DacStreamValues_TruncatedTotalSize(MockTarget.Architecture arch) + { + // Test behavior if TotalSize isn't big enough to hold the last entry + + TargetTestHelpers targetTestHelpers = new(arch); + + DacStreamsContractHelper(arch, + (builder) => + { + List<(ulong Pointer, string Name)> values = [((ulong)0x1234, "Type1"), ((ulong)0x1238, "Type2")]; + builder = AddMiniMetaDataBuffAddress(targetTestHelpers, builder, TestMiniMetaDataBuffAddress); + builder = AddMiniMetaDataBuffMaxSize(targetTestHelpers, builder, 0x10000); + CurrentPointer currentPointer = new(); + ulong eeNameStreamStart = TestMiniMetaDataBuffAddress + MiniMetaDataStreamsHeaderSize; + currentPointer.Pointer = TestMiniMetaDataBuffAddress + MiniMetaDataStreamsHeaderSize; + builder = AddEENameStream(targetTestHelpers, builder, values, currentPointer); + uint eeNameStreamSize = checked((uint)(currentPointer.Pointer - eeNameStreamStart)); + + currentPointer.Pointer = TestMiniMetaDataBuffAddress; + builder = AddMiniMetaDataStreamsHeader(targetTestHelpers, builder, eeNameStreamSize - 2, 1, currentPointer); + return builder; + }, + (target) => + { + Contracts.IDacStreams dacStreamsContract = target.Contracts.DacStreams; + Assert.NotNull(dacStreamsContract); + Assert.Null(dacStreamsContract.StringFromEEAddress(0)); + Assert.Equal("Type1", dacStreamsContract.StringFromEEAddress(0x1234)); + Assert.Null(dacStreamsContract.StringFromEEAddress(0x1238)); + }); + } + + [Theory] + [ClassData(typeof(MockTarget.StdArch))] + public void DacStreamValues_TruncatedBuffMaxSize(MockTarget.Architecture arch) + { + // Test behavior if MaxSize global is smaller than TotalSize + TargetTestHelpers targetTestHelpers = new(arch); + + DacStreamsContractHelper(arch, + (builder) => + { + List<(ulong Pointer, string Name)> values = [((ulong)0x1234, "Type1"), ((ulong)0x1238, "Type2")]; + builder = AddMiniMetaDataBuffAddress(targetTestHelpers, builder, TestMiniMetaDataBuffAddress); + builder = AddMiniMetaDataBuffMaxSize(targetTestHelpers, builder, (uint)(0x20 + targetTestHelpers.PointerSize * 2 - 1)); + CurrentPointer currentPointer = new(); + ulong eeNameStreamStart = TestMiniMetaDataBuffAddress + MiniMetaDataStreamsHeaderSize; + currentPointer.Pointer = TestMiniMetaDataBuffAddress + MiniMetaDataStreamsHeaderSize; + builder = AddEENameStream(targetTestHelpers, builder, values, currentPointer); + uint eeNameStreamSize = checked((uint)(currentPointer.Pointer - eeNameStreamStart)); + + currentPointer.Pointer = TestMiniMetaDataBuffAddress; + builder = AddMiniMetaDataStreamsHeader(targetTestHelpers, builder, eeNameStreamSize, 1, currentPointer); + return builder; + }, + (target) => + { + Contracts.IDacStreams dacStreamsContract = target.Contracts.DacStreams; + Assert.NotNull(dacStreamsContract); + Assert.Null(dacStreamsContract.StringFromEEAddress(0)); + Assert.Null(dacStreamsContract.StringFromEEAddress(0x1234)); + Assert.Null(dacStreamsContract.StringFromEEAddress(0x1238)); + }); + } +} diff --git a/src/native/managed/cdacreader/tests/MethodTableTests.cs b/src/native/managed/cdacreader/tests/MethodTableTests.cs index dd451fa075f6ae..1e2c2cf53d9fa9 100644 --- a/src/native/managed/cdacreader/tests/MethodTableTests.cs +++ b/src/native/managed/cdacreader/tests/MethodTableTests.cs @@ -24,6 +24,7 @@ public unsafe class MethodTableTests { nameof(Data.MethodTable.ParentMethodTable), new () { Offset = 40, Type = DataType.pointer}}, { nameof(Data.MethodTable.NumInterfaces), new () { Offset = 48, Type = DataType.uint16}}, { nameof(Data.MethodTable.NumVirtuals), new () { Offset = 50, Type = DataType.uint16}}, + { nameof(Data.MethodTable.PerInstInfo), new () { Offset = 56, Type = DataType.pointer}}, } }; @@ -33,6 +34,7 @@ public unsafe class MethodTableTests { nameof (Data.EEClass.MethodTable), new () { Offset = 8, Type = DataType.pointer}}, { nameof (Data.EEClass.CorTypeAttr), new () { Offset = 16, Type = DataType.uint32}}, { nameof (Data.EEClass.NumMethods), new () { Offset = 20, Type = DataType.uint16}}, + { nameof (Data.EEClass.InternalCorElementType), new () { Offset = 22, Type = DataType.uint8}}, } }; @@ -150,7 +152,7 @@ public void HasRuntimeTypeSystemContract(MockTarget.Architecture arch) { Contracts.IRuntimeTypeSystem metadataContract = target.Contracts.RuntimeTypeSystem; Assert.NotNull(metadataContract); - Contracts.MethodTableHandle handle = metadataContract.GetMethodTableHandle(TestFreeObjectMethodTableAddress); + Contracts.TypeHandle handle = metadataContract.GetTypeHandle(TestFreeObjectMethodTableAddress); Assert.NotEqual(TargetPointer.Null, handle.Address); Assert.True(metadataContract.IsFreeObjectMethodTable(handle)); }); @@ -187,9 +189,9 @@ public void ValidateSystemObjectMethodTable(MockTarget.Architecture arch) { Contracts.IRuntimeTypeSystem metadataContract = target.Contracts.RuntimeTypeSystem; Assert.NotNull(metadataContract); - Contracts.MethodTableHandle systemObjectMethodTableHandle = metadataContract.GetMethodTableHandle(systemObjectMethodTablePtr); - Assert.Equal(systemObjectMethodTablePtr.Value, systemObjectMethodTableHandle.Address.Value); - Assert.False(metadataContract.IsFreeObjectMethodTable(systemObjectMethodTableHandle)); + Contracts.TypeHandle systemObjectTypeHandle = metadataContract.GetTypeHandle(systemObjectMethodTablePtr); + Assert.Equal(systemObjectMethodTablePtr.Value, systemObjectTypeHandle.Address.Value); + Assert.False(metadataContract.IsFreeObjectMethodTable(systemObjectTypeHandle)); }); } @@ -226,10 +228,10 @@ public void ValidateSystemStringMethodTable(MockTarget.Architecture arch) { Contracts.IRuntimeTypeSystem metadataContract = target.Contracts.RuntimeTypeSystem; Assert.NotNull(metadataContract); - Contracts.MethodTableHandle systemStringMethodTableHandle = metadataContract.GetMethodTableHandle(systemStringMethodTablePtr); - Assert.Equal(systemStringMethodTablePtr.Value, systemStringMethodTableHandle.Address.Value); - Assert.False(metadataContract.IsFreeObjectMethodTable(systemStringMethodTableHandle)); - Assert.True(metadataContract.IsString(systemStringMethodTableHandle)); + Contracts.TypeHandle systemStringTypeHandle = metadataContract.GetTypeHandle(systemStringMethodTablePtr); + Assert.Equal(systemStringMethodTablePtr.Value, systemStringTypeHandle.Address.Value); + Assert.False(metadataContract.IsFreeObjectMethodTable(systemStringTypeHandle)); + Assert.True(metadataContract.IsString(systemStringTypeHandle)); }); } @@ -258,7 +260,7 @@ public void MethodTableEEClassInvalidThrows(MockTarget.Architecture arch) { Contracts.IRuntimeTypeSystem metadataContract = target.Contracts.RuntimeTypeSystem; Assert.NotNull(metadataContract); - Assert.Throws(() => metadataContract.GetMethodTableHandle(badMethodTablePtr)); + Assert.Throws(() => metadataContract.GetTypeHandle(badMethodTablePtr)); }); } @@ -308,11 +310,11 @@ public void ValidateGenericInstMethodTable(MockTarget.Architecture arch) { Contracts.IRuntimeTypeSystem metadataContract = target.Contracts.RuntimeTypeSystem; Assert.NotNull(metadataContract); - Contracts.MethodTableHandle genericInstanceMethodTableHandle = metadataContract.GetMethodTableHandle(genericInstanceMethodTablePtr); - Assert.Equal(genericInstanceMethodTablePtr.Value, genericInstanceMethodTableHandle.Address.Value); - Assert.False(metadataContract.IsFreeObjectMethodTable(genericInstanceMethodTableHandle)); - Assert.False(metadataContract.IsString(genericInstanceMethodTableHandle)); - Assert.Equal(numMethods, metadataContract.GetNumMethods(genericInstanceMethodTableHandle)); + Contracts.TypeHandle genericInstanceTypeHandle = metadataContract.GetTypeHandle(genericInstanceMethodTablePtr); + Assert.Equal(genericInstanceMethodTablePtr.Value, genericInstanceTypeHandle.Address.Value); + Assert.False(metadataContract.IsFreeObjectMethodTable(genericInstanceTypeHandle)); + Assert.False(metadataContract.IsString(genericInstanceTypeHandle)); + Assert.Equal(numMethods, metadataContract.GetNumMethods(genericInstanceTypeHandle)); }); } @@ -365,11 +367,11 @@ public void ValidateArrayInstMethodTable(MockTarget.Architecture arch) { Contracts.IRuntimeTypeSystem metadataContract = target.Contracts.RuntimeTypeSystem; Assert.NotNull(metadataContract); - Contracts.MethodTableHandle arrayInstanceMethodTableHandle = metadataContract.GetMethodTableHandle(arrayInstanceMethodTablePtr); - Assert.Equal(arrayInstanceMethodTablePtr.Value, arrayInstanceMethodTableHandle.Address.Value); - Assert.False(metadataContract.IsFreeObjectMethodTable(arrayInstanceMethodTableHandle)); - Assert.False(metadataContract.IsString(arrayInstanceMethodTableHandle)); - Assert.Equal(arrayInstanceComponentSize, metadataContract.GetComponentSize(arrayInstanceMethodTableHandle)); + Contracts.TypeHandle arrayInstanceTypeHandle = metadataContract.GetTypeHandle(arrayInstanceMethodTablePtr); + Assert.Equal(arrayInstanceMethodTablePtr.Value, arrayInstanceTypeHandle.Address.Value); + Assert.False(metadataContract.IsFreeObjectMethodTable(arrayInstanceTypeHandle)); + Assert.False(metadataContract.IsString(arrayInstanceTypeHandle)); + Assert.Equal(arrayInstanceComponentSize, metadataContract.GetComponentSize(arrayInstanceTypeHandle)); }); } diff --git a/src/native/managed/cdacreader/tests/MockMemorySpace.cs b/src/native/managed/cdacreader/tests/MockMemorySpace.cs index c74771a7f5b5c3..f2e2419a8ba4b7 100644 --- a/src/native/managed/cdacreader/tests/MockMemorySpace.cs +++ b/src/native/managed/cdacreader/tests/MockMemorySpace.cs @@ -234,23 +234,41 @@ public HeapFragmentReader(IReadOnlyList fragments) public int ReadFragment(ulong address, Span buffer) { - foreach (var fragment in _fragments) + bool partialReadOcurred = false; + HeapFragment lastHeapFragment = default; + int availableLength = 0; + while (true) { - if (address >= fragment.Address && address < fragment.Address + (ulong)fragment.Data.Length) + bool tryAgain = false; + foreach (var fragment in _fragments) { - int offset = (int)(address - fragment.Address); - int availableLength = fragment.Data.Length - offset; - if (availableLength >= buffer.Length) + if (address >= fragment.Address && address < fragment.Address + (ulong)fragment.Data.Length) { - fragment.Data.AsSpan(offset, buffer.Length).CopyTo(buffer); - return 0; - } - else - { - throw new InvalidOperationException($"Not enough data in fragment at {fragment.Address:X} ('{fragment.Name}') to read {buffer.Length} bytes at {address:X} (only {availableLength} bytes available)"); + int offset = (int)(address - fragment.Address); + availableLength = fragment.Data.Length - offset; + if (availableLength >= buffer.Length) + { + fragment.Data.AsSpan(offset, buffer.Length).CopyTo(buffer); + return 0; + } + else + { + lastHeapFragment = fragment; + partialReadOcurred = true; + tryAgain = true; + fragment.Data.AsSpan(offset, availableLength).CopyTo(buffer); + buffer = buffer.Slice(availableLength); + address = fragment.Address + (ulong)fragment.Data.Length; + break; + } } } + if (!tryAgain) + break; } + + if (partialReadOcurred) + throw new InvalidOperationException($"Not enough data in fragment at {lastHeapFragment.Address:X} ('{lastHeapFragment.Name}') to read {buffer.Length} bytes at {address:X} (only {availableLength} bytes available)"); return -1; } }