From 255da992603928e801f68bb8595ed47ba9f6b666 Mon Sep 17 00:00:00 2001 From: David Wrighton Date: Tue, 9 Jul 2024 08:52:03 -0700 Subject: [PATCH 01/15] Progress towards GetMethodTableName --- .../cdacreader/src/Contracts/Metadata.cs | 174 ++++++ .../cdacreader/src/Contracts/Metadata_1.cs | 446 ++++++++++++++ .../cdacreader/src/Contracts/Registry.cs | 1 + .../src/Contracts/RuntimeTypeSystem.cs | 91 +++ .../cdacreader/src/Legacy/SOSDacImpl.cs | 71 ++- .../cdacreader/src/Legacy/TypeNameBuilder.cs | 569 ++++++++++++++++++ src/native/managed/cdacreader/src/Target.cs | 24 + 7 files changed, 1369 insertions(+), 7 deletions(-) create mode 100644 src/native/managed/cdacreader/src/Contracts/Metadata.cs create mode 100644 src/native/managed/cdacreader/src/Contracts/Metadata_1.cs create mode 100644 src/native/managed/cdacreader/src/Legacy/TypeNameBuilder.cs diff --git a/src/native/managed/cdacreader/src/Contracts/Metadata.cs b/src/native/managed/cdacreader/src/Contracts/Metadata.cs new file mode 100644 index 0000000000000..93cdb6bd44208 --- /dev/null +++ b/src/native/managed/cdacreader/src/Contracts/Metadata.cs @@ -0,0 +1,174 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; + +namespace Microsoft.Diagnostics.DataContractReader.Contracts; + +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, + MaxValue = 0x2c +} + +internal struct MetadataCursor +{ + public ulong reserved1; + public object reserved2; +} + +internal enum MetadataColumnIndex +{ + Assembly_HashAlgId, + Assembly_MajorVersion, + Assembly_MinorVersion, + Assembly_BuildNumber, + Assembly_RevisionNumber, + Assembly_Flags, + Assembly_PublicKey, + Assembly_Name, + Assembly_Culture, + + GenericParam_Number, + GenericParam_Flags, + GenericParam_Owner, + GenericParam_Name, + NestedClass_NestedClass, + NestedClass_EnclosingClass, + TypeDef_Flags, + TypeDef_TypeName, + TypeDef_TypeNamespace, + TypeDef_Extends, + TypeDef_FieldList, + TypeDef_MethodList, + Count +} + +internal abstract class MetadataReader +{ + public static MetadataTable TokenToTable(uint token) + { + byte tableIndex = (byte)(token >> 24); + if (tableIndex > (uint)MetadataTable.GenericParamConstraint) + { + return MetadataTable.Unused; + } + else + { + return (MetadataTable)tableIndex; + } + } + + 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; + } + + public abstract MetadataCursor GetCursor(uint token); + public abstract bool TryGetCursor(uint token, out MetadataCursor cursor); + public abstract bool TryGetCursorToFirstEntryInTable(MetadataTable table, out MetadataCursor cursor); + public abstract uint GetToken(MetadataCursor c); + + // Query row's column values + // The returned number represents the number of valid cursor(s) for indexing. + public abstract uint GetColumnAsToken(MetadataCursor c, MetadataColumnIndex col_idx); + public abstract MetadataCursor GetColumnAsCursor(MetadataCursor c, MetadataColumnIndex col_idx); + // Resolve the column to a cursor and a range based on the run/list pattern in tables. + // The run continues to the smaller of: + // * the last row of the target table + // * the next run in the target table, found by inspecting the column value of the next row in the current table. + // See md_find_token_of_range_element() for mapping elements in the other direction. + public abstract void GetColumnAsRange(MetadataCursor c, MetadataColumnIndex col_idx, out MetadataCursor cursor, out int count); + public abstract uint GetColumnAsConstant(MetadataCursor c, MetadataColumnIndex col_idx); + public abstract ReadOnlySpan GetColumnAsUtf8(MetadataCursor c, MetadataColumnIndex col_idx); + public virtual string GetColumnAsUtf8String(MetadataCursor 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; + } + public abstract ReadOnlySpan GetColumnAsUserstring(MetadataCursor c, MetadataColumnIndex col_idx); + public abstract ReadOnlySpan GetColumnAsBlob(MetadataCursor c, MetadataColumnIndex col_idx); + public abstract Guid GetColumnAsGuid(MetadataCursor c, MetadataColumnIndex col_idx); + + // Find a row or range of rows where the supplied column has the expected value. + // These APIs assume the value to look for is the value in the table, typically record IDs (RID) + // for tokens. An exception is made for coded indices, which are cumbersome to compute. + // If the queried column contains a coded index value, the value will be validated and + // transformed to its coded form for comparison. + public abstract bool TryFindRowFromCursor(MetadataCursor begin, MetadataColumnIndex col_idx, uint value, out MetadataCursor foundCursor); +} + +internal interface IMetadata : IContract +{ + static string IContract.Name => nameof(Metadata); + static IContract IContract.Create(Target target, int version) + { + return version switch + { + _ => default(Metadata), + }; + } + + public virtual MetadataReader GetMetadataReader(ModuleHandle module) => throw new NotImplementedException(); +} + +internal readonly struct Metadata : IMetadata +{ + // Everything throws NotImplementedException +} diff --git a/src/native/managed/cdacreader/src/Contracts/Metadata_1.cs b/src/native/managed/cdacreader/src/Contracts/Metadata_1.cs new file mode 100644 index 0000000000000..b852acb8a5dc8 --- /dev/null +++ b/src/native/managed/cdacreader/src/Contracts/Metadata_1.cs @@ -0,0 +1,446 @@ +// 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.Diagnostics; +using System.Collections.Generic; +using System.Numerics; +using System.Reflection.Metadata; +using System.Reflection.Metadata.Ecma335; +using System.Runtime.CompilerServices; +using System.Reflection; + +namespace Microsoft.Diagnostics.DataContractReader.Contracts; + +internal readonly struct Metadata_1 : IMetadata +{ + private readonly Target _target; + + internal Metadata_1(Target target) + { + _target = target; + } + + public MetadataReader GetMetadataReader(ModuleHandle module) => throw new System.NotImplementedException(); + + private class SystemReflectionMetadataBasedReader : MetadataReader + { + private enum ColumnType + { + Unknown, + TwoByteConstant, + FourByteConstant, + Utf8String, + UserString, + Blob, + Token + } + private byte[] _bytes; + private System.Reflection.Metadata.MetadataReader mr; + private int[] tableRowCount; + private int[] columnSize; + private int[] columnOffset; + private int stringHeapSize; + private int userStringHeapSize; + private int blobHeapSize; + private static readonly MetadataTable[] columnTable = GetColumnTables(); + private static readonly ColumnType[] columnTypes = GetColumnTypes(); + private static readonly Func[] columnTokenDecode = GetColumnTokenDecode(); + private static readonly MetadataTable[][] codedIndexDecoderRing = GetCodedIndexDecoderRing(); + + private static readonly MetadataTable[] TypeOrMethodDef = { MetadataTable.TypeDef, MetadataTable.MethodDef }; + private static readonly MetadataTable[] TypeDefOrRef = { MetadataTable.TypeDef, MetadataTable.TypeRef, MetadataTable.TypeSpec }; + + private static ColumnType[] GetColumnTypes() + { + ColumnType[] columnTypes = new ColumnType[(int)MetadataColumnIndex.Count]; + + 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.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.NestedClass_NestedClass] = ColumnType.Token; + columnTypes[(int)MetadataColumnIndex.NestedClass_EnclosingClass] = ColumnType.Token; + + 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; + + return columnTypes; + } + + private static MetadataTable[] GetColumnTables() + { + MetadataTable[] metadataTables = new MetadataTable[(int)MetadataColumnIndex.Count]; + + 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.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.NestedClass_NestedClass] = MetadataTable.NestedClass; + metadataTables[(int)MetadataColumnIndex.NestedClass_EnclosingClass] = MetadataTable.NestedClass; + + 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; + + return metadataTables; + } + + private static MetadataTable[][] GetCodedIndexDecoderRing() + { + MetadataTable[][] decoderRing = new MetadataTable[(int)MetadataColumnIndex.Count][]; + + decoderRing[(int)MetadataColumnIndex.GenericParam_Owner] = TypeOrMethodDef; + + decoderRing[(int)MetadataColumnIndex.NestedClass_NestedClass] = new[] { MetadataTable.TypeDef }; + decoderRing[(int)MetadataColumnIndex.NestedClass_EnclosingClass] = new[] { MetadataTable.TypeDef }; + + decoderRing[(int)MetadataColumnIndex.TypeDef_Extends] = TypeDefOrRef; + decoderRing[(int)MetadataColumnIndex.TypeDef_FieldList] = new[] { MetadataTable.Field }; + decoderRing[(int)MetadataColumnIndex.TypeDef_MethodList] = new[] { MetadataTable.MethodDef }; + + return decoderRing; + } + private static Func[] GetColumnTokenDecode() + { + Func[] columnTokenDecode = new Func[(int)MetadataColumnIndex.Count]; + MetadataTable[][] decoderRing = 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 MetadataReader.CreateToken(metadataTable, input); }; + } + else + { + result = delegate (uint input) { return DecodeCodedIndex(input, decoderData); }; + } + + return result; + } + } + + + public unsafe SystemReflectionMetadataBasedReader(Target target, TargetPointer metadataLocation, int metadataSize) + { + _bytes = GC.AllocateArray(metadataSize, pinned: true); + target.ReadBuffer(metadataLocation, _bytes); + fixed (byte* actualPtr = _bytes) + { + mr = new(actualPtr, _bytes.Length); + } + + columnSize = new int[(int)MetadataColumnIndex.Count]; + columnOffset = new int[(int)MetadataColumnIndex.Count]; + + tableRowCount = new int[(int)MetadataTable.MaxValue + 1]; + tableRowCount[(int)MetadataTable.TypeDef] = MetadataReaderExtensions.GetTableRowCount(mr, TableIndex.TypeDef); + tableRowCount[(int)MetadataTable.MethodDef] = MetadataReaderExtensions.GetTableRowCount(mr, TableIndex.MethodDef); + tableRowCount[(int)MetadataTable.Field] = MetadataReaderExtensions.GetTableRowCount(mr, TableIndex.Field); + tableRowCount[(int)MetadataTable.TypeRef] = MetadataReaderExtensions.GetTableRowCount(mr, TableIndex.TypeRef); + tableRowCount[(int)MetadataTable.TypeSpec] = MetadataReaderExtensions.GetTableRowCount(mr, TableIndex.TypeSpec); + + blobHeapSize = MetadataReaderExtensions.GetHeapSize(mr, HeapIndex.Blob); + stringHeapSize = MetadataReaderExtensions.GetHeapSize(mr, HeapIndex.String); + userStringHeapSize = MetadataReaderExtensions.GetHeapSize(mr, HeapIndex.UserString); + + ComputeColumnSizesAndOffsets(); + + void ComputeColumnSizesAndOffsets() + { + MetadataTable currentTable = MetadataTable.Unused; + MetadataColumnIndex? prevColumn = null; + + for (int i = 0; i < (int)MetadataColumnIndex.Count; i++) + { + MetadataColumnIndex column = (MetadataColumnIndex)i; + if (currentTable != ColumnTable(column)) + { + columnOffset[i] = 0; + prevColumn = null; + } + else + { + columnOffset[i] = ComputeColumnEnd(prevColumn!.Value); + } + prevColumn = column; + + columnSize[i] = columnTypes[i] switch + { + ColumnType.TwoByteConstant => 2, + ColumnType.FourByteConstant => 4, + ColumnType.Utf8String => StringEncodingBytes, + ColumnType.UserString => UserStringEncodingBytes, + ColumnType.Blob => BlobEncodingBytes, + ColumnType.Token => CodedIndexEncodingBytes(codedIndexDecoderRing[i]), + _ => throw new System.Exception() + }; + } + } + + int ComputeColumnEnd(MetadataColumnIndex column) + { + return ColumnOffset(column) + ColumnSize(column); + } + } + + private int ColumnSize(MetadataColumnIndex column) + { + return columnSize[(int)column]; + } + + private int ColumnOffset(MetadataColumnIndex column) + { + return columnOffset[(int)column]; + } + + private static MetadataTable ColumnTable(MetadataColumnIndex column) + { + return columnTable[(int)column]; + } + + private int StringEncodingBytes => stringHeapSize > 0xFFFF ? 4 : 2; + private int UserStringEncodingBytes => userStringHeapSize > 0xFFFF ? 4 : 2; + private int BlobEncodingBytes => blobHeapSize > 0xFFFF ? 4 : 2; + + private int RidEncodingBits(MetadataTable table) + { + if (table == MetadataTable.Unused) + return 0; + + int countInTable = tableRowCount[(int)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. + } + foreach (MetadataTable table in tablesEncoded) + { + if ((RidEncodingBits(table) + bitsForTableEncoding) > 16) + return 4; + } + return 2; + } + + 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 MetadataReader.CreateToken(table, rid); + } + + private ReadOnlySpan TokenToRow(MetadataTable table, uint rid) + { + if (table == MetadataTable.Unused) + throw new ArgumentOutOfRangeException(nameof(table)); + + if (MetadataReader.RidFromToken(rid) <= 0) + throw new ArgumentOutOfRangeException(nameof(rid)); + + TableIndex tableIndex = (TableIndex)table; + + int offset = MetadataReaderExtensions.GetTableMetadataOffset(mr, tableIndex); + int tableRowSize = MetadataReaderExtensions.GetTableRowSize(mr, tableIndex); + int tableEntryCount = MetadataReaderExtensions.GetTableRowCount(mr, tableIndex); + + ReadOnlySpan tableBytes = _bytes.AsSpan().Slice(offset, tableRowSize * tableEntryCount); + return tableBytes.Slice(0, tableRowSize * (int)(rid - 1)); + } + + [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, IsSigned(), out value); + } + private bool TryReadTableEntry(ReadOnlySpan bytes, MetadataColumnIndex column, out uint value) + { + if (ColumnOffset(column) == 0) + throw new ArgumentOutOfRangeException(nameof(column)); + + 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(MetadataCursor c, MetadataColumnIndex col_idx) + { + MetadataTable tableEnumValue = MetadataReader.TokenToTable((uint)c.reserved1); + if (tableEnumValue != ColumnTable(col_idx)) + throw new ArgumentOutOfRangeException(nameof(col_idx)); + + ReadOnlySpan row = TokenToRow(tableEnumValue, MetadataReader.RidFromToken((uint)c.reserved1)); + if (!TryReadTableEntry(row, col_idx, out uint rawResult)) + { + throw new ArgumentOutOfRangeException(nameof(col_idx)); + } + return rawResult; + } + + public override System.ReadOnlySpan GetColumnAsBlob(MetadataCursor c, MetadataColumnIndex col_idx) + { + throw new NotImplementedException(); + } + public override uint GetColumnAsConstant(MetadataCursor 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 override MetadataCursor GetColumnAsCursor(MetadataCursor c, MetadataColumnIndex col_idx) => throw new System.NotImplementedException(); + public override System.Guid GetColumnAsGuid(MetadataCursor c, MetadataColumnIndex col_idx) => throw new System.NotImplementedException(); + public override void GetColumnAsRange(MetadataCursor c, MetadataColumnIndex col_idx, out MetadataCursor cursor, out int count) => throw new System.NotImplementedException(); + public override uint GetColumnAsToken(MetadataCursor 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 override System.ReadOnlySpan GetColumnAsUserstring(MetadataCursor c, MetadataColumnIndex col_idx) => throw new System.NotImplementedException(); + + public override System.ReadOnlySpan GetColumnAsUtf8(MetadataCursor c, MetadataColumnIndex col_idx) + { + if (columnTypes[(int)col_idx] != ColumnType.Utf8String) + throw new ArgumentOutOfRangeException(nameof(col_idx)); + uint rawResult = GetColumnRaw(c, col_idx); + + if (rawResult == 0) + return default(ReadOnlySpan); + + checked + { + int initialOffset = MetadataReaderExtensions.GetHeapMetadataOffset(mr, HeapIndex.String); + initialOffset += (int)rawResult; + int curOffset = initialOffset; + while (_bytes[curOffset] != '\0') + { + curOffset++; + } + return _bytes.AsSpan().Slice(initialOffset, curOffset - initialOffset); + } + } + public override MetadataCursor GetCursor(uint token) + { + if (!TryGetCursor(token, out MetadataCursor cursor)) + { + throw new ArgumentOutOfRangeException(nameof(token)); + } + return cursor; + } + public override uint GetToken(MetadataCursor c) + { + return (uint)c.reserved1; + } + public override bool TryFindRowFromCursor(MetadataCursor begin, MetadataColumnIndex col_idx, uint value, out MetadataCursor foundCursor) => throw new System.NotImplementedException(); + public override bool TryGetCursor(uint token, out MetadataCursor cursor) + { + cursor = default; + MetadataTable tableEnumValue = MetadataReader.TokenToTable(token); + if (tableEnumValue == MetadataTable.Unused) + return false; + + TableIndex table = (TableIndex)tableEnumValue; + + if (MetadataReaderExtensions.GetTableRowCount(mr, table) < MetadataReader.RidFromToken(token)) + return false; + + cursor.reserved1 = token; + cursor.reserved2 = this; + return true; + } + public override bool TryGetCursorToFirstEntryInTable(MetadataTable table, out MetadataCursor cursor) + { + cursor = default; + if (MetadataReaderExtensions.GetTableRowCount(mr, (TableIndex)table) > 0) + { + cursor.reserved1 = MetadataReader.CreateToken(table, 1); + cursor.reserved2 = this; + return true; + } + return false; + } + } +} diff --git a/src/native/managed/cdacreader/src/Contracts/Registry.cs b/src/native/managed/cdacreader/src/Contracts/Registry.cs index 21f46e481ecd9..fef95df1364f1 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 IMetadata Metadata => 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 8d37ca5441fcd..80c12d8ba1c37 100644 --- a/src/native/managed/cdacreader/src/Contracts/RuntimeTypeSystem.cs +++ b/src/native/managed/cdacreader/src/Contracts/RuntimeTypeSystem.cs @@ -17,6 +17,58 @@ internal MethodTableHandle(TargetPointer address) internal TargetPointer Address { get; } } +internal readonly struct TypeHandle +{ + private readonly MethodTableHandle? _mtHandle; + + internal TypeHandle(MethodTableHandle mtHandle) + { + _mtHandle = mtHandle; + } + + public static implicit operator TypeHandle(MethodTableHandle mtHandle) => new TypeHandle(mtHandle); + + public MethodTableHandle AsMethodTable => _mtHandle!.Value; + public bool IsMethodTable => _mtHandle.HasValue; + public bool IsNull => !_mtHandle.HasValue; +} + +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 { static string IContract.Name => nameof(RuntimeTypeSystem); @@ -58,7 +110,46 @@ static IContract IContract.Create(Target target, int version) // 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(); + + public virtual uint GetInstantiation(MethodTableHandle methodTable, out TargetPointer instantiation) => throw new NotImplementedException(); + public virtual bool IsGenericTypeDefinition(MethodTableHandle methodTable) => throw new NotImplementedException(); #endregion MethodTable inspection APIs + + #region TypeHandle inspection APIs + public virtual bool HasTypeParam(TypeHandle typeHandle) => throw new NotImplementedException(); + 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 + + // Default implementation is implemented in terms of other apis already on RuntimeTypeSystem + public virtual TargetPointer GetModule(TypeHandle typeHandle) + { + if (typeHandle.IsMethodTable) + return GetModule(typeHandle.AsMethodTable); + else + { + 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; + } + } + } + + #endregion TypeHandle inspection APIs } internal struct RuntimeTypeSystem : IRuntimeTypeSystem diff --git a/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs b/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs index 3567f3b8571f3..9e2cbb22f637d 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; @@ -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; } @@ -141,12 +145,65 @@ public unsafe int GetMethodTableForEEClass(ulong eeClassReallyCanonMT, ulong* va *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.MethodTableHandle methodTableHandle = typeSystemContract.GetMethodTableHandle(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, new TypeHandle(methodTableHandle), TypeNameFormat.FormatNamespace | TypeNameFormat.FormatFullInst); + } + catch + { + // TODO(cdac) - + Debug.Fail("Need to implment the fallback path here"); + } + 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 +248,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 +265,7 @@ public unsafe int GetNestedExceptionData(ulong exception, ulong* exceptionObject *exceptionObject = exceptionObjectLocal; *nextNestedException = nextNestedExceptionLocal; } - catch (Exception ex) + catch (global::System.Exception ex) { return ex.HResult; } @@ -260,7 +317,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; } @@ -290,7 +347,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 0000000000000..a37f2641eb319 --- /dev/null +++ b/src/native/managed/cdacreader/src/Legacy/TypeNameBuilder.cs @@ -0,0 +1,569 @@ +// 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; + +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); + MetadataReader reader = tnb.Target.Contracts.Metadata.GetMetadataReader(module); + MetadataCursor cursor = reader.GetCursor(genericParamToken); + if (format.HasFlag(TypeNameFormat.FormatGenericParam)) + { + uint owner = reader.GetColumnAsToken(cursor, MetadataColumnIndex.GenericParam_Owner); + if (MetadataReader.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 + MethodTableHandle methodTable = typeHandle.AsMethodTable; + uint typeDefToken = typeSystemContract.GetTypeDefToken(methodTable); + Contracts.ModuleHandle moduleHandle = tnb.Target.Contracts.Loader.GetModuleHandle(typeSystemContract.GetModule(methodTable)); + if (MetadataReader.RidFromToken(typeDefToken) == 0) + { + tnb.AddName("(dynamicClass)"); + } + else + { + MetadataReader reader = tnb.Target.Contracts.Metadata.GetMetadataReader(moduleHandle); + AppendNestedTypeDef(ref tnb, reader, typeDefToken, format); + } + + // Append instantiation + + + if (format.HasFlag(TypeNameFormat.FormatNamespace) || format.HasFlag(TypeNameFormat.FormatAssembly)) + { + TargetPointer instantiationPointer; + uint instantiationLength = typeSystemContract.GetInstantiation(methodTable, out instantiationPointer); + + if ((instantiationLength > 0) && (!typeSystemContract.IsGenericTypeDefinition(methodTable) || toString)) + { + if (instantiation.Length == 0) + { + Span targetPointerSpan = stackalloc TargetPointer[4]; + Span instantiationSpan = stackalloc MethodTableHandle[4]; + if (instantiationLength > targetPointerSpan.Length) + { + targetPointerSpan = new TargetPointer[instantiationLength]; + instantiationSpan = new MethodTableHandle[instantiationLength]; + } + targetPointerSpan = targetPointerSpan.Slice(0, (int)instantiationLength); + instantiationSpan = instantiationSpan.Slice(0, (int)instantiationLength); + + tnb.Target.ReadPointers(instantiationPointer, targetPointerSpan); + + for (int i = 0; i < instantiationLength; i++) + { + instantiationSpan[i] = new MethodTableHandle(targetPointerSpan[i]); + } + } + 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 + MetadataReader mr = tnb.Target.Contracts.Metadata.GetMetadataReader(module); + MetadataCursor 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 (MethodTableHandle 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(','); + } + + 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)) + { + if (InstNesting > 0) + EscapeEmbeddedAssemblyName(assemblySpec); + else + EscapeAssemblyName(assemblySpec); + + HasAssemblySpec = true; + } + } + + private static void AppendNestedTypeDef(ref TypeNameBuilder tnb, MetadataReader reader, uint typeDefToken, TypeNameFormat format) + { + MetadataCursor 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(); + MetadataCursor nestedTypesCursor = reader.GetCursor(MetadataReader.CreateToken(MetadataTable.NestedClass, 1)); + while (reader.TryFindRowFromCursor(nestedTypesCursor, MetadataColumnIndex.NestedClass_NestedClass, currentTypeDefToken, out MetadataCursor 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, MetadataReader reader, uint typeDefToken, TypeNameFormat format) + { + MetadataCursor 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 06b201d3957d5..f2745a5e26e8e 100644 --- a/src/native/managed/cdacreader/src/Target.cs +++ b/src/native/managed/cdacreader/src/Target.cs @@ -266,6 +266,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 +291,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)) From 0671cd41ddb0c2883ef2026be15a31b7414b0efa Mon Sep 17 00:00:00 2001 From: David Wrighton Date: Wed, 10 Jul 2024 17:07:39 -0700 Subject: [PATCH 02/15] Move metadata reader into helpers Add implementations for changes to RuntimeTypeSystem contract Add SOSDacInterface call into cDac for GetMethodTableName --- src/coreclr/debug/daccess/dacimpl.h | 1 + src/coreclr/debug/daccess/request.cpp | 39 +- .../debug/runtimeinfo/datadescriptor.h | 36 ++ src/coreclr/vm/class.h | 6 + src/coreclr/vm/methodtable.cpp | 14 +- src/coreclr/vm/methodtable.h | 8 + src/coreclr/vm/typedesc.h | 32 + .../cdacreader/src/Contracts/Metadata.cs | 301 ++++++--- .../cdacreader/src/Contracts/Metadata_1.cs | 423 ------------- .../src/Contracts/RuntimeTypeSystem.cs | 23 +- .../RuntimeTypeSystem_1.MethodTableFlags.cs | 9 + .../src/Contracts/RuntimeTypeSystem_1.cs | 190 ++++++ .../managed/cdacreader/src/Data/EEClass.cs | 15 + .../cdacreader/src/Data/GenericsDictInfo.cs | 25 + .../managed/cdacreader/src/Data/IData.cs | 5 + .../cdacreader/src/Data/MethodTable.cs | 2 + .../cdacreader/src/Data/MethodTableArray.cs | 33 + .../managed/cdacreader/src/Data/TypeDesc.cs | 78 +++ .../cdacreader/src/Data/TypeHandleArray.cs | 33 + src/native/managed/cdacreader/src/DataType.cs | 6 + .../EcmaMetadataReader.InternalHelpers.cs | 109 ++++ .../Helpers/EcmaMetadataReader.StaticData.cs | 594 ++++++++++++++++++ .../EcmaMetadataReader.StaticHelpers.cs | 59 ++ .../src/Helpers/EcmaMetadataReader.cs | 396 ++++++++++++ .../cdacreader/src/Legacy/TypeNameBuilder.cs | 50 +- src/native/managed/cdacreader/src/Target.cs | 53 ++ 26 files changed, 1997 insertions(+), 543 deletions(-) create mode 100644 src/native/managed/cdacreader/src/Data/GenericsDictInfo.cs create mode 100644 src/native/managed/cdacreader/src/Data/MethodTableArray.cs create mode 100644 src/native/managed/cdacreader/src/Data/TypeDesc.cs create mode 100644 src/native/managed/cdacreader/src/Data/TypeHandleArray.cs create mode 100644 src/native/managed/cdacreader/src/Helpers/EcmaMetadataReader.InternalHelpers.cs create mode 100644 src/native/managed/cdacreader/src/Helpers/EcmaMetadataReader.StaticData.cs create mode 100644 src/native/managed/cdacreader/src/Helpers/EcmaMetadataReader.StaticHelpers.cs create mode 100644 src/native/managed/cdacreader/src/Helpers/EcmaMetadataReader.cs diff --git a/src/coreclr/debug/daccess/dacimpl.h b/src/coreclr/debug/daccess/dacimpl.h index 2d2aad5bd1f96..2955fb4d3ec61 100644 --- a/src/coreclr/debug/daccess/dacimpl.h +++ b/src/coreclr/debug/daccess/dacimpl.h @@ -1235,6 +1235,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); BOOL IsExceptionFromManagedCode(EXCEPTION_RECORD * pExceptionRecord); #ifndef TARGET_UNIX diff --git a/src/coreclr/debug/daccess/request.cpp b/src/coreclr/debug/daccess/request.cpp index 291d048eed7ed..9c779136fa3c5 100644 --- a/src/coreclr/debug/daccess/request.cpp +++ b/src/coreclr/debug/daccess/request.cpp @@ -1916,7 +1916,45 @@ 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 == wcsncmp(pNeeded, (WCHAR *)pwszNameLocal, count)); + } + if (pNeeded != NULL) + { + _ASSERTE(*pNeeded == neededLocal); + } + } +#endif + } + else + { + hr = GetMethodTableNameImpl(mt, count, mtName, pNeeded); + } + + SOSDacLeave(); +} + +HRESULT +ClrDataAccess::GetMethodTableNameImpl(CLRDATA_ADDRESS mt, unsigned int count, _Inout_updates_z_(count) WCHAR *mtName, unsigned int *pNeeded) +{ PTR_MethodTable pMT = PTR_MethodTable(TO_TADDR(mt)); BOOL free = FALSE; @@ -1987,7 +2025,6 @@ ClrDataAccess::GetMethodTableName(CLRDATA_ADDRESS mt, unsigned int count, _Inout } } - SOSDacLeave(); return hr; } diff --git a/src/coreclr/debug/runtimeinfo/datadescriptor.h b/src/coreclr/debug/runtimeinfo/datadescriptor.h index d1b5443e497b5..248ff7822a1ed 100644 --- a/src/coreclr/debug/runtimeinfo/datadescriptor.h +++ b/src/coreclr/debug/runtimeinfo/datadescriptor.h @@ -186,6 +186,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) @@ -193,8 +194,43 @@ 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(GenericDictInfo) +CDAC_TYPE_INDETERMINATE(GenericDictInfo) +CDAC_TYPE_FIELD(GenericDictInfo, /*uint16*/, NumDicts, cdac_offsets::NumDicts) +CDAC_TYPE_FIELD(GenericDictInfo, /*uint16*/, NumTyPars, cdac_offsets::NumTyPars) +CDAC_TYPE_END(GenericDictInfo) + +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_TYPES_END() CDAC_GLOBALS_BEGIN() diff --git a/src/coreclr/vm/class.h b/src/coreclr/vm/class.h index 74c66714555f3..a5bd9bd8be662 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); @@ -1996,7 +1997,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 dde37703a384c..26176d6832884 100644 --- a/src/coreclr/vm/methodtable.cpp +++ b/src/coreclr/vm/methodtable.cpp @@ -4831,7 +4831,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; @@ -4842,17 +4842,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 80abb784df1fa..437d7ba8cfe2d 100644 --- a/src/coreclr/vm/methodtable.h +++ b/src/coreclr/vm/methodtable.h @@ -293,9 +293,16 @@ 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 NumDicts = offsetof(GenericsDictInfo, m_wNumDicts); + static constexpr size_t NumTyPars = offsetof(GenericsDictInfo, m_wNumTyPars); +}; // These various statics structures exist directly before the MethodTableAuxiliaryData @@ -3904,6 +3911,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 e8d7526bd0b16..2518f86d5adaf 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/native/managed/cdacreader/src/Contracts/Metadata.cs b/src/native/managed/cdacreader/src/Contracts/Metadata.cs index 93cdb6bd44208..4d3db918ee8f5 100644 --- a/src/native/managed/cdacreader/src/Contracts/Metadata.cs +++ b/src/native/managed/cdacreader/src/Contracts/Metadata.cs @@ -53,17 +53,194 @@ internal enum MetadataTable GenericParam = 0x2a, MethodSpec = 0x2b, GenericParamConstraint = 0x2c, - MaxValue = 0x2c + Count = 0x2c } -internal struct MetadataCursor +internal struct EcmaMetadataSchema { - public ulong reserved1; - public object reserved2; + 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 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; } + + // This isn't technically part of the contract, but it is here to reduce the complexity of using this contract + 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, @@ -74,84 +251,46 @@ internal enum MetadataColumnIndex 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, - NestedClass_NestedClass, - NestedClass_EnclosingClass, - TypeDef_Flags, - TypeDef_TypeName, - TypeDef_TypeNamespace, - TypeDef_Extends, - TypeDef_FieldList, - TypeDef_MethodList, - Count -} -internal abstract class MetadataReader -{ - public static MetadataTable TokenToTable(uint token) - { - byte tableIndex = (byte)(token >> 24); - if (tableIndex > (uint)MetadataTable.GenericParamConstraint) - { - return MetadataTable.Unused; - } - else - { - return (MetadataTable)tableIndex; - } - } + MethodSpec_Method, + MethodSpec_Instantiation, - 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; - } + GenericParamConstraint_Owner, + GenericParamConstraint_Constraint, - public abstract MetadataCursor GetCursor(uint token); - public abstract bool TryGetCursor(uint token, out MetadataCursor cursor); - public abstract bool TryGetCursorToFirstEntryInTable(MetadataTable table, out MetadataCursor cursor); - public abstract uint GetToken(MetadataCursor c); - - // Query row's column values - // The returned number represents the number of valid cursor(s) for indexing. - public abstract uint GetColumnAsToken(MetadataCursor c, MetadataColumnIndex col_idx); - public abstract MetadataCursor GetColumnAsCursor(MetadataCursor c, MetadataColumnIndex col_idx); - // Resolve the column to a cursor and a range based on the run/list pattern in tables. - // The run continues to the smaller of: - // * the last row of the target table - // * the next run in the target table, found by inspecting the column value of the next row in the current table. - // See md_find_token_of_range_element() for mapping elements in the other direction. - public abstract void GetColumnAsRange(MetadataCursor c, MetadataColumnIndex col_idx, out MetadataCursor cursor, out int count); - public abstract uint GetColumnAsConstant(MetadataCursor c, MetadataColumnIndex col_idx); - public abstract ReadOnlySpan GetColumnAsUtf8(MetadataCursor c, MetadataColumnIndex col_idx); - public virtual string GetColumnAsUtf8String(MetadataCursor 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; - } - public abstract ReadOnlySpan GetColumnAsUserstring(MetadataCursor c, MetadataColumnIndex col_idx); - public abstract ReadOnlySpan GetColumnAsBlob(MetadataCursor c, MetadataColumnIndex col_idx); - public abstract Guid GetColumnAsGuid(MetadataCursor c, MetadataColumnIndex col_idx); - - // Find a row or range of rows where the supplied column has the expected value. - // These APIs assume the value to look for is the value in the table, typically record IDs (RID) - // for tokens. An exception is made for coded indices, which are cumbersome to compute. - // If the queried column contains a coded index value, the value will be validated and - // transformed to its coded form for comparison. - public abstract bool TryFindRowFromCursor(MetadataCursor begin, MetadataColumnIndex col_idx, uint value, out MetadataCursor foundCursor); + Count } internal interface IMetadata : IContract @@ -165,7 +304,17 @@ static IContract IContract.Create(Target target, int version) }; } - public virtual MetadataReader GetMetadataReader(ModuleHandle module) => throw new NotImplementedException(); + public virtual EcmaMetadata GetMetadata(ModuleHandle module) => throw new NotImplementedException(); + + // Allow users to provide metadata from outside the contract system. Used to enable supporting scenarios where the metadata is not in a + // dump file, or the contract api user wishes to provide a memory mapped metadata instead of reading it from the target process. + public virtual void RegisterMetadataProvider(Func provider) => throw new NotImplementedException(); + + // Helper api intended for users of RegisterMetadataProvider, not officially part of the documented contract, but placed here in the code for greater visibility + public EcmaMetadata ProduceEcmaMetadataFromMemory(ReadOnlyMemory image) + { + return (new Helpers.EcmaMetadataReader(image)).UnderlyingMetadata; + } } internal readonly struct Metadata : IMetadata diff --git a/src/native/managed/cdacreader/src/Contracts/Metadata_1.cs b/src/native/managed/cdacreader/src/Contracts/Metadata_1.cs index b852acb8a5dc8..89d2a462746fe 100644 --- a/src/native/managed/cdacreader/src/Contracts/Metadata_1.cs +++ b/src/native/managed/cdacreader/src/Contracts/Metadata_1.cs @@ -20,427 +20,4 @@ internal Metadata_1(Target target) { _target = target; } - - public MetadataReader GetMetadataReader(ModuleHandle module) => throw new System.NotImplementedException(); - - private class SystemReflectionMetadataBasedReader : MetadataReader - { - private enum ColumnType - { - Unknown, - TwoByteConstant, - FourByteConstant, - Utf8String, - UserString, - Blob, - Token - } - private byte[] _bytes; - private System.Reflection.Metadata.MetadataReader mr; - private int[] tableRowCount; - private int[] columnSize; - private int[] columnOffset; - private int stringHeapSize; - private int userStringHeapSize; - private int blobHeapSize; - private static readonly MetadataTable[] columnTable = GetColumnTables(); - private static readonly ColumnType[] columnTypes = GetColumnTypes(); - private static readonly Func[] columnTokenDecode = GetColumnTokenDecode(); - private static readonly MetadataTable[][] codedIndexDecoderRing = GetCodedIndexDecoderRing(); - - private static readonly MetadataTable[] TypeOrMethodDef = { MetadataTable.TypeDef, MetadataTable.MethodDef }; - private static readonly MetadataTable[] TypeDefOrRef = { MetadataTable.TypeDef, MetadataTable.TypeRef, MetadataTable.TypeSpec }; - - private static ColumnType[] GetColumnTypes() - { - ColumnType[] columnTypes = new ColumnType[(int)MetadataColumnIndex.Count]; - - 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.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.NestedClass_NestedClass] = ColumnType.Token; - columnTypes[(int)MetadataColumnIndex.NestedClass_EnclosingClass] = ColumnType.Token; - - 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; - - return columnTypes; - } - - private static MetadataTable[] GetColumnTables() - { - MetadataTable[] metadataTables = new MetadataTable[(int)MetadataColumnIndex.Count]; - - 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.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.NestedClass_NestedClass] = MetadataTable.NestedClass; - metadataTables[(int)MetadataColumnIndex.NestedClass_EnclosingClass] = MetadataTable.NestedClass; - - 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; - - return metadataTables; - } - - private static MetadataTable[][] GetCodedIndexDecoderRing() - { - MetadataTable[][] decoderRing = new MetadataTable[(int)MetadataColumnIndex.Count][]; - - decoderRing[(int)MetadataColumnIndex.GenericParam_Owner] = TypeOrMethodDef; - - decoderRing[(int)MetadataColumnIndex.NestedClass_NestedClass] = new[] { MetadataTable.TypeDef }; - decoderRing[(int)MetadataColumnIndex.NestedClass_EnclosingClass] = new[] { MetadataTable.TypeDef }; - - decoderRing[(int)MetadataColumnIndex.TypeDef_Extends] = TypeDefOrRef; - decoderRing[(int)MetadataColumnIndex.TypeDef_FieldList] = new[] { MetadataTable.Field }; - decoderRing[(int)MetadataColumnIndex.TypeDef_MethodList] = new[] { MetadataTable.MethodDef }; - - return decoderRing; - } - private static Func[] GetColumnTokenDecode() - { - Func[] columnTokenDecode = new Func[(int)MetadataColumnIndex.Count]; - MetadataTable[][] decoderRing = 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 MetadataReader.CreateToken(metadataTable, input); }; - } - else - { - result = delegate (uint input) { return DecodeCodedIndex(input, decoderData); }; - } - - return result; - } - } - - - public unsafe SystemReflectionMetadataBasedReader(Target target, TargetPointer metadataLocation, int metadataSize) - { - _bytes = GC.AllocateArray(metadataSize, pinned: true); - target.ReadBuffer(metadataLocation, _bytes); - fixed (byte* actualPtr = _bytes) - { - mr = new(actualPtr, _bytes.Length); - } - - columnSize = new int[(int)MetadataColumnIndex.Count]; - columnOffset = new int[(int)MetadataColumnIndex.Count]; - - tableRowCount = new int[(int)MetadataTable.MaxValue + 1]; - tableRowCount[(int)MetadataTable.TypeDef] = MetadataReaderExtensions.GetTableRowCount(mr, TableIndex.TypeDef); - tableRowCount[(int)MetadataTable.MethodDef] = MetadataReaderExtensions.GetTableRowCount(mr, TableIndex.MethodDef); - tableRowCount[(int)MetadataTable.Field] = MetadataReaderExtensions.GetTableRowCount(mr, TableIndex.Field); - tableRowCount[(int)MetadataTable.TypeRef] = MetadataReaderExtensions.GetTableRowCount(mr, TableIndex.TypeRef); - tableRowCount[(int)MetadataTable.TypeSpec] = MetadataReaderExtensions.GetTableRowCount(mr, TableIndex.TypeSpec); - - blobHeapSize = MetadataReaderExtensions.GetHeapSize(mr, HeapIndex.Blob); - stringHeapSize = MetadataReaderExtensions.GetHeapSize(mr, HeapIndex.String); - userStringHeapSize = MetadataReaderExtensions.GetHeapSize(mr, HeapIndex.UserString); - - ComputeColumnSizesAndOffsets(); - - void ComputeColumnSizesAndOffsets() - { - MetadataTable currentTable = MetadataTable.Unused; - MetadataColumnIndex? prevColumn = null; - - for (int i = 0; i < (int)MetadataColumnIndex.Count; i++) - { - MetadataColumnIndex column = (MetadataColumnIndex)i; - if (currentTable != ColumnTable(column)) - { - columnOffset[i] = 0; - prevColumn = null; - } - else - { - columnOffset[i] = ComputeColumnEnd(prevColumn!.Value); - } - prevColumn = column; - - columnSize[i] = columnTypes[i] switch - { - ColumnType.TwoByteConstant => 2, - ColumnType.FourByteConstant => 4, - ColumnType.Utf8String => StringEncodingBytes, - ColumnType.UserString => UserStringEncodingBytes, - ColumnType.Blob => BlobEncodingBytes, - ColumnType.Token => CodedIndexEncodingBytes(codedIndexDecoderRing[i]), - _ => throw new System.Exception() - }; - } - } - - int ComputeColumnEnd(MetadataColumnIndex column) - { - return ColumnOffset(column) + ColumnSize(column); - } - } - - private int ColumnSize(MetadataColumnIndex column) - { - return columnSize[(int)column]; - } - - private int ColumnOffset(MetadataColumnIndex column) - { - return columnOffset[(int)column]; - } - - private static MetadataTable ColumnTable(MetadataColumnIndex column) - { - return columnTable[(int)column]; - } - - private int StringEncodingBytes => stringHeapSize > 0xFFFF ? 4 : 2; - private int UserStringEncodingBytes => userStringHeapSize > 0xFFFF ? 4 : 2; - private int BlobEncodingBytes => blobHeapSize > 0xFFFF ? 4 : 2; - - private int RidEncodingBits(MetadataTable table) - { - if (table == MetadataTable.Unused) - return 0; - - int countInTable = tableRowCount[(int)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. - } - foreach (MetadataTable table in tablesEncoded) - { - if ((RidEncodingBits(table) + bitsForTableEncoding) > 16) - return 4; - } - return 2; - } - - 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 MetadataReader.CreateToken(table, rid); - } - - private ReadOnlySpan TokenToRow(MetadataTable table, uint rid) - { - if (table == MetadataTable.Unused) - throw new ArgumentOutOfRangeException(nameof(table)); - - if (MetadataReader.RidFromToken(rid) <= 0) - throw new ArgumentOutOfRangeException(nameof(rid)); - - TableIndex tableIndex = (TableIndex)table; - - int offset = MetadataReaderExtensions.GetTableMetadataOffset(mr, tableIndex); - int tableRowSize = MetadataReaderExtensions.GetTableRowSize(mr, tableIndex); - int tableEntryCount = MetadataReaderExtensions.GetTableRowCount(mr, tableIndex); - - ReadOnlySpan tableBytes = _bytes.AsSpan().Slice(offset, tableRowSize * tableEntryCount); - return tableBytes.Slice(0, tableRowSize * (int)(rid - 1)); - } - - [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, IsSigned(), out value); - } - private bool TryReadTableEntry(ReadOnlySpan bytes, MetadataColumnIndex column, out uint value) - { - if (ColumnOffset(column) == 0) - throw new ArgumentOutOfRangeException(nameof(column)); - - 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(MetadataCursor c, MetadataColumnIndex col_idx) - { - MetadataTable tableEnumValue = MetadataReader.TokenToTable((uint)c.reserved1); - if (tableEnumValue != ColumnTable(col_idx)) - throw new ArgumentOutOfRangeException(nameof(col_idx)); - - ReadOnlySpan row = TokenToRow(tableEnumValue, MetadataReader.RidFromToken((uint)c.reserved1)); - if (!TryReadTableEntry(row, col_idx, out uint rawResult)) - { - throw new ArgumentOutOfRangeException(nameof(col_idx)); - } - return rawResult; - } - - public override System.ReadOnlySpan GetColumnAsBlob(MetadataCursor c, MetadataColumnIndex col_idx) - { - throw new NotImplementedException(); - } - public override uint GetColumnAsConstant(MetadataCursor 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 override MetadataCursor GetColumnAsCursor(MetadataCursor c, MetadataColumnIndex col_idx) => throw new System.NotImplementedException(); - public override System.Guid GetColumnAsGuid(MetadataCursor c, MetadataColumnIndex col_idx) => throw new System.NotImplementedException(); - public override void GetColumnAsRange(MetadataCursor c, MetadataColumnIndex col_idx, out MetadataCursor cursor, out int count) => throw new System.NotImplementedException(); - public override uint GetColumnAsToken(MetadataCursor 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 override System.ReadOnlySpan GetColumnAsUserstring(MetadataCursor c, MetadataColumnIndex col_idx) => throw new System.NotImplementedException(); - - public override System.ReadOnlySpan GetColumnAsUtf8(MetadataCursor c, MetadataColumnIndex col_idx) - { - if (columnTypes[(int)col_idx] != ColumnType.Utf8String) - throw new ArgumentOutOfRangeException(nameof(col_idx)); - uint rawResult = GetColumnRaw(c, col_idx); - - if (rawResult == 0) - return default(ReadOnlySpan); - - checked - { - int initialOffset = MetadataReaderExtensions.GetHeapMetadataOffset(mr, HeapIndex.String); - initialOffset += (int)rawResult; - int curOffset = initialOffset; - while (_bytes[curOffset] != '\0') - { - curOffset++; - } - return _bytes.AsSpan().Slice(initialOffset, curOffset - initialOffset); - } - } - public override MetadataCursor GetCursor(uint token) - { - if (!TryGetCursor(token, out MetadataCursor cursor)) - { - throw new ArgumentOutOfRangeException(nameof(token)); - } - return cursor; - } - public override uint GetToken(MetadataCursor c) - { - return (uint)c.reserved1; - } - public override bool TryFindRowFromCursor(MetadataCursor begin, MetadataColumnIndex col_idx, uint value, out MetadataCursor foundCursor) => throw new System.NotImplementedException(); - public override bool TryGetCursor(uint token, out MetadataCursor cursor) - { - cursor = default; - MetadataTable tableEnumValue = MetadataReader.TokenToTable(token); - if (tableEnumValue == MetadataTable.Unused) - return false; - - TableIndex table = (TableIndex)tableEnumValue; - - if (MetadataReaderExtensions.GetTableRowCount(mr, table) < MetadataReader.RidFromToken(token)) - return false; - - cursor.reserved1 = token; - cursor.reserved2 = this; - return true; - } - public override bool TryGetCursorToFirstEntryInTable(MetadataTable table, out MetadataCursor cursor) - { - cursor = default; - if (MetadataReaderExtensions.GetTableRowCount(mr, (TableIndex)table) > 0) - { - cursor.reserved1 = MetadataReader.CreateToken(table, 1); - cursor.reserved2 = this; - return true; - } - return false; - } - } } diff --git a/src/native/managed/cdacreader/src/Contracts/RuntimeTypeSystem.cs b/src/native/managed/cdacreader/src/Contracts/RuntimeTypeSystem.cs index 80c12d8ba1c37..9550a7339b12e 100644 --- a/src/native/managed/cdacreader/src/Contracts/RuntimeTypeSystem.cs +++ b/src/native/managed/cdacreader/src/Contracts/RuntimeTypeSystem.cs @@ -17,19 +17,37 @@ internal MethodTableHandle(TargetPointer address) internal TargetPointer Address { get; } } +internal readonly struct TypeDescHandle +{ + internal TypeDescHandle(TargetPointer address) + { + Address = address; + } + + internal TargetPointer Address { get; } +} + internal readonly struct TypeHandle { private readonly MethodTableHandle? _mtHandle; + private readonly TypeDescHandle? _typeDescHandle; internal TypeHandle(MethodTableHandle mtHandle) { _mtHandle = mtHandle; } + internal TypeHandle(TypeDescHandle typeDescHandle) + { + _typeDescHandle = typeDescHandle; + } + public static implicit operator TypeHandle(MethodTableHandle mtHandle) => new TypeHandle(mtHandle); public MethodTableHandle AsMethodTable => _mtHandle!.Value; public bool IsMethodTable => _mtHandle.HasValue; + public TypeDescHandle AsTypeDesc => _typeDescHandle!.Value; + public bool IsTypeDesc => _typeDescHandle.HasValue; public bool IsNull => !_mtHandle.HasValue; } @@ -111,12 +129,15 @@ static IContract IContract.Create(Target target, int version) // or for its generic type definition if it is a generic instantiation public virtual uint GetTypeDefTypeAttributes(MethodTableHandle methodTable) => throw new NotImplementedException(); - public virtual uint GetInstantiation(MethodTableHandle methodTable, out TargetPointer instantiation) => throw new NotImplementedException(); + public virtual ReadOnlySpan GetInstantiation(MethodTableHandle methodTable) => throw new NotImplementedException(); public virtual bool IsGenericTypeDefinition(MethodTableHandle methodTable) => throw new NotImplementedException(); #endregion MethodTable inspection APIs #region TypeHandle inspection APIs + public virtual TypeHandle TypeHandleFromAddress(TargetPointer address) => 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 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. 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 e6f98f0d8e2f0..d0c92864ad794 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, @@ -78,10 +85,12 @@ private bool TestFlagWithMask(WFLAGS2_ENUM mask, WFLAGS2_ENUM flag) public bool IsInterface => GetFlag(WFLAGS_HIGH.Category_Mask) == WFLAGS_HIGH.Category_Interface; public bool IsString => HasComponentSize && !IsArray && ComponentSizeBits == 2; public bool IsArray => GetFlag(WFLAGS_HIGH.Category_Array_Mask) == WFLAGS_HIGH.Category_Array; + public bool IfArrayThenSzArray => GetFlag(WFLAGS_HIGH.Category_IfArrayThenSzArray) != 0; public bool IsStringOrArray => HasComponentSize; public ushort ComponentSize => HasComponentSize ? ComponentSizeBits : (ushort)0; 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 da2ea6c8b01b3..4670b2535028b 100644 --- a/src/native/managed/cdacreader/src/Contracts/RuntimeTypeSystem_1.cs +++ b/src/native/managed/cdacreader/src/Contracts/RuntimeTypeSystem_1.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Reflection.Metadata.Ecma335; +using Microsoft.Diagnostics.DataContractReader.Data; namespace Microsoft.Diagnostics.DataContractReader.Contracts; @@ -27,6 +28,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 +42,7 @@ internal MethodTable(Data.MethodTable data) EEClassOrCanonMT = data.EEClassOrCanonMT; Module = data.Module; ParentMethodTable = data.ParentMethodTable; + PerInstInfo = data.PerInstInfo; } } @@ -154,4 +157,191 @@ public uint GetTypeDefToken(MethodTableHandle methodTableHandle) public bool IsDynamicStatics(MethodTableHandle methodTableHandle) => _methodTables[methodTableHandle.Address].Flags.IsDynamicStatics; + public ReadOnlySpan GetInstantiation(MethodTableHandle methodTableHandle) + { + MethodTable methodTable = _methodTables[methodTableHandle.Address]; + if (!methodTable.Flags.HasInstantiation) + return default; + + TargetPointer perInstInfo = methodTable.PerInstInfo; + var typeInfo = _target.GetTypeInfo(DataType.pointer); + uint? size = typeInfo.Size; + TargetPointer genericsDictInfo = _target.ReadPointer(perInstInfo - size!.Value); + + TargetPointer dictionaryPointer = _target.ReadPointer(perInstInfo); + + int numberOfGenericArgs = _target.ProcessedData.GetOrAdd(genericsDictInfo).NumTyPars; + MethodTableArray instantiation = _target.ProcessedData.GetOrAdd<(TargetPointer, int), MethodTableArray> + ((dictionaryPointer, numberOfGenericArgs)); + + return instantiation.Types.AsSpan(); + } + + public bool IsGenericTypeDefinition(MethodTableHandle methodTableHandle) => _methodTables[methodTableHandle.Address].Flags.IsGenericTypeDefinition; + + TypeHandle IRuntimeTypeSystem.TypeHandleFromAddress(TargetPointer address) + { + return TypeHandleFromAddress(address); + } + + private static TypeHandle TypeHandleFromAddress(TargetPointer address) + { + if (address == 0) + return default; + + if (((ulong)address & 2) == (ulong)2) + { + return new TypeHandle(new TypeDescHandle(address - 2)); + } + else + { + return new TypeHandle(new MethodTableHandle(address)); + } + } + + public bool HasTypeParam(TypeHandle typeHandle) + { + if (typeHandle.IsMethodTable) + { + MethodTable methodTable = _methodTables[typeHandle.AsMethodTable.Address]; + return methodTable.Flags.IsArray; + } + else if (typeHandle.IsTypeDesc) + { + var typeDesc = _target.ProcessedData.GetOrAdd(typeHandle.AsTypeDesc.Address); + 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.AsMethodTable.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.AsMethodTable).InternalCorElementType; + default: + return CorElementType.Class; + } + } + else if (typeHandle.IsTypeDesc) + { + var typeDesc = _target.ProcessedData.GetOrAdd(typeHandle.AsTypeDesc.Address); + return (CorElementType)(typeDesc.TypeAndFlags & 0xFF); + } + return default(CorElementType); + } + + // 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.AsMethodTable.Address]; + + switch (methodTable.Flags.GetFlag(WFLAGS_HIGH.Category_Mask)) + { + case WFLAGS_HIGH.Category_Array: + TargetPointer clsPtr = GetClassPointer(typeHandle.AsMethodTable); + 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.AsMethodTable.Address]; + if (!methodTable.Flags.IsArray) + throw new ArgumentException(nameof(typeHandle)); + + return TypeHandleFromAddress(methodTable.PerInstInfo); + } + else if (typeHandle.IsTypeDesc) + { + var typeDesc = _target.ProcessedData.GetOrAdd(typeHandle.AsTypeDesc.Address); + CorElementType elemType = (CorElementType)(typeDesc.TypeAndFlags & 0xFF); + switch (elemType) + { + case CorElementType.ValueType: + case CorElementType.Byref: + case CorElementType.Ptr: + ParamTypeDesc paramTypeDesc = _target.ProcessedData.GetOrAdd(typeHandle.AsTypeDesc.Address); + return TypeHandleFromAddress(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.AsTypeDesc.Address); + CorElementType elemType = (CorElementType)(typeDesc.TypeAndFlags & 0xFF); + switch (elemType) + { + case CorElementType.MVar: + case CorElementType.Var: + TypeVarTypeDesc typeVarTypeDesc = _target.ProcessedData.GetOrAdd(typeHandle.AsTypeDesc.Address); + 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; + + if (!typeHandle.IsTypeDesc) + return false; + + var typeDesc = _target.ProcessedData.GetOrAdd(typeHandle.AsTypeDesc.Address); + CorElementType elemType = (CorElementType)(typeDesc.TypeAndFlags & 0xFF); + if (elemType != CorElementType.FnPtr) + return false; + + FnPtrTypeDesc fnPtrTypeDesc = _target.ProcessedData.GetOrAdd(typeHandle.AsTypeDesc.Address); + + TypeHandleArray retAndArgTypesArray = _target.ProcessedData.GetOrAdd<(TargetPointer, int), TypeHandleArray> + ((fnPtrTypeDesc.RetAndArgTypes, checked((int)fnPtrTypeDesc.NumArgs + 1))); + retAndArgTypes = retAndArgTypesArray.Types; + callConv = (byte)fnPtrTypeDesc.CallConv; + return true; + } } diff --git a/src/native/managed/cdacreader/src/Data/EEClass.cs b/src/native/managed/cdacreader/src/Data/EEClass.cs index e697b3f40756c..726c6064d427b 100644 --- a/src/native/managed/cdacreader/src/Data/EEClass.cs +++ b/src/native/managed/cdacreader/src/Data/EEClass.cs @@ -15,9 +15,24 @@ 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; } + 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 0000000000000..e5b96de528701 --- /dev/null +++ b/src/native/managed/cdacreader/src/Data/GenericsDictInfo.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. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +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); + + NumDicts = target.Read(address + (ulong)type.Fields[nameof(NumDicts)].Offset); + NumTyPars = target.Read(address + (ulong)type.Fields[nameof(NumTyPars)].Offset); + } + + public ushort NumDicts { get; init; } + public ushort NumTyPars { get; init; } +} diff --git a/src/native/managed/cdacreader/src/Data/IData.cs b/src/native/managed/cdacreader/src/Data/IData.cs index 65b7be805c838..8ed7785728621 100644 --- a/src/native/managed/cdacreader/src/Data/IData.cs +++ b/src/native/managed/cdacreader/src/Data/IData.cs @@ -7,3 +7,8 @@ internal interface IData where TSelf : IData { static abstract TSelf Create(Target target, TargetPointer address); } + +internal interface IData where TSelf : IData +{ + static abstract TSelf Create(Target target, TKey address); +} diff --git a/src/native/managed/cdacreader/src/Data/MethodTable.cs b/src/native/managed/cdacreader/src/Data/MethodTable.cs index 3319c6547f056..b1b6723d55654 100644 --- a/src/native/managed/cdacreader/src/Data/MethodTable.cs +++ b/src/native/managed/cdacreader/src/Data/MethodTable.cs @@ -20,6 +20,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 +29,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/MethodTableArray.cs b/src/native/managed/cdacreader/src/Data/MethodTableArray.cs new file mode 100644 index 0000000000000..93396fb8fa635 --- /dev/null +++ b/src/native/managed/cdacreader/src/Data/MethodTableArray.cs @@ -0,0 +1,33 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using Microsoft.Diagnostics.DataContractReader.Contracts; + +namespace Microsoft.Diagnostics.DataContractReader.Data; + +internal sealed class MethodTableArray : IData +{ + public static MethodTableArray Create(Target target, (TargetPointer ptr, int size) address) + => new MethodTableArray(target, address); + + public readonly MethodTableHandle[] Types; + + public MethodTableArray(Target target, (TargetPointer ptr, int size) key) + { + Span targetPointerSpan = stackalloc TargetPointer[4]; + Types = new MethodTableHandle[key.size]; + Span instantiationSpan = Types.AsSpan(); + if (key.size > targetPointerSpan.Length) + { + targetPointerSpan = new TargetPointer[key.size]; + } + + target.ReadPointers(key.ptr, targetPointerSpan); + + for (int i = 0; i < key.size; i++) + { + instantiationSpan[i] = new MethodTableHandle(targetPointerSpan[i]); + } + } +} 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 0000000000000..5cef78482999d --- /dev/null +++ b/src/native/managed/cdacreader/src/Data/TypeDesc.cs @@ -0,0 +1,78 @@ +// 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.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/Data/TypeHandleArray.cs b/src/native/managed/cdacreader/src/Data/TypeHandleArray.cs new file mode 100644 index 0000000000000..f377132621436 --- /dev/null +++ b/src/native/managed/cdacreader/src/Data/TypeHandleArray.cs @@ -0,0 +1,33 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using Microsoft.Diagnostics.DataContractReader.Contracts; + +namespace Microsoft.Diagnostics.DataContractReader.Data; + +internal sealed class TypeHandleArray : IData +{ + public static TypeHandleArray Create(Target target, (TargetPointer ptr, int size) address) + => new TypeHandleArray(target, address); + + public readonly TypeHandle[] Types; + + public TypeHandleArray(Target target, (TargetPointer ptr, int size) key) + { + Span targetPointerSpan = stackalloc TargetPointer[4]; + Types = new TypeHandle[key.size]; + Span instantiationSpan = Types.AsSpan(); + if (key.size > targetPointerSpan.Length) + { + targetPointerSpan = new TargetPointer[key.size]; + } + + target.ReadPointers(key.ptr, targetPointerSpan); + + for (int i = 0; i < key.size; i++) + { + instantiationSpan[i] = target.Contracts.RuntimeTypeSystem.TypeHandleFromAddress(targetPointerSpan[i]); + } + } +} diff --git a/src/native/managed/cdacreader/src/DataType.cs b/src/native/managed/cdacreader/src/DataType.cs index 88e973c82ee14..cfcc7e93f362a 100644 --- a/src/native/managed/cdacreader/src/DataType.cs +++ b/src/native/managed/cdacreader/src/DataType.cs @@ -28,5 +28,11 @@ public enum DataType Module, MethodTable, EEClass, + ArrayClass, MethodTableAuxiliaryData, + GenericsDictInfo, + TypeDesc, + ParamTypeDesc, + TypeVarTypeDesc, + FnPtrTypeDesc, } 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 0000000000000..c6d5f2894fd65 --- /dev/null +++ b/src/native/managed/cdacreader/src/Helpers/EcmaMetadataReader.InternalHelpers.cs @@ -0,0 +1,109 @@ +// 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) + { + if (ColumnOffset(column) == 0) + throw new ArgumentOutOfRangeException(nameof(column)); + + 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 0000000000000..8f6fc03d79b8f --- /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.TwoByteConstant; + columnTypes[(int)MetadataColumnIndex.ImplMap_ImportScope] = ColumnType.Token; + + columnTypes[(int)MetadataColumnIndex.FieldRva_Rva] = ColumnType.TwoByteConstant; + 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 0000000000000..82157a35e7512 --- /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, IsSigned(), out value); + } + + private static T ReadLittleEndian(ReadOnlySpan bytes) where T : struct, IBinaryInteger, IMinMaxValue + { + if (!T.TryReadLittleEndian(bytes, IsSigned(), 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 0000000000000..2a7c87f47c2ab --- /dev/null +++ b/src/native/managed/cdacreader/src/Helpers/EcmaMetadataReader.cs @@ -0,0 +1,396 @@ +// 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), (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 + 1]; + 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(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 < tables.Length; 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 < tables.Length; i++) + { + checked + { + if ((validTables & ((ulong)1 << i)) != 0) + { + int tableSize = checked(rowSize![i] * _ecmaMetadata.Schema.RowCount[i]); + tableData[i] = TablesHeap.Slice(currentTablesOffset, tableSize); + currentTablesOffset += AlignUp(tableSize, 4); + } + } + } + + (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 + 1]; + + _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; + if (currentTable != ColumnTable(column)) + { + if (prevColumn.HasValue) + rowSize[(int)ColumnTable(prevColumn.Value)] = ComputeColumnEnd(prevColumn.Value); + 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; + } + } + } + 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/Legacy/TypeNameBuilder.cs b/src/native/managed/cdacreader/src/Legacy/TypeNameBuilder.cs index a37f2641eb319..28d0b1d805d22 100644 --- a/src/native/managed/cdacreader/src/Legacy/TypeNameBuilder.cs +++ b/src/native/managed/cdacreader/src/Legacy/TypeNameBuilder.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.Text; using Microsoft.Diagnostics.DataContractReader.Contracts; +using Microsoft.Diagnostics.DataContractReader.Helpers; namespace Microsoft.Diagnostics.DataContractReader.Legacy; @@ -88,12 +89,12 @@ private static void AppendTypeCore(ref TypeNameBuilder tnb, Contracts.TypeHandle else if (typeSystemContract.IsGenericVariable(typeHandle, out TargetPointer modulePointer, out uint genericParamToken)) { Contracts.ModuleHandle module = tnb.Target.Contracts.Loader.GetModuleHandle(modulePointer); - MetadataReader reader = tnb.Target.Contracts.Metadata.GetMetadataReader(module); - MetadataCursor cursor = reader.GetCursor(genericParamToken); + EcmaMetadataReader reader = tnb.Target.Contracts.Metadata.GetMetadata(module).EcmaMetadataReader; + EcmaMetadataCursor cursor = reader.GetCursor(genericParamToken); if (format.HasFlag(TypeNameFormat.FormatGenericParam)) { uint owner = reader.GetColumnAsToken(cursor, MetadataColumnIndex.GenericParam_Owner); - if (MetadataReader.TokenToTable(owner) == MetadataTable.TypeDef) + if (EcmaMetadataReader.TokenToTable(owner) == MetadataTable.TypeDef) { tnb.TypeString.Append('!'); } @@ -143,44 +144,27 @@ private static void AppendTypeCore(ref TypeNameBuilder tnb, Contracts.TypeHandle MethodTableHandle methodTable = typeHandle.AsMethodTable; uint typeDefToken = typeSystemContract.GetTypeDefToken(methodTable); Contracts.ModuleHandle moduleHandle = tnb.Target.Contracts.Loader.GetModuleHandle(typeSystemContract.GetModule(methodTable)); - if (MetadataReader.RidFromToken(typeDefToken) == 0) + if (EcmaMetadataReader.RidFromToken(typeDefToken) == 0) { tnb.AddName("(dynamicClass)"); } else { - MetadataReader reader = tnb.Target.Contracts.Metadata.GetMetadataReader(moduleHandle); + EcmaMetadataReader reader = tnb.Target.Contracts.Metadata.GetMetadata(moduleHandle).EcmaMetadataReader; AppendNestedTypeDef(ref tnb, reader, typeDefToken, format); } // Append instantiation - if (format.HasFlag(TypeNameFormat.FormatNamespace) || format.HasFlag(TypeNameFormat.FormatAssembly)) { - TargetPointer instantiationPointer; - uint instantiationLength = typeSystemContract.GetInstantiation(methodTable, out instantiationPointer); + ReadOnlySpan instantiationSpan = typeSystemContract.GetInstantiation(methodTable); - if ((instantiationLength > 0) && (!typeSystemContract.IsGenericTypeDefinition(methodTable) || toString)) + if ((instantiationSpan.Length > 0) && (!typeSystemContract.IsGenericTypeDefinition(methodTable) || toString)) { if (instantiation.Length == 0) { - Span targetPointerSpan = stackalloc TargetPointer[4]; - Span instantiationSpan = stackalloc MethodTableHandle[4]; - if (instantiationLength > targetPointerSpan.Length) - { - targetPointerSpan = new TargetPointer[instantiationLength]; - instantiationSpan = new MethodTableHandle[instantiationLength]; - } - targetPointerSpan = targetPointerSpan.Slice(0, (int)instantiationLength); - instantiationSpan = instantiationSpan.Slice(0, (int)instantiationLength); - - tnb.Target.ReadPointers(instantiationPointer, targetPointerSpan); - - for (int i = 0; i < instantiationLength; i++) - { - instantiationSpan[i] = new MethodTableHandle(targetPointerSpan[i]); - } + instantiation = instantiationSpan; } AppendInst(ref tnb, instantiation, format); } @@ -192,8 +176,8 @@ private static void AppendTypeCore(ref TypeNameBuilder tnb, Contracts.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 - MetadataReader mr = tnb.Target.Contracts.Metadata.GetMetadataReader(module); - MetadataCursor cursor = mr.GetCursor(0x20000001); + EcmaMetadataReader mr = tnb.Target.Contracts.Metadata.GetMetadata(module).EcmaMetadataReader; + EcmaMetadataCursor cursor = mr.GetCursor(0x20000001); string assemblySimpleName = mr.GetColumnAsUtf8String(cursor, MetadataColumnIndex.Assembly_Name); tnb.AddAssemblySpec(assemblySimpleName); @@ -344,16 +328,16 @@ private void AddAssemblySpec(string? assemblySpec) } } - private static void AppendNestedTypeDef(ref TypeNameBuilder tnb, MetadataReader reader, uint typeDefToken, TypeNameFormat format) + private static void AppendNestedTypeDef(ref TypeNameBuilder tnb, EcmaMetadataReader reader, uint typeDefToken, TypeNameFormat format) { - MetadataCursor cursor = reader.GetCursor(typeDefToken); + 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(); - MetadataCursor nestedTypesCursor = reader.GetCursor(MetadataReader.CreateToken(MetadataTable.NestedClass, 1)); - while (reader.TryFindRowFromCursor(nestedTypesCursor, MetadataColumnIndex.NestedClass_NestedClass, currentTypeDefToken, out MetadataCursor foundNestedClassRecord)) + 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); @@ -367,9 +351,9 @@ private static void AppendNestedTypeDef(ref TypeNameBuilder tnb, MetadataReader AppendTypeDef(ref tnb, reader, typeDefToken, format); } - private static void AppendTypeDef(ref TypeNameBuilder tnb, MetadataReader reader, uint typeDefToken, TypeNameFormat format) + private static void AppendTypeDef(ref TypeNameBuilder tnb, EcmaMetadataReader reader, uint typeDefToken, TypeNameFormat format) { - MetadataCursor cursor = reader.GetCursor(typeDefToken); + EcmaMetadataCursor cursor = reader.GetCursor(typeDefToken); string? typeNamespace = null; if (format.HasFlag(TypeNameFormat.FormatNamespace)) { diff --git a/src/native/managed/cdacreader/src/Target.cs b/src/native/managed/cdacreader/src/Target.cs index f2745a5e26e8e..ae2202b84d380 100644 --- a/src/native/managed/cdacreader/src/Target.cs +++ b/src/native/managed/cdacreader/src/Target.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; +using System.Net; using System.Numerics; using System.Runtime.CompilerServices; using Microsoft.Diagnostics.DataContractReader.Data; @@ -36,6 +37,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 /// @@ -406,6 +419,7 @@ internal sealed class DataCache { private readonly Target _target; private readonly Dictionary<(ulong, Type), object?> _readDataByAddress = []; + private readonly Dictionary _customKeyReadDataByAddress = []; public DataCache(Target target) { @@ -426,6 +440,22 @@ public T GetOrAdd(TargetPointer address) where T : IData return result!; } + public TValue GetOrAdd(TKey key) where TValue : IData where TKey : notnull, IEquatable + { + if (TryGet(key, out TValue? result)) + return result!; + + Dictionary<(TKey, Type), object?> dictionary = (Dictionary<(TKey, Type), object?>)_customKeyReadDataByAddress[typeof(TKey)]; + + TValue constructed = TValue.Create(_target, key); + if (dictionary.TryAdd((key, typeof(TValue)), constructed)) + return constructed; + + bool found = TryGet(key, out result); + Debug.Assert(found); + return result!; + } + public bool TryGet(ulong address, [NotNullWhen(true)] out T? data) { data = default; @@ -440,6 +470,29 @@ public bool TryGet(ulong address, [NotNullWhen(true)] out T? data) return false; } + + public bool TryGet(TKey key, [NotNullWhen(true)] out TValue? data) where TValue : IData where TKey : notnull, IEquatable + { + data = default; + + if (!_customKeyReadDataByAddress.TryGetValue(typeof(TKey), out var dictionaryObject)) + { + dictionaryObject = new Dictionary<(TKey, Type), TValue>(); + _customKeyReadDataByAddress.Add(typeof(TKey), dictionaryObject); + } + Dictionary<(TKey, Type), object?> dictionary = (Dictionary<(TKey, Type), object?>)dictionaryObject; + + if (!dictionary.TryGetValue((key, typeof(TValue)), out object? dataObj)) + return false; + + if (dataObj is TValue dataMaybe) + { + data = dataMaybe; + return true; + } + + return false; + } } private sealed class Reader From 636bb2b27133ad139d9b1a4650e061c6d6cd9f21 Mon Sep 17 00:00:00 2001 From: David Wrighton Date: Thu, 11 Jul 2024 09:34:06 -0700 Subject: [PATCH 03/15] Simplify storage of saved metadata copy for reflection emit scenarios - Move pointer to Module class - Replace use of SBuffer abstraction with a simple counted byte memory block Add metadata details to Loader contract Add Metadata helper api for use by contracts within the cDAC (and possibly clients of cDAC too) --- src/coreclr/debug/daccess/daccess.cpp | 6 +- src/coreclr/debug/daccess/dacdbiimpl.cpp | 5 +- src/coreclr/debug/daccess/request.cpp | 4 +- .../debug/runtimeinfo/datadescriptor.h | 10 +- src/coreclr/vm/ceeload.cpp | 15 ++- src/coreclr/vm/ceeload.h | 17 ++- src/coreclr/vm/methodtable.h | 3 +- .../cdacreader/src/Contracts/Loader.cs | 76 +++++++++++ .../cdacreader/src/Contracts/Loader_1.cs | 26 ++++ .../cdacreader/src/Contracts/Metadata_1.cs | 23 ---- .../cdacreader/src/Contracts/Registry.cs | 1 - .../src/Contracts/RuntimeTypeSystem_1.cs | 2 +- .../cdacreader/src/Data/GenericsDictInfo.cs | 6 +- .../managed/cdacreader/src/Data/Module.cs | 2 + .../src/{Contracts => Helpers}/Metadata.cs | 125 ++++++++++-------- .../cdacreader/src/Legacy/TypeNameBuilder.cs | 6 +- src/native/managed/cdacreader/src/Target.cs | 2 + 17 files changed, 218 insertions(+), 111 deletions(-) delete mode 100644 src/native/managed/cdacreader/src/Contracts/Metadata_1.cs rename src/native/managed/cdacreader/src/{Contracts => Helpers}/Metadata.cs (63%) diff --git a/src/coreclr/debug/daccess/daccess.cpp b/src/coreclr/debug/daccess/daccess.cpp index 6dd0f52fa2e55..7666e634a19a7 100644 --- a/src/coreclr/debug/daccess/daccess.cpp +++ b/src/coreclr/debug/daccess/daccess.cpp @@ -6713,11 +6713,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 + sizeof(uint32_t)); + mdSize = *dac_cast(metadataBuffer); } else { diff --git a/src/coreclr/debug/daccess/dacdbiimpl.cpp b/src/coreclr/debug/daccess/dacdbiimpl.cpp index a6dda59127855..5e408eab3d73a 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 + sizeof(TADDR)); + pTargetBuffer->Init(addr, *dac_cast(metadataBuffer)); } else { diff --git a/src/coreclr/debug/daccess/request.cpp b/src/coreclr/debug/daccess/request.cpp index 9c779136fa3c5..324c279f05313 100644 --- a/src/coreclr/debug/daccess/request.cpp +++ b/src/coreclr/debug/daccess/request.cpp @@ -1935,7 +1935,7 @@ ClrDataAccess::GetMethodTableName(CLRDATA_ADDRESS mt, unsigned int count, _Inout if (mtName != NULL) { - _ASSERTE(0 == wcsncmp(pNeeded, (WCHAR *)pwszNameLocal, count)); + _ASSERTE(0 == wcsncmp(mtName, (WCHAR *)pwszNameLocal, count)); } if (pNeeded != NULL) { @@ -1950,11 +1950,13 @@ ClrDataAccess::GetMethodTableName(CLRDATA_ADDRESS mt, unsigned int count, _Inout } 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; diff --git a/src/coreclr/debug/runtimeinfo/datadescriptor.h b/src/coreclr/debug/runtimeinfo/datadescriptor.h index 248ff7822a1ed..2b7887606a6ca 100644 --- a/src/coreclr/debug/runtimeinfo/datadescriptor.h +++ b/src/coreclr/debug/runtimeinfo/datadescriptor.h @@ -165,6 +165,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,11 +203,10 @@ CDAC_TYPE_INDETERMINATE(ArrayClass) CDAC_TYPE_FIELD(ArrayClass, /*uint8*/, Rank, cdac_offsets::Rank) CDAC_TYPE_END(ArrayClass) -CDAC_TYPE_BEGIN(GenericDictInfo) -CDAC_TYPE_INDETERMINATE(GenericDictInfo) -CDAC_TYPE_FIELD(GenericDictInfo, /*uint16*/, NumDicts, cdac_offsets::NumDicts) -CDAC_TYPE_FIELD(GenericDictInfo, /*uint16*/, NumTyPars, cdac_offsets::NumTyPars) -CDAC_TYPE_END(GenericDictInfo) +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) diff --git a/src/coreclr/vm/ceeload.cpp b/src/coreclr/vm/ceeload.cpp index eb49ed746edc2..0c6df0e1d3c26 100644 --- a/src/coreclr/vm/ceeload.cpp +++ b/src/coreclr/vm/ceeload.cpp @@ -3806,7 +3806,7 @@ void ReflectionModule::Destruct() Module::Destruct(); - delete m_pDynamicMetadata; + delete (uint32_t*)m_pDynamicMetadata; m_pDynamicMetadata = NULL; m_CrstLeafLock.Destroy(); @@ -3923,7 +3923,8 @@ 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()); + int sizeInInts = (numBytes / sizeof(int32_t)) + 2; + NewArrayHolder pBuffer(new uint32_t[sizeInInts]); _ASSERTE(pBuffer != NULL); // allocation would throw first // ReflectionModule is still in a consistent state, and now we're just operating on local data to @@ -3931,9 +3932,9 @@ void ReflectionModule::CaptureModuleMetaDataToMemory() // recently generated classes. // Caller ensures serialization that guarantees that the metadata doesn't grow underneath us. - BYTE * pRawData = pBuffer->OpenRawBuffer(numBytes); + BYTE * pRawData = (BYTE*)&pBuffer[1]; hr = pEmitter->SaveToMemory(pRawData, numBytes); - pBuffer->CloseRawBuffer(); + pBuffer[0] = numBytes; IfFailThrow(hr); @@ -3941,9 +3942,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 +3966,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 cb370bae893e0..5fbbfd2307d63 100644 --- a/src/coreclr/vm/ceeload.h +++ b/src/coreclr/vm/ceeload.h @@ -1588,6 +1588,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 +1626,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 +1657,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 +1665,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/methodtable.h b/src/coreclr/vm/methodtable.h index 437d7ba8cfe2d..b1ce54ab27464 100644 --- a/src/coreclr/vm/methodtable.h +++ b/src/coreclr/vm/methodtable.h @@ -300,8 +300,7 @@ typedef DPTR(GenericsDictInfo) PTR_GenericsDictInfo; template<> struct cdac_offsets { - static constexpr size_t NumDicts = offsetof(GenericsDictInfo, m_wNumDicts); - static constexpr size_t NumTyPars = offsetof(GenericsDictInfo, m_wNumTyPars); + static constexpr size_t NumTypeArgs = offsetof(GenericsDictInfo, m_wNumTyPars); }; // These various statics structures exist directly before the MethodTableAuxiliaryData diff --git a/src/native/managed/cdacreader/src/Contracts/Loader.cs b/src/native/managed/cdacreader/src/Contracts/Loader.cs index e30633fdf2a25..508fd8e56ad83 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 533dbc467d34e..531633f314153 100644 --- a/src/native/managed/cdacreader/src/Contracts/Loader_1.cs +++ b/src/native/managed/cdacreader/src/Contracts/Loader_1.cs @@ -59,6 +59,32 @@ 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); + TargetPointer result = module.DynamicMetadata + (ulong)_target.GetTypeInfo(DataType.pointer)!.Size!.Value; + size = _target.Read(module.DynamicMetadata); + 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/Metadata_1.cs b/src/native/managed/cdacreader/src/Contracts/Metadata_1.cs deleted file mode 100644 index 89d2a462746fe..0000000000000 --- a/src/native/managed/cdacreader/src/Contracts/Metadata_1.cs +++ /dev/null @@ -1,23 +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.Diagnostics; -using System.Collections.Generic; -using System.Numerics; -using System.Reflection.Metadata; -using System.Reflection.Metadata.Ecma335; -using System.Runtime.CompilerServices; -using System.Reflection; - -namespace Microsoft.Diagnostics.DataContractReader.Contracts; - -internal readonly struct Metadata_1 : IMetadata -{ - private readonly Target _target; - - internal Metadata_1(Target target) - { - _target = target; - } -} diff --git a/src/native/managed/cdacreader/src/Contracts/Registry.cs b/src/native/managed/cdacreader/src/Contracts/Registry.cs index fef95df1364f1..21f46e481ecd9 100644 --- a/src/native/managed/cdacreader/src/Contracts/Registry.cs +++ b/src/native/managed/cdacreader/src/Contracts/Registry.cs @@ -22,7 +22,6 @@ public Registry(Target target) public ILoader Loader => GetContract(); public IThread Thread => GetContract(); public IRuntimeTypeSystem RuntimeTypeSystem => GetContract(); - public IMetadata Metadata => GetContract(); private T GetContract() where T : IContract { diff --git a/src/native/managed/cdacreader/src/Contracts/RuntimeTypeSystem_1.cs b/src/native/managed/cdacreader/src/Contracts/RuntimeTypeSystem_1.cs index 4670b2535028b..da11b5f9d4cee 100644 --- a/src/native/managed/cdacreader/src/Contracts/RuntimeTypeSystem_1.cs +++ b/src/native/managed/cdacreader/src/Contracts/RuntimeTypeSystem_1.cs @@ -170,7 +170,7 @@ public ReadOnlySpan GetInstantiation(MethodTableHandle method TargetPointer dictionaryPointer = _target.ReadPointer(perInstInfo); - int numberOfGenericArgs = _target.ProcessedData.GetOrAdd(genericsDictInfo).NumTyPars; + int numberOfGenericArgs = _target.ProcessedData.GetOrAdd(genericsDictInfo).NumTypeArgs; MethodTableArray instantiation = _target.ProcessedData.GetOrAdd<(TargetPointer, int), MethodTableArray> ((dictionaryPointer, numberOfGenericArgs)); diff --git a/src/native/managed/cdacreader/src/Data/GenericsDictInfo.cs b/src/native/managed/cdacreader/src/Data/GenericsDictInfo.cs index e5b96de528701..a25f4a87c8471 100644 --- a/src/native/managed/cdacreader/src/Data/GenericsDictInfo.cs +++ b/src/native/managed/cdacreader/src/Data/GenericsDictInfo.cs @@ -16,10 +16,8 @@ public GenericsDictInfo(Target target, TargetPointer address) { Target.TypeInfo type = target.GetTypeInfo(DataType.GenericsDictInfo); - NumDicts = target.Read(address + (ulong)type.Fields[nameof(NumDicts)].Offset); - NumTyPars = target.Read(address + (ulong)type.Fields[nameof(NumTyPars)].Offset); + NumTypeArgs = target.Read(address + (ulong)type.Fields[nameof(NumTypeArgs)].Offset); } - public ushort NumDicts { get; init; } - public ushort NumTyPars { get; init; } + public ushort NumTypeArgs { get; init; } } diff --git a/src/native/managed/cdacreader/src/Data/Module.cs b/src/native/managed/cdacreader/src/Data/Module.cs index e462a201241ce..eb8746ea64c8e 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/Contracts/Metadata.cs b/src/native/managed/cdacreader/src/Helpers/Metadata.cs similarity index 63% rename from src/native/managed/cdacreader/src/Contracts/Metadata.cs rename to src/native/managed/cdacreader/src/Helpers/Metadata.cs index 4d3db918ee8f5..b3c46933000bc 100644 --- a/src/native/managed/cdacreader/src/Contracts/Metadata.cs +++ b/src/native/managed/cdacreader/src/Helpers/Metadata.cs @@ -2,8 +2,16 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; +using System.Collections.Generic; +using System.Drawing; +using System.Linq; +using System.Net; +using System.Text; +using System.Threading.Tasks; +using Microsoft.Diagnostics.DataContractReader.Contracts; +using static System.Net.Mime.MediaTypeNames; -namespace Microsoft.Diagnostics.DataContractReader.Contracts; +namespace Microsoft.Diagnostics.DataContractReader.Helpers; internal enum MetadataTable { @@ -56,39 +64,6 @@ internal enum MetadataTable Count = 0x2c } -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 EcmaMetadata { public EcmaMetadata(EcmaMetadataSchema schema, @@ -115,7 +90,6 @@ public EcmaMetadata(EcmaMetadataSchema schema, public ReadOnlyMemory BlobHeap { get; init; } public ReadOnlyMemory GuidHeap { get; init; } - // This isn't technically part of the contract, but it is here to reduce the complexity of using this contract private Microsoft.Diagnostics.DataContractReader.Helpers.EcmaMetadataReader? _ecmaMetadataReader; public Microsoft.Diagnostics.DataContractReader.Helpers.EcmaMetadataReader EcmaMetadataReader { @@ -293,31 +267,74 @@ internal enum MetadataColumnIndex Count } -internal interface IMetadata : IContract +internal class Metadata { - static string IContract.Name => nameof(Metadata); - static IContract IContract.Create(Target target, int version) + private readonly Target _target; + private readonly Dictionary _metadata = []; + + public Metadata(Target target) { - return version switch - { - _ => default(Metadata), - }; + _target = target; } - public virtual EcmaMetadata GetMetadata(ModuleHandle module) => throw new NotImplementedException(); + public virtual EcmaMetadata GetMetadata(Contracts.ModuleHandle module) + { + if (_metadata.TryGetValue(module.Address, out EcmaMetadata? result)) + return result; - // Allow users to provide metadata from outside the contract system. Used to enable supporting scenarios where the metadata is not in a - // dump file, or the contract api user wishes to provide a memory mapped metadata instead of reading it from the target process. - public virtual void RegisterMetadataProvider(Func provider) => throw new NotImplementedException(); + AvailableMetadataType metadataType = _target.Contracts.Loader.GetAvailableMetadataType(module); - // Helper api intended for users of RegisterMetadataProvider, not officially part of the documented contract, but placed here in the code for greater visibility - public EcmaMetadata ProduceEcmaMetadataFromMemory(ReadOnlyMemory image) - { - return (new Helpers.EcmaMetadataReader(image)).UnderlyingMetadata; + 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; } -} -internal readonly struct Metadata : IMetadata -{ - // Everything throws NotImplementedException + public Func? MetadataProvider; } diff --git a/src/native/managed/cdacreader/src/Legacy/TypeNameBuilder.cs b/src/native/managed/cdacreader/src/Legacy/TypeNameBuilder.cs index 28d0b1d805d22..e093ae1c59320 100644 --- a/src/native/managed/cdacreader/src/Legacy/TypeNameBuilder.cs +++ b/src/native/managed/cdacreader/src/Legacy/TypeNameBuilder.cs @@ -89,7 +89,7 @@ private static void AppendTypeCore(ref TypeNameBuilder tnb, Contracts.TypeHandle else if (typeSystemContract.IsGenericVariable(typeHandle, out TargetPointer modulePointer, out uint genericParamToken)) { Contracts.ModuleHandle module = tnb.Target.Contracts.Loader.GetModuleHandle(modulePointer); - EcmaMetadataReader reader = tnb.Target.Contracts.Metadata.GetMetadata(module).EcmaMetadataReader; + EcmaMetadataReader reader = tnb.Target.Metadata.GetMetadata(module).EcmaMetadataReader; EcmaMetadataCursor cursor = reader.GetCursor(genericParamToken); if (format.HasFlag(TypeNameFormat.FormatGenericParam)) { @@ -150,7 +150,7 @@ private static void AppendTypeCore(ref TypeNameBuilder tnb, Contracts.TypeHandle } else { - EcmaMetadataReader reader = tnb.Target.Contracts.Metadata.GetMetadata(moduleHandle).EcmaMetadataReader; + EcmaMetadataReader reader = tnb.Target.Metadata.GetMetadata(moduleHandle).EcmaMetadataReader; AppendNestedTypeDef(ref tnb, reader, typeDefToken, format); } @@ -176,7 +176,7 @@ private static void AppendTypeCore(ref TypeNameBuilder tnb, Contracts.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.Contracts.Metadata.GetMetadata(module).EcmaMetadataReader; + EcmaMetadataReader mr = tnb.Target.Metadata.GetMetadata(module).EcmaMetadataReader; EcmaMetadataCursor cursor = mr.GetCursor(0x20000001); string assemblySimpleName = mr.GetColumnAsUtf8String(cursor, MetadataColumnIndex.Assembly_Name); diff --git a/src/native/managed/cdacreader/src/Target.cs b/src/native/managed/cdacreader/src/Target.cs index ae2202b84d380..da5d57334dc88 100644 --- a/src/native/managed/cdacreader/src/Target.cs +++ b/src/native/managed/cdacreader/src/Target.cs @@ -93,6 +93,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) { @@ -111,6 +112,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; From d359ed9d05bd4066ae88b5c95b429f69ef0599f7 Mon Sep 17 00:00:00 2001 From: David Wrighton Date: Thu, 11 Jul 2024 13:34:32 -0700 Subject: [PATCH 04/15] Fix bugs found when trying to actually use this logic 1. It works now! --- src/coreclr/vm/ceeload.cpp | 2 +- .../cdacreader/src/Contracts/Loader_1.cs | 2 +- .../src/Contracts/RuntimeTypeSystem_1.cs | 13 ++------- .../cdacreader/src/Data/MethodTableArray.cs | 2 +- .../EcmaMetadataReader.InternalHelpers.cs | 3 -- .../Helpers/EcmaMetadataReader.StaticData.cs | 4 +-- .../EcmaMetadataReader.StaticHelpers.cs | 4 +-- .../src/Helpers/EcmaMetadataReader.cs | 21 ++++++++------ .../cdacreader/src/Helpers/Metadata.cs | 2 +- .../cdacreader/src/Legacy/TypeNameBuilder.cs | 6 ++++ src/native/managed/cdacreader/src/Target.cs | 28 ++++++------------- 11 files changed, 38 insertions(+), 49 deletions(-) diff --git a/src/coreclr/vm/ceeload.cpp b/src/coreclr/vm/ceeload.cpp index 0c6df0e1d3c26..0efb59cc6da9e 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 = 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); diff --git a/src/native/managed/cdacreader/src/Contracts/Loader_1.cs b/src/native/managed/cdacreader/src/Contracts/Loader_1.cs index 531633f314153..c5d096674462c 100644 --- a/src/native/managed/cdacreader/src/Contracts/Loader_1.cs +++ b/src/native/managed/cdacreader/src/Contracts/Loader_1.cs @@ -77,7 +77,7 @@ AvailableMetadataType ILoader.GetAvailableMetadataType(ModuleHandle handle) TargetPointer ILoader.GetReadWriteSavedMetadataAddress(ModuleHandle handle, out ulong size) { Data.Module module = _target.ProcessedData.GetOrAdd(handle.Address); - TargetPointer result = module.DynamicMetadata + (ulong)_target.GetTypeInfo(DataType.pointer)!.Size!.Value; + TargetPointer result = module.DynamicMetadata + (ulong)_target.PointerSize; size = _target.Read(module.DynamicMetadata); return result; } diff --git a/src/native/managed/cdacreader/src/Contracts/RuntimeTypeSystem_1.cs b/src/native/managed/cdacreader/src/Contracts/RuntimeTypeSystem_1.cs index da11b5f9d4cee..aa5bb80978ed7 100644 --- a/src/native/managed/cdacreader/src/Contracts/RuntimeTypeSystem_1.cs +++ b/src/native/managed/cdacreader/src/Contracts/RuntimeTypeSystem_1.cs @@ -164,9 +164,7 @@ public ReadOnlySpan GetInstantiation(MethodTableHandle method return default; TargetPointer perInstInfo = methodTable.PerInstInfo; - var typeInfo = _target.GetTypeInfo(DataType.pointer); - uint? size = typeInfo.Size; - TargetPointer genericsDictInfo = _target.ReadPointer(perInstInfo - size!.Value); + TargetPointer genericsDictInfo = perInstInfo - (ulong)_target.PointerSize; TargetPointer dictionaryPointer = _target.ReadPointer(perInstInfo); @@ -179,12 +177,7 @@ public ReadOnlySpan GetInstantiation(MethodTableHandle method public bool IsGenericTypeDefinition(MethodTableHandle methodTableHandle) => _methodTables[methodTableHandle.Address].Flags.IsGenericTypeDefinition; - TypeHandle IRuntimeTypeSystem.TypeHandleFromAddress(TargetPointer address) - { - return TypeHandleFromAddress(address); - } - - private static TypeHandle TypeHandleFromAddress(TargetPointer address) + public TypeHandle TypeHandleFromAddress(TargetPointer address) { if (address == 0) return default; @@ -195,7 +188,7 @@ private static TypeHandle TypeHandleFromAddress(TargetPointer address) } else { - return new TypeHandle(new MethodTableHandle(address)); + return new TypeHandle(GetMethodTableHandle(address)); } } diff --git a/src/native/managed/cdacreader/src/Data/MethodTableArray.cs b/src/native/managed/cdacreader/src/Data/MethodTableArray.cs index 93396fb8fa635..e3b34fce83b63 100644 --- a/src/native/managed/cdacreader/src/Data/MethodTableArray.cs +++ b/src/native/managed/cdacreader/src/Data/MethodTableArray.cs @@ -27,7 +27,7 @@ public MethodTableArray(Target target, (TargetPointer ptr, int size) key) for (int i = 0; i < key.size; i++) { - instantiationSpan[i] = new MethodTableHandle(targetPointerSpan[i]); + instantiationSpan[i] = target.Contracts.RuntimeTypeSystem.GetMethodTableHandle(targetPointerSpan[i]); } } } diff --git a/src/native/managed/cdacreader/src/Helpers/EcmaMetadataReader.InternalHelpers.cs b/src/native/managed/cdacreader/src/Helpers/EcmaMetadataReader.InternalHelpers.cs index c6d5f2894fd65..1fe072ce1e926 100644 --- a/src/native/managed/cdacreader/src/Helpers/EcmaMetadataReader.InternalHelpers.cs +++ b/src/native/managed/cdacreader/src/Helpers/EcmaMetadataReader.InternalHelpers.cs @@ -32,9 +32,6 @@ private int RowCount(MetadataTable table) private bool TryReadTableEntry(ReadOnlySpan bytes, MetadataColumnIndex column, out uint value) { - if (ColumnOffset(column) == 0) - throw new ArgumentOutOfRangeException(nameof(column)); - int size = ColumnSize(column); ReadOnlySpan singleColumn = bytes.Slice(ColumnOffset(column), size); if (size == 2) diff --git a/src/native/managed/cdacreader/src/Helpers/EcmaMetadataReader.StaticData.cs b/src/native/managed/cdacreader/src/Helpers/EcmaMetadataReader.StaticData.cs index 8f6fc03d79b8f..851a10d0f7834 100644 --- a/src/native/managed/cdacreader/src/Helpers/EcmaMetadataReader.StaticData.cs +++ b/src/native/managed/cdacreader/src/Helpers/EcmaMetadataReader.StaticData.cs @@ -147,10 +147,10 @@ private static ColumnType[] GetColumnTypes() columnTypes[(int)MetadataColumnIndex.ImplMap_MappingFlags] = ColumnType.TwoByteConstant; columnTypes[(int)MetadataColumnIndex.ImplMap_MemberForwarded] = ColumnType.Token; - columnTypes[(int)MetadataColumnIndex.ImplMap_ImportName] = ColumnType.TwoByteConstant; + columnTypes[(int)MetadataColumnIndex.ImplMap_ImportName] = ColumnType.Utf8String; columnTypes[(int)MetadataColumnIndex.ImplMap_ImportScope] = ColumnType.Token; - columnTypes[(int)MetadataColumnIndex.FieldRva_Rva] = ColumnType.TwoByteConstant; + columnTypes[(int)MetadataColumnIndex.FieldRva_Rva] = ColumnType.FourByteConstant; columnTypes[(int)MetadataColumnIndex.FieldRva_Field] = ColumnType.Token; columnTypes[(int)MetadataColumnIndex.ENCLog_Token] = ColumnType.FourByteConstant; diff --git a/src/native/managed/cdacreader/src/Helpers/EcmaMetadataReader.StaticHelpers.cs b/src/native/managed/cdacreader/src/Helpers/EcmaMetadataReader.StaticHelpers.cs index 82157a35e7512..07c8ef21fea9a 100644 --- a/src/native/managed/cdacreader/src/Helpers/EcmaMetadataReader.StaticHelpers.cs +++ b/src/native/managed/cdacreader/src/Helpers/EcmaMetadataReader.StaticHelpers.cs @@ -35,12 +35,12 @@ private static bool IsSigned() where T : struct, INumberBase, IMinMaxValue } private static bool TryReadCore(ReadOnlySpan bytes, out T value) where T : struct, IBinaryInteger, IMinMaxValue { - return T.TryReadLittleEndian(bytes, IsSigned(), out value); + return T.TryReadLittleEndian(bytes.Slice(0, Unsafe.SizeOf()), IsSigned(), out value); } private static T ReadLittleEndian(ReadOnlySpan bytes) where T : struct, IBinaryInteger, IMinMaxValue { - if (!T.TryReadLittleEndian(bytes, IsSigned(), out T value)) + if (!TryReadCore(bytes, out T value)) throw new ArgumentOutOfRangeException(nameof(value)); return value; } diff --git a/src/native/managed/cdacreader/src/Helpers/EcmaMetadataReader.cs b/src/native/managed/cdacreader/src/Helpers/EcmaMetadataReader.cs index 2a7c87f47c2ab..d358ccc15d524 100644 --- a/src/native/managed/cdacreader/src/Helpers/EcmaMetadataReader.cs +++ b/src/native/managed/cdacreader/src/Helpers/EcmaMetadataReader.cs @@ -26,7 +26,7 @@ public ReadOnlySpan Row { get { - return TableData.Span.Slice((int)(RowSize * Rid), (int)RowSize); + return TableData.Span.Slice((int)(RowSize * (Rid - 1)), (int)RowSize); } } } @@ -44,7 +44,7 @@ public EcmaMetadataReader(ReadOnlyMemory imageMemory) { columnSize = new int[(int)MetadataColumnIndex.Count]; columnOffset = new int[(int)MetadataColumnIndex.Count]; - rowSize = new int[(int)MetadataTable.Count + 1]; + rowSize = new int[(int)MetadataTable.Count]; columnTokenDecode = Array.Empty>(); ReadOnlySpan image = imageMemory.Span; @@ -64,7 +64,7 @@ public EcmaMetadataReader(ReadOnlyMemory imageMemory) throw new ArgumentException(nameof(imageMemory)); } - string metadataVersion = Encoding.UTF8.GetString(versionName.Slice(nullTerminatorIndex)); + string metadataVersion = Encoding.UTF8.GetString(versionName.Slice(0, nullTerminatorIndex)); int currentOffset = 16 + versionSize; @@ -120,7 +120,7 @@ public EcmaMetadataReader(ReadOnlyMemory imageMemory) int[] tableRowCounts = new int[(int)MetadataTable.Count]; bool[] isSorted = new bool[(int)MetadataTable.Count]; int currentTablesOffset = 24; - for (int i = 0; i < tables.Length; i++) + for (int i = 0; i < (int)MetadataTable.Count; i++) { if ((validTables & ((ulong)1 << i)) != 0) { @@ -156,7 +156,7 @@ public EcmaMetadataReader(ReadOnlyMemory imageMemory) // Init will compute row sizes, which is necessary for actually computing the tableData - for (int i = 0; i < tables.Length; i++) + for (int i = 0; i < (int)MetadataTable.Count; i++) { checked { @@ -164,7 +164,7 @@ public EcmaMetadataReader(ReadOnlyMemory imageMemory) { int tableSize = checked(rowSize![i] * _ecmaMetadata.Schema.RowCount[i]); tableData[i] = TablesHeap.Slice(currentTablesOffset, tableSize); - currentTablesOffset += AlignUp(tableSize, 4); + currentTablesOffset += tableSize; } } } @@ -200,7 +200,7 @@ 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 + 1]; + rowSize = new int[(int)MetadataTable.Count]; _ecmaMetadata = ecmaMetadata; Init(); @@ -232,10 +232,12 @@ void ComputeColumnSizesAndOffsets() for (int i = 0; i < (int)MetadataColumnIndex.Count; i++) { MetadataColumnIndex column = (MetadataColumnIndex)i; - if (currentTable != ColumnTable(column)) + MetadataTable newColumnTable = ColumnTable(column); + if (currentTable != newColumnTable) { if (prevColumn.HasValue) - rowSize[(int)ColumnTable(prevColumn.Value)] = ComputeColumnEnd(prevColumn.Value); + rowSize[(int)currentTable] = ComputeColumnEnd(prevColumn.Value); + currentTable = newColumnTable; columnOffset[i] = 0; } else @@ -359,6 +361,7 @@ public bool TryFindRowFromCursor(EcmaMetadataCursor tableCursor, MetadataColumnI { return true; } + foundRow.Rid += 1; } } return false; diff --git a/src/native/managed/cdacreader/src/Helpers/Metadata.cs b/src/native/managed/cdacreader/src/Helpers/Metadata.cs index b3c46933000bc..f861794529346 100644 --- a/src/native/managed/cdacreader/src/Helpers/Metadata.cs +++ b/src/native/managed/cdacreader/src/Helpers/Metadata.cs @@ -61,7 +61,7 @@ internal enum MetadataTable GenericParam = 0x2a, MethodSpec = 0x2b, GenericParamConstraint = 0x2c, - Count = 0x2c + Count = 0x2d } internal class EcmaMetadata diff --git a/src/native/managed/cdacreader/src/Legacy/TypeNameBuilder.cs b/src/native/managed/cdacreader/src/Legacy/TypeNameBuilder.cs index e093ae1c59320..3f942f2469a92 100644 --- a/src/native/managed/cdacreader/src/Legacy/TypeNameBuilder.cs +++ b/src/native/managed/cdacreader/src/Legacy/TypeNameBuilder.cs @@ -238,6 +238,10 @@ private void OpenGenericArgument() { TypeString.Append(','); } + else + { + FirstInstArg = false; + } TypeString.Append(UseAngleBracketsForGenerics ? '<' : '['); PushOpenGenericArgument(); @@ -319,6 +323,8 @@ private void AddAssemblySpec(string? assemblySpec) State = ParseState.AssemSpec; if (!string.IsNullOrEmpty(assemblySpec)) { + TypeString.Append(", "); + if (InstNesting > 0) EscapeEmbeddedAssemblyName(assemblySpec); else diff --git a/src/native/managed/cdacreader/src/Target.cs b/src/native/managed/cdacreader/src/Target.cs index da5d57334dc88..9039011c4bd69 100644 --- a/src/native/managed/cdacreader/src/Target.cs +++ b/src/native/managed/cdacreader/src/Target.cs @@ -261,6 +261,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)) @@ -447,10 +449,10 @@ public TValue GetOrAdd(TKey key) where TValue : IData dictionary = (Dictionary<(TKey, Type), object?>)_customKeyReadDataByAddress[typeof(TKey)]; + Dictionary dictionary = (Dictionary)_customKeyReadDataByAddress[typeof(Tuple)]; TValue constructed = TValue.Create(_target, key); - if (dictionary.TryAdd((key, typeof(TValue)), constructed)) + if (dictionary.TryAdd(key, constructed)) return constructed; bool found = TryGet(key, out result); @@ -475,25 +477,13 @@ public bool TryGet(ulong address, [NotNullWhen(true)] out T? data) public bool TryGet(TKey key, [NotNullWhen(true)] out TValue? data) where TValue : IData where TKey : notnull, IEquatable { - data = default; - - if (!_customKeyReadDataByAddress.TryGetValue(typeof(TKey), out var dictionaryObject)) - { - dictionaryObject = new Dictionary<(TKey, Type), TValue>(); - _customKeyReadDataByAddress.Add(typeof(TKey), dictionaryObject); - } - Dictionary<(TKey, Type), object?> dictionary = (Dictionary<(TKey, Type), object?>)dictionaryObject; - - if (!dictionary.TryGetValue((key, typeof(TValue)), out object? dataObj)) - return false; - - if (dataObj is TValue dataMaybe) + if (!_customKeyReadDataByAddress.TryGetValue(typeof(Tuple), out var dictionaryObject)) { - data = dataMaybe; - return true; + dictionaryObject = new Dictionary(); + _customKeyReadDataByAddress.Add(typeof(Tuple), dictionaryObject); } - - return false; + Dictionary dictionary = (Dictionary)dictionaryObject; + return dictionary.TryGetValue(key, out data); } } From fc24cd419757c1be0f849c7fae0c73dd7c1c6f93 Mon Sep 17 00:00:00 2001 From: David Wrighton Date: Thu, 11 Jul 2024 14:21:59 -0700 Subject: [PATCH 05/15] Update contract documentation --- docs/design/datacontracts/Loader.md | 97 ++++++ .../design/datacontracts/RuntimeTypeSystem.md | 298 +++++++++++++++++- .../RuntimeTypeSystem_1.MethodTableFlags.cs | 1 - 3 files changed, 391 insertions(+), 5 deletions(-) diff --git a/docs/design/datacontracts/Loader.md b/docs/design/datacontracts/Loader.md index 4291c96569b27..962226ec41d7e 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,6 +105,9 @@ 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); ``` @@ -94,6 +166,31 @@ 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(dynamicMetadata); + TargetPointer result = module.DynamicMetadata + (ulong)_target.PointerSize; + return result; +} + ModuleLookupTables GetLookupTables(ModuleHandle handle) { return new ModuleLookupTables( diff --git a/docs/design/datacontracts/RuntimeTypeSystem.md b/docs/design/datacontracts/RuntimeTypeSystem.md index f8382ee4fdc59..085766f089651 100644 --- a/docs/design/datacontracts/RuntimeTypeSystem.md +++ b/docs/design/datacontracts/RuntimeTypeSystem.md @@ -4,17 +4,64 @@ 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 `MethodTable` 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 `MethodTableHandle` for querying the `MethodTable`. +Some other apis are available while using `TypeHandle` which is provided similarly to a MethodTable + ``` csharp struct MethodTableHandle { // no public properties or constructors - internal TargetPointer Address { get; } + public TargetPointer Address { get; } +} + +struct TypeHandle +{ +} + + +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, } ``` +A `TypeHandle` is the runtime representation of the type information about a value. This can be constructed from either a `MethodTableHandle` or +struct TypeHandle + ``` csharp #region MethodTable inspection APIs public virtual MethodTableHandle GetMethodTableHandle(TargetPointer targetPointer); @@ -43,7 +90,24 @@ struct MethodTableHandle // 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); + public virtual ReadOnlySpan GetInstantiation(MethodTableHandle methodTable); + public virtual bool IsGenericTypeDefinition(MethodTableHandle methodTable); + #endregion MethodTable inspection APIs + + #region TypeHandle inspection APIs + public virtual TypeHandle TypeHandleFromAddress(TargetPointer address); + public virtual bool HasTypeParam(TypeHandle typeHandle); + // Element type of the type. NOTE: this drops the CorElementType.GenericInst for generics, and CorElementType.String is returned as CorElementType.Class + 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 +124,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 +134,15 @@ internal partial struct RuntimeTypeSystem_1 internal enum WFLAGS_HIGH : uint { Category_Mask = 0x000F0000, + Category_ElementType_Mask = 0x000E0000, + Category_IfArrayThenSzArray = 0x00020000, Category_Array = 0x00080000, - Category_Array_Mask = 0x000C0000, + Category_ValueType = 0x00040000, + Category_Nullable = 0x00050000, + Category_PrimitiveValueType = 0x00060000, + Category_TruePrimitive = 0x00070000, Category_Interface = 0x000C0000, + Category_Array_Mask = 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 +189,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] @@ -141,6 +213,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,12 +227,39 @@ internal struct MethodTable_1 EEClassOrCanonMT = data.EEClassOrCanonMT; Module = data.Module; ParentMethodTable = data.ParentMethodTable; + PerInstInfo = data.PerInstInfo; } } ``` +Internally the contract depends on a `TypeDescHandle` structure that represents the address of something that implements the TypeDesc data descriptor. + +Internally the contract depends on the `TypeHandle` structure being capable of holding a TypeDescHandle or a MethodTableHandle. +```csharp +struct TypeHandle +{ + internal TypeHandle(TypeDescHandle typeDescHandle); + internal TypeHandle(MethodTableHandle methodTableHandle); + internal bool IsMethodTable { get; } + internal MethodTableHandle AsMethodTable { get; } + internal bool IsTypeDesc { get; } + internal TypeDescHandle AsTypeDesc { get; } +} +``` + 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 | +| --- | +| `EEClass` | +| `ArraayClass` | +| `TypeDesc` | +| `ParamTypeDesc` | +| `TypeVarTypeDesc` | +| `FnPtrTypeDesc` | +| `GenericsDictInfo` | + ```csharp private readonly Dictionary _methodTables; @@ -221,4 +321,194 @@ The contract additionally depends on the `EEClass` data descriptor. public uint GetTypeDefTypeAttributes(MethodTableHandle methodTableHandle) => GetClassData(methodTableHandle).CorTypeAttr; public bool IsDynamicStatics(MethodTableHandle methodTableHandle) => _methodTables[methodTableHandle.Address].Flags.IsDynamicStatics; + + public ReadOnlySpan GetInstantiation(MethodTableHandle methodTableHandle) + { + MethodTable_1 methodTable = _methodTables[methodTableHandle.Address]; + if (!methodTable.Flags.HasInstantiation) + return default; + + TargetPointer perInstInfo = methodTable.PerInstInfo; + TargetPointer genericsDictInfo = perInstInfo - (ulong)_target.PointerSize; + TargetPointer dictionaryPointer = _target.ReadPointer(perInstInfo); + + int NumTypeArgs = // Read NumTypeArgs from genericsDictInfo using GenericsDictInfo contract + MethodTableHandle[] instantiation = new MethodTableHandle[NumTypeArgs]; + for (int i = 0; i < NumTypeArgs; i++) + instantiation[i] = GetMethodTableHandle(_target.ReadPointer(dictionaryPointer + _target.PointerSize * i)); + + return instantiation; + } + + public bool IsDynamicStatics(MethodTableHandle methodTableHandle) => _methodTables[methodTableHandle.Address].Flags.IsGenericTypeDefinition; + + public TypeHandle TypeHandleFromAddress(TargetPointer address) + { + // validate that address points to something that looks like a TypeHandle. + + if (address & 2 == 2) + { + return new TypeHandle(new TypeDescHandle(address - 2)); + } + else + { + return new TypeHandle(new MethodTableHandle(address)); + } + } + + public bool HasTypeParam(TypeHandle typeHandle) + { + if (typeHandle.IsMethodTable) + { + MethodTable methodTable = _methodTables[typeHandle.AsMethodTable.Address]; + return methodTable.Flags.IsArray; + } + else if (typeHandle.IsTypeDesc) + { + int TypeAndFlags = // Read TypeAndFlags field from TypeDesc contract using address typeHandle.AsTypeDesc.Address + 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.AsMethodTable.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.AsMethodTable).InternalCorElementType; + default: + return CorElementType.Class; + } + } + else if (typeHandle.IsTypeDesc) + { + int TypeAndFlags = // Read TypeAndFlags field from TypeDesc contract using address typeHandle.AsTypeDesc.Address + return (CorElementType)(typeDesc.TypeAndFlags & 0xFF); + } + return default(CorElementType); + } + + // 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.AsMethodTable.Address]; + + switch (methodTable.Flags.GetFlag(WFLAGS_HIGH.Category_Mask)) + { + case WFLAGS_HIGH.Category_Array: + TargetPointer clsPtr = GetClassPointer(typeHandle.AsMethodTable); + 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) + { + if (typeHandle.IsMethodTable) + { + MethodTable methodTable = _methodTables[typeHandle.AsMethodTable.Address]; + + // Validate that this is an array + if (!methodTable.Flags.IsArray) + throw new ArgumentException(nameof(typeHandle)); + + return TypeHandleFromAddress(methodTable.PerInstInfo); + } + else if (typeHandle.IsTypeDesc) + { + int TypeAndFlags = // Read TypeAndFlags field from TypeDesc contract using address typeHandle.AsTypeDesc.Address + 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.AsTypeDesc.Address + return TypeHandleFromAddress(typeArgPointer); + } + } + 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; + + int TypeAndFlags = // Read TypeAndFlags field from TypeDesc contract using address typeHandle.AsTypeDesc.Address + CorElementType elemType = (CorElementType)(typeDesc.TypeAndFlags & 0xFF); + + switch (elemType) + { + case CorElementType.MVar: + case CorElementType.Var: + TypeVarTypeDesc typeVarTypeDesc = _target.ProcessedData.GetOrAdd(typeHandle.AsTypeDesc.Address); + module = // Read Module field from TypeVarTypeDesc contract using address typeHandle.AsTypeDesc.Address + token = // Read Module field from TypeVarTypeDesc contract using address typeHandle.AsTypeDesc.Address + return true; + } + return false; + } + + public bool IsFunctionPointer(TypeHandle typeHandle, out ReadOnlySpan retAndArgTypes, out byte callConv) + { + retAndArgTypes = default; + callConv = default; + + if (!typeHandle.IsTypeDesc) + return false; + + int TypeAndFlags = // Read TypeAndFlags field from TypeDesc contract using address typeHandle.AsTypeDesc.Address + CorElementType elemType = (CorElementType)(typeDesc.TypeAndFlags & 0xFF); + + if (elemType != CorElementType.FnPtr) + return false; + + int NumArgs = // Read NumArgs field from FnPtrTypeDesc contract using address typeHandle.AsTypeDesc.Address + TargetPointer RetAndArgTypes = // Read NumArgs field from FnPtrTypeDesc contract using address typeHandle.AsTypeDesc.Address + + MethodTableHandle[] retAndArgTypesArray = new TypeHandle[NumTypeArgs + 1]; + for (int i = 0; i <= NumTypeArgs; i++) + retAndArgTypesArray[i] = TypeHandleFromAddress(_target.ReadPointer(RetAndArgTypes + _target.PointerSize * i)); + + TypeHandleArray retAndArgTypesArray = _target.ProcessedData.GetOrAdd<(TargetPointer, int), TypeHandleArray> + ((fnPtrTypeDesc.RetAndArgTypes, checked((int)fnPtrTypeDesc.NumArgs + 1))); + retAndArgTypes = retAndArgTypesArray; + callConv = (byte) // Read CallConv field from FnPtrTypeDesc contract using address typeHandle.AsTypeDesc.Address, and then ignore all but the low 8 bits. + return true; + } ``` 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 d0c92864ad794..6e02d241b994c 100644 --- a/src/native/managed/cdacreader/src/Contracts/RuntimeTypeSystem_1.MethodTableFlags.cs +++ b/src/native/managed/cdacreader/src/Contracts/RuntimeTypeSystem_1.MethodTableFlags.cs @@ -85,7 +85,6 @@ private bool TestFlagWithMask(WFLAGS2_ENUM mask, WFLAGS2_ENUM flag) public bool IsInterface => GetFlag(WFLAGS_HIGH.Category_Mask) == WFLAGS_HIGH.Category_Interface; public bool IsString => HasComponentSize && !IsArray && ComponentSizeBits == 2; public bool IsArray => GetFlag(WFLAGS_HIGH.Category_Array_Mask) == WFLAGS_HIGH.Category_Array; - public bool IfArrayThenSzArray => GetFlag(WFLAGS_HIGH.Category_IfArrayThenSzArray) != 0; public bool IsStringOrArray => HasComponentSize; public ushort ComponentSize => HasComponentSize ? ComponentSizeBits : (ushort)0; public bool HasInstantiation => !TestFlagWithMask(WFLAGS_LOW.GenericsMask, WFLAGS_LOW.GenericsMask_NonGeneric); From fead4092c2a1e7fc20d4f63eebb3d46ebc015dbc Mon Sep 17 00:00:00 2001 From: David Wrighton Date: Fri, 12 Jul 2024 10:21:24 -0700 Subject: [PATCH 06/15] Feedback --- .../design/datacontracts/RuntimeTypeSystem.md | 67 +++++++------------ .../src/Contracts/RuntimeTypeSystem_1.cs | 2 +- .../managed/cdacreader/src/Data/EEClass.cs | 8 +++ 3 files changed, 32 insertions(+), 45 deletions(-) diff --git a/docs/design/datacontracts/RuntimeTypeSystem.md b/docs/design/datacontracts/RuntimeTypeSystem.md index 085766f089651..ab4f3bb81d760 100644 --- a/docs/design/datacontracts/RuntimeTypeSystem.md +++ b/docs/design/datacontracts/RuntimeTypeSystem.md @@ -21,41 +21,11 @@ struct TypeHandle { } - 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, + // 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 separate from a live process. } ``` @@ -135,6 +105,8 @@ internal partial struct RuntimeTypeSystem_1 { Category_Mask = 0x000F0000, Category_ElementType_Mask = 0x000E0000, + Category_Array_Mask = 0x000C0000, + Category_IfArrayThenSzArray = 0x00020000, Category_Array = 0x00080000, Category_ValueType = 0x00040000, @@ -142,7 +114,7 @@ internal partial struct RuntimeTypeSystem_1 Category_PrimitiveValueType = 0x00060000, Category_TruePrimitive = 0x00070000, Category_Interface = 0x000C0000, - Category_Array_Mask = 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 @@ -248,17 +220,24 @@ struct TypeHandle ``` The contract depends on the global pointer value `FreeObjectMethodTablePointer`. + The contract additionally depends on these data descriptors -| Data Descriptor Name | -| --- | -| `EEClass` | -| `ArraayClass` | -| `TypeDesc` | -| `ParamTypeDesc` | -| `TypeVarTypeDesc` | -| `FnPtrTypeDesc` | -| `GenericsDictInfo` | +| Data Descriptor Name | Field | Meaning | +| --- | --- | --- | +| `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 @@ -346,7 +325,7 @@ The contract additionally depends on these data descriptors { // validate that address points to something that looks like a TypeHandle. - if (address & 2 == 2) + if (address & 2 != 0) { return new TypeHandle(new TypeDescHandle(address - 2)); } diff --git a/src/native/managed/cdacreader/src/Contracts/RuntimeTypeSystem_1.cs b/src/native/managed/cdacreader/src/Contracts/RuntimeTypeSystem_1.cs index aa5bb80978ed7..dcb6b59486bf7 100644 --- a/src/native/managed/cdacreader/src/Contracts/RuntimeTypeSystem_1.cs +++ b/src/native/managed/cdacreader/src/Contracts/RuntimeTypeSystem_1.cs @@ -182,7 +182,7 @@ public TypeHandle TypeHandleFromAddress(TargetPointer address) if (address == 0) return default; - if (((ulong)address & 2) == (ulong)2) + if (((ulong)address & 2) != 0) { return new TypeHandle(new TypeDescHandle(address - 2)); } diff --git a/src/native/managed/cdacreader/src/Data/EEClass.cs b/src/native/managed/cdacreader/src/Data/EEClass.cs index 726c6064d427b..531a30389e41c 100644 --- a/src/native/managed/cdacreader/src/Data/EEClass.cs +++ b/src/native/managed/cdacreader/src/Data/EEClass.cs @@ -21,6 +21,14 @@ public EEClass(Target target, TargetPointer address) 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; } } From d2b36fcb23afaf6970bda6987aed409626677732 Mon Sep 17 00:00:00 2001 From: David Wrighton Date: Mon, 15 Jul 2024 10:50:39 -0700 Subject: [PATCH 07/15] Add support for the EEName DacStream --- docs/design/datacontracts/DacStreams.md | 81 +++++++++++++ src/coreclr/debug/daccess/request.cpp | 2 +- src/coreclr/debug/runtimeinfo/contracts.jsonc | 3 +- .../debug/runtimeinfo/datadescriptor.h | 2 + .../managed/cdacreader/src/Constants.cs | 3 + .../cdacreader/src/Contracts/DacStreams.cs | 30 +++++ .../cdacreader/src/Contracts/DacStreams_1.cs | 113 ++++++++++++++++++ .../cdacreader/src/Contracts/Registry.cs | 1 + .../cdacreader/src/Legacy/SOSDacImpl.cs | 13 +- 9 files changed, 244 insertions(+), 4 deletions(-) create mode 100644 docs/design/datacontracts/DacStreams.md create mode 100644 src/native/managed/cdacreader/src/Contracts/DacStreams.cs create mode 100644 src/native/managed/cdacreader/src/Contracts/DacStreams_1.cs diff --git a/docs/design/datacontracts/DacStreams.md b/docs/design/datacontracts/DacStreams.md new file mode 100644 index 0000000000000..aaceda4699982 --- /dev/null +++ b/docs/design/datacontracts/DacStreams.md @@ -0,0 +1,81 @@ +# 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 | + +``` csharp +string StringFromEEAddress(TargetPointer address) +{ + TargetPointer miniMetaDataBuffAddress = _target.Read(_target.ReadGlobalPointer(Constants.Globals.MiniMetaDataBuffAddress)); + uint miniMetaDataBuffMaxSize = _target.Read(_target.ReadGlobalPointer(Constants.Globals.MiniMetaDataBuffMaxSize)); + + if (miniMetaDataBuffMaxSize < 20) + { + // buffer isn't long enough to hold required headers + return null; + } + + if (_target.Read(miniMetaDataBuffAddress) != 0x6d727473) + { + // Magic number is incorrect, data is either corrupt, or this is not a crash dump with embedded mini metadata streams + return null; + } + + uint totalSize = _target.Read(miniMetaDataBuffAddress + 0x4); + if (totalSize > miniMetaDataBuffMaxSize) + { + // totalSize is inconsistent with miniMetaDataBuffMaxSize + return stringToAddress; + } + uint countStreams = _target.Read(miniMetaDataBuffAddress + 0x8); + if (countStreams != 1) + { + // This implementation is only aware of 1 possible stream type, so only 1 can exist + return stringToAddress; + } + uint eeNameSig = _target.Read(miniMetaDataBuffAddress + 0xC); + if (eeNameSig != 0x614e4545) + { + // name of first stream is not 0x614e4545 == "EENa" + return stringToAddress; + } + uint countNames = _target.Read(miniMetaDataBuffAddress + 0x10); + + uint currentOffset = 20; + + for (int i = 0; i < countNames; i++) + { + if ((currentOffset + _target.PointerSize) > miniMetaDataBuffMaxSize) + break; + TargetPointer eeObjectPointer = _target.ReadPointer(miniMetaDataBuffAddress + currentOffset); + currentOffset += (uint)_target.PointerSize; + int stringLen = // Compute IndexOf null terminator starting at currentOffset, or -1 if it can't be found within miniMetaDataBuffMaxSize + if (stringLen == -1) + break; + + if (eeObjectPointer != address) + { + currentOffset += stringLen + 1; + continue; + } + + return Encoding.UTF8.GetString(miniMdBuffer.Slice((int)currentOffset, stringLen)); + } + + return null; +} +``` diff --git a/src/coreclr/debug/daccess/request.cpp b/src/coreclr/debug/daccess/request.cpp index 324c279f05313..abc2b8defe4c8 100644 --- a/src/coreclr/debug/daccess/request.cpp +++ b/src/coreclr/debug/daccess/request.cpp @@ -1935,7 +1935,7 @@ ClrDataAccess::GetMethodTableName(CLRDATA_ADDRESS mt, unsigned int count, _Inout if (mtName != NULL) { - _ASSERTE(0 == wcsncmp(mtName, (WCHAR *)pwszNameLocal, count)); + _ASSERTE(0 == u16_strncmp(mtName, (WCHAR *)pwszNameLocal, count)); } if (pNeeded != NULL) { diff --git a/src/coreclr/debug/runtimeinfo/contracts.jsonc b/src/coreclr/debug/runtimeinfo/contracts.jsonc index f40991da45b67..48ffcae518250 100644 --- a/src/coreclr/debug/runtimeinfo/contracts.jsonc +++ b/src/coreclr/debug/runtimeinfo/contracts.jsonc @@ -12,5 +12,6 @@ "Exception": 1, "Loader": 1, "RuntimeTypeSystem": 1, - "Thread": 1 + "Thread": 1, + "DacStreams": 1 } diff --git a/src/coreclr/debug/runtimeinfo/datadescriptor.h b/src/coreclr/debug/runtimeinfo/datadescriptor.h index 2b7887606a6ca..a5b937f51ee43 100644 --- a/src/coreclr/debug/runtimeinfo/datadescriptor.h +++ b/src/coreclr/debug/runtimeinfo/datadescriptor.h @@ -245,6 +245,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/native/managed/cdacreader/src/Constants.cs b/src/native/managed/cdacreader/src/Constants.cs index 5c95db68d227f..6ca10e46a949b 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 0000000000000..167c7f89ad2bf --- /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 0000000000000..cd43407ffc1f0 --- /dev/null +++ b/src/native/managed/cdacreader/src/Contracts/DacStreams_1.cs @@ -0,0 +1,113 @@ +// 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; + + internal DacStreams_1(Target target) + { + _target = target; + } + + public virtual string? StringFromEEAddress(TargetPointer address) + { + TargetPointer miniMetaDataBuffAddress = _target.ReadPointer(_target.ReadGlobalPointer(Constants.Globals.MiniMetaDataBuffAddress)); + uint miniMetaDataBuffMaxSize = _target.Read(_target.ReadGlobalPointer(Constants.Globals.MiniMetaDataBuffMaxSize)); + + // We use the data subsystem to handle caching results from processing this data + var dictionary = _target.ProcessedData.GetOrAdd<(TargetPointer, uint), DacStreams_1_Data>((miniMetaDataBuffAddress, miniMetaDataBuffMaxSize)).EEObjectToString; + + dictionary.TryGetValue(address, out string? result); + return result; + } + + internal class DacStreams_1_Data : IData + { + static DacStreams_1_Data IData.Create(Target target, (TargetPointer, uint) key) => new DacStreams_1_Data(target, key); + + public DacStreams_1_Data(Target target, (TargetPointer miniMetaDataBuffAddress, uint miniMetaDataBuffMaxSize) key) + { + EEObjectToString = GetEEAddressToStringMap(target, key.miniMetaDataBuffAddress, key.miniMetaDataBuffMaxSize); + } + + public readonly Dictionary EEObjectToString; + + internal static Dictionary GetEEAddressToStringMap(Target target, TargetPointer miniMetaDataBuffAddress, uint miniMetaDataBuffMaxSize) + { + Dictionary stringToAddress = new(); + if (miniMetaDataBuffMaxSize < 20) + { + // buffer isn't long enough to hold required headers + return stringToAddress; + } + + if (target.Read(miniMetaDataBuffAddress) != 0x6d727473) + { + // Magic number is incorrect + return stringToAddress; + } + + + uint totalSize = target.Read(miniMetaDataBuffAddress + 0x4); + 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 + 0x8); + if (countStreams != 1) + { + // This implementation is only aware of 1 possible stream type, so only 1 can exist + return stringToAddress; + } + uint eeNameSig = target.Read(miniMetaDataBuffAddress + 0xC); + if (eeNameSig != 0x614e4545) + { + // name of first stream is not 0x614e4545 == "EENa" + return stringToAddress; + } + uint countNames = target.Read(miniMetaDataBuffAddress + 0x10); + + uint currentOffset = 20; + + for (int i = 0; i < countNames; i++) + { + if ((currentOffset + target.PointerSize) > miniMetaDataBuffMaxSize) + break; + TargetPointer eeObjectPointer = target.ReadPointer(miniMetaDataBuffAddress + currentOffset); + currentOffset += (uint)target.PointerSize; + int stringLen = miniMdBuffer.Slice((int)currentOffset).IndexOf((byte)0); + if (stringLen == -1) + break; + + try + { + string name = Encoding.UTF8.GetString(miniMdBuffer.Slice((int)currentOffset, stringLen)); + stringToAddress.Add(eeObjectPointer, name); + } + catch + { + // Tolerate malformed strings without causing all lookups to fail + } + + currentOffset += (uint)stringLen + 1; + } + + return stringToAddress; + } + } +} diff --git a/src/native/managed/cdacreader/src/Contracts/Registry.cs b/src/native/managed/cdacreader/src/Contracts/Registry.cs index 21f46e481ecd9..6a5bf35f64d64 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/Legacy/SOSDacImpl.cs b/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs index 9e2cbb22f637d..d707fddaa44db 100644 --- a/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs +++ b/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs @@ -192,8 +192,17 @@ public unsafe int GetMethodTableName(ulong mt, uint count, char* mtName, uint* p } catch { - // TODO(cdac) - - Debug.Fail("Need to implment the fallback path here"); + 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; From 8fdcc2493275116c47e3675ab7dcf39783dc30ea Mon Sep 17 00:00:00 2001 From: David Wrighton Date: Mon, 15 Jul 2024 13:26:11 -0700 Subject: [PATCH 08/15] - Refactor to expose TypeHandle exclusively from the contracts - Remove TypeHandleArray and MethodTableArray in favor of contract specific logic --- .../design/datacontracts/RuntimeTypeSystem.md | 271 ++++++++++-------- .../cdacreader/src/Contracts/DacStreams_1.cs | 18 +- .../src/Contracts/RuntimeTypeSystem.cs | 106 ++----- .../src/Contracts/RuntimeTypeSystem_1.cs | 242 ++++++++++------ .../Contracts/RuntimeTypeSystem_1_Helpers.cs | 31 ++ .../managed/cdacreader/src/Data/IData.cs | 5 - .../cdacreader/src/Data/MethodTableArray.cs | 33 --- .../cdacreader/src/Data/TypeHandleArray.cs | 33 --- .../cdacreader/src/Legacy/SOSDacImpl.cs | 8 +- .../cdacreader/src/Legacy/TypeNameBuilder.cs | 21 +- src/native/managed/cdacreader/src/Target.cs | 28 -- 11 files changed, 393 insertions(+), 403 deletions(-) create mode 100644 src/native/managed/cdacreader/src/Contracts/RuntimeTypeSystem_1_Helpers.cs delete mode 100644 src/native/managed/cdacreader/src/Data/MethodTableArray.cs delete mode 100644 src/native/managed/cdacreader/src/Data/TypeHandleArray.cs diff --git a/docs/design/datacontracts/RuntimeTypeSystem.md b/docs/design/datacontracts/RuntimeTypeSystem.md index ab4f3bb81d760..5399e83f5b4a3 100644 --- a/docs/design/datacontracts/RuntimeTypeSystem.md +++ b/docs/design/datacontracts/RuntimeTypeSystem.md @@ -4,17 +4,17 @@ 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 which is represented as a TypeHandle. -Given a `TargetPointer` address, the `RuntimeTypeSystem` contract provides a `MethodTableHandle` for querying the `MethodTable`. -Some other apis are available while using `TypeHandle` which is provided similarly to a 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 public TargetPointer Address { get; } + public bool IsNull => Address != 0; } struct TypeHandle @@ -29,46 +29,46 @@ internal enum CorElementType } ``` -A `TypeHandle` is the runtime representation of the type information about a value. This can be constructed from either a `MethodTableHandle` or +A `TypeHandle` is the runtime representation of the type information about a value. This can be constructed from either a `TypeHandle` or struct TypeHandle ``` csharp #region MethodTable inspection APIs - public virtual MethodTableHandle GetMethodTableHandle(TargetPointer targetPointer); + 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); - public virtual ReadOnlySpan GetInstantiation(MethodTableHandle methodTable); - public virtual bool IsGenericTypeDefinition(MethodTableHandle methodTable); - - #endregion MethodTable inspection APIs + public virtual uint GetTypeDefTypeAttributes(TypeHandle typeHandle); + public virtual ReadOnlySpan GetInstantiation(TypeHandle typeHandle); + public virtual bool IsGenericTypeDefinition(TypeHandle typeHandle); - #region TypeHandle inspection APIs public virtual TypeHandle TypeHandleFromAddress(TargetPointer address); public virtual bool HasTypeParam(TypeHandle typeHandle); - // Element type of the type. NOTE: this drops the CorElementType.GenericInst for generics, and CorElementType.String is returned as CorElementType.Class + + // 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. @@ -171,6 +171,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, + } } ``` @@ -204,18 +214,27 @@ internal struct MethodTable_1 } ``` -Internally the contract depends on a `TypeDescHandle` structure that represents the address of something that implements the TypeDesc data descriptor. - -Internally the contract depends on the `TypeHandle` structure being capable of holding a TypeDescHandle or a MethodTableHandle. +Internally the contract uses extension methods on the `TypeHandle` api so that it can distinguish between `MethodTable` and `TypeDesc` ```csharp -struct TypeHandle +static class RuntimeTypeSystem_1_Helpers { - internal TypeHandle(TypeDescHandle typeDescHandle); - internal TypeHandle(MethodTableHandle methodTableHandle); - internal bool IsMethodTable { get; } - internal MethodTableHandle AsMethodTable { get; } - internal bool IsTypeDesc { get; } - internal TypeDescHandle AsTypeDesc { get; } + 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; + } } ``` @@ -225,6 +244,15 @@ 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 | @@ -245,12 +273,13 @@ The contract additionally depends on these data descriptors 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) @@ -258,11 +287,11 @@ The contract additionally depends on these data descriptors 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 @@ -270,82 +299,93 @@ The contract additionally depends on these data descriptors // 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(MethodTableHandle methodTableHandle) => _methodTables[methodTableHandle.Address].Module; - public TargetPointer GetParentMethodTable(MethodTableHandle methodTableHandle) => _methodTables[methodTableHandle.Address].ParentMethodTable; + 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(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_1 methodTable = _methodTables[methodTableHandle.Address]; - return (uint)(methodTable.Flags.GetTypeDefRid() | ((int)TableIndex.TypeDef << 24)); + if (!typeHandle.IsMethodTable()) + return 0; + + MethodTable_1 typeHandle = _methodTables[TypeHandle.Address]; + return (uint)(typeHandle.Flags.GetTypeDefRid() | ((int)TableIndex.TypeDef << 24)); } - public ushort GetNumMethods(MethodTableHandle methodTableHandle) => GetClassData(methodTableHandle).NumMethods; + public ushort GetNumMethods(TypeHandle TypeHandle) => !typeHandle.IsMethodTable() ? 0 : GetClassData(TypeHandle).NumMethods; - public ushort GetNumInterfaces(MethodTableHandle methodTableHandle) => _methodTables[methodTableHandle.Address].NumInterfaces; + public ushort GetNumInterfaces(TypeHandle TypeHandle) => !typeHandle.IsMethodTable() ? 0 : _methodTables[TypeHandle.Address].NumInterfaces; - public uint GetTypeDefTypeAttributes(MethodTableHandle methodTableHandle) => GetClassData(methodTableHandle).CorTypeAttr; + public uint GetTypeDefTypeAttributes(TypeHandle TypeHandle) => !typeHandle.IsMethodTable() ? 0 : GetClassData(TypeHandle).CorTypeAttr; - public bool IsDynamicStatics(MethodTableHandle methodTableHandle) => _methodTables[methodTableHandle.Address].Flags.IsDynamicStatics; + public bool IsDynamicStatics(TypeHandle TypeHandle) => !typeHandle.IsMethodTable() ? false : _methodTables[TypeHandle.Address].Flags.IsDynamicStatics; - public ReadOnlySpan GetInstantiation(MethodTableHandle methodTableHandle) + public ReadOnlySpan GetInstantiation(TypeHandle TypeHandle) { - MethodTable_1 methodTable = _methodTables[methodTableHandle.Address]; - if (!methodTable.Flags.HasInstantiation) + if (!typeHandle.IsMethodTable()) + return default; + + MethodTable_1 typeHandle = _methodTables[TypeHandle.Address]; + if (!typeHandle.Flags.HasInstantiation) return default; - TargetPointer perInstInfo = methodTable.PerInstInfo; + TargetPointer perInstInfo = typeHandle.PerInstInfo; TargetPointer genericsDictInfo = perInstInfo - (ulong)_target.PointerSize; TargetPointer dictionaryPointer = _target.ReadPointer(perInstInfo); int NumTypeArgs = // Read NumTypeArgs from genericsDictInfo using GenericsDictInfo contract - MethodTableHandle[] instantiation = new MethodTableHandle[NumTypeArgs]; + TypeHandle[] instantiation = new TypeHandle[NumTypeArgs]; for (int i = 0; i < NumTypeArgs; i++) - instantiation[i] = GetMethodTableHandle(_target.ReadPointer(dictionaryPointer + _target.PointerSize * i)); + instantiation[i] = GetTypeHandle(_target.ReadPointer(dictionaryPointer + _target.PointerSize * i)); return instantiation; } - public bool IsDynamicStatics(MethodTableHandle methodTableHandle) => _methodTables[methodTableHandle.Address].Flags.IsGenericTypeDefinition; - - public TypeHandle TypeHandleFromAddress(TargetPointer address) - { - // validate that address points to something that looks like a TypeHandle. - - if (address & 2 != 0) - { - return new TypeHandle(new TypeDescHandle(address - 2)); - } - else - { - return new TypeHandle(new MethodTableHandle(address)); - } - } + public bool IsDynamicStatics(TypeHandle TypeHandle) => !typeHandle.IsMethodTable() ? false : _methodTables[TypeHandle.Address].Flags.IsDynamicStatics; public bool HasTypeParam(TypeHandle typeHandle) { - if (typeHandle.IsMethodTable) + if (typeHandle.IsMethodTable()) { - MethodTable methodTable = _methodTables[typeHandle.AsMethodTable.Address]; - return methodTable.Flags.IsArray; + MethodTable typeHandle = _methodTables[typeHandle.Address]; + return typeHandle.Flags.IsArray; } - else if (typeHandle.IsTypeDesc) + else if (typeHandle.IsTypeDesc()) { - int TypeAndFlags = // Read TypeAndFlags field from TypeDesc contract using address typeHandle.AsTypeDesc.Address - CorElementType elemType = (CorElementType)(typeDesc.TypeAndFlags & 0xFF); + int TypeAndFlags = // Read TypeAndFlags field from TypeDesc contract using address typeHandle.TypeDescAddress() + CorElementType elemType = (CorElementType)(TypeAndFlags & 0xFF); switch (elemType) { case CorElementType.ValueType: @@ -359,11 +399,11 @@ The contract additionally depends on these data descriptors public CorElementType GetSignatureCorElementType(TypeHandle typeHandle) { - if (typeHandle.IsMethodTable) + if (typeHandle.IsMethodTable()) { - MethodTable methodTable = _methodTables[typeHandle.AsMethodTable.Address]; + MethodTable typeHandle = _methodTables[typeHandle.Address]; - switch (methodTable.Flags.GetFlag(WFLAGS_HIGH.Category_Mask)) + switch (typeHandle.Flags.GetFlag(WFLAGS_HIGH.Category_Mask)) { case WFLAGS_HIGH.Category_Array: return CorElementType.Array; @@ -374,15 +414,15 @@ The contract additionally depends on these data descriptors case WFLAGS_HIGH.Category_PrimitiveValueType: return CorElementType.ValueType; case WFLAGS_HIGH.Category_TruePrimitive: - return (CorElementType)GetClassData(typeHandle.AsMethodTable).InternalCorElementType; + return (CorElementType)GetClassData(typeHandle).InternalCorElementType; default: return CorElementType.Class; } } - else if (typeHandle.IsTypeDesc) + else if (typeHandle.IsTypeDesc()) { - int TypeAndFlags = // Read TypeAndFlags field from TypeDesc contract using address typeHandle.AsTypeDesc.Address - return (CorElementType)(typeDesc.TypeAndFlags & 0xFF); + int TypeAndFlags = // Read TypeAndFlags field from TypeDesc contract using address typeHandle.TypeDescAddress() + return (CorElementType)(TypeAndFlags & 0xFF); } return default(CorElementType); } @@ -390,14 +430,14 @@ The contract additionally depends on these data descriptors // 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) + if (typeHandle.IsMethodTable()) { - MethodTable methodTable = _methodTables[typeHandle.AsMethodTable.Address]; + MethodTable typeHandle = _methodTables[typeHandle.Address]; - switch (methodTable.Flags.GetFlag(WFLAGS_HIGH.Category_Mask)) + switch (typeHandle.Flags.GetFlag(WFLAGS_HIGH.Category_Mask)) { case WFLAGS_HIGH.Category_Array: - TargetPointer clsPtr = GetClassPointer(typeHandle.AsMethodTable); + TargetPointer clsPtr = GetClassPointer(typeHandle); rank = // Read Rank field from ArrayClass contract using address clsPtr return true; @@ -413,19 +453,19 @@ The contract additionally depends on these data descriptors public TypeHandle GetTypeParam(TypeHandle typeHandle) { - if (typeHandle.IsMethodTable) + if (typeHandle.IsMethodTable()) { - MethodTable methodTable = _methodTables[typeHandle.AsMethodTable.Address]; + MethodTable typeHandle = _methodTables[typeHandle.Address]; // Validate that this is an array - if (!methodTable.Flags.IsArray) + if (!typeHandle.Flags.IsArray) throw new ArgumentException(nameof(typeHandle)); - return TypeHandleFromAddress(methodTable.PerInstInfo); + return TypeHandleFromAddress(typeHandle.PerInstInfo); } - else if (typeHandle.IsTypeDesc) + else if (typeHandle.IsTypeDesc()) { - int TypeAndFlags = // Read TypeAndFlags field from TypeDesc contract using address typeHandle.AsTypeDesc.Address + int TypeAndFlags = // Read TypeAndFlags field from TypeDesc contract using address typeHandle.TypeDescAddress() CorElementType elemType = (CorElementType)(typeDesc.TypeAndFlags & 0xFF); switch (elemType) @@ -433,7 +473,7 @@ The contract additionally depends on these data descriptors case CorElementType.ValueType: case CorElementType.Byref: case CorElementType.Ptr: - TargetPointer typeArgPointer = // Read TypeArg field from ParamTypeDesc contract using address typeHandle.AsTypeDesc.Address + TargetPointer typeArgPointer = // Read TypeArg field from ParamTypeDesc contract using address typeHandle.TypeDescAddress() return TypeHandleFromAddress(typeArgPointer); } } @@ -445,19 +485,18 @@ The contract additionally depends on these data descriptors module = TargetPointer.Null; token = 0; - if (!typeHandle.IsTypeDesc) + if (!typeHandle.IsTypeDesc()) return false; - int TypeAndFlags = // Read TypeAndFlags field from TypeDesc contract using address typeHandle.AsTypeDesc.Address + int TypeAndFlags = // Read TypeAndFlags field from TypeDesc contract using address typeHandle.TypeDescAddress() CorElementType elemType = (CorElementType)(typeDesc.TypeAndFlags & 0xFF); switch (elemType) { case CorElementType.MVar: case CorElementType.Var: - TypeVarTypeDesc typeVarTypeDesc = _target.ProcessedData.GetOrAdd(typeHandle.AsTypeDesc.Address); - module = // Read Module field from TypeVarTypeDesc contract using address typeHandle.AsTypeDesc.Address - token = // Read Module field from TypeVarTypeDesc contract using address typeHandle.AsTypeDesc.Address + 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; @@ -468,26 +507,24 @@ The contract additionally depends on these data descriptors retAndArgTypes = default; callConv = default; - if (!typeHandle.IsTypeDesc) + if (!typeHandle.IsTypeDesc()) return false; - int TypeAndFlags = // Read TypeAndFlags field from TypeDesc contract using address typeHandle.AsTypeDesc.Address + 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.AsTypeDesc.Address - TargetPointer RetAndArgTypes = // Read NumArgs field from FnPtrTypeDesc contract using address typeHandle.AsTypeDesc.Address + int NumArgs = // Read NumArgs field from FnPtrTypeDesc contract using address typeHandle.TypeDescAddress() + TargetPointer RetAndArgTypes = // Read NumArgs field from FnPtrTypeDesc contract using address typeHandle.TypeDescAddress() - MethodTableHandle[] retAndArgTypesArray = new TypeHandle[NumTypeArgs + 1]; + TypeHandle[] retAndArgTypesArray = new TypeHandle[NumTypeArgs + 1]; for (int i = 0; i <= NumTypeArgs; i++) - retAndArgTypesArray[i] = TypeHandleFromAddress(_target.ReadPointer(RetAndArgTypes + _target.PointerSize * i)); + retAndArgTypesArray[i] = GetTypeHandle(_target.ReadPointer(RetAndArgTypes + _target.PointerSize * i)); - TypeHandleArray retAndArgTypesArray = _target.ProcessedData.GetOrAdd<(TargetPointer, int), TypeHandleArray> - ((fnPtrTypeDesc.RetAndArgTypes, checked((int)fnPtrTypeDesc.NumArgs + 1))); retAndArgTypes = retAndArgTypesArray; - callConv = (byte) // Read CallConv field from FnPtrTypeDesc contract using address typeHandle.AsTypeDesc.Address, and then ignore all but the low 8 bits. + 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/native/managed/cdacreader/src/Contracts/DacStreams_1.cs b/src/native/managed/cdacreader/src/Contracts/DacStreams_1.cs index cd43407ffc1f0..6d78b6161f90f 100644 --- a/src/native/managed/cdacreader/src/Contracts/DacStreams_1.cs +++ b/src/native/managed/cdacreader/src/Contracts/DacStreams_1.cs @@ -21,29 +21,29 @@ internal DacStreams_1(Target target) public virtual string? StringFromEEAddress(TargetPointer address) { - TargetPointer miniMetaDataBuffAddress = _target.ReadPointer(_target.ReadGlobalPointer(Constants.Globals.MiniMetaDataBuffAddress)); - uint miniMetaDataBuffMaxSize = _target.Read(_target.ReadGlobalPointer(Constants.Globals.MiniMetaDataBuffMaxSize)); - // We use the data subsystem to handle caching results from processing this data - var dictionary = _target.ProcessedData.GetOrAdd<(TargetPointer, uint), DacStreams_1_Data>((miniMetaDataBuffAddress, miniMetaDataBuffMaxSize)).EEObjectToString; + var dictionary = _target.ProcessedData.GetOrAdd(0).EEObjectToString; dictionary.TryGetValue(address, out string? result); return result; } - internal class DacStreams_1_Data : IData + internal class DacStreams_1_Data : IData { - static DacStreams_1_Data IData.Create(Target target, (TargetPointer, uint) key) => new DacStreams_1_Data(target, key); + static DacStreams_1_Data IData.Create(Target target, TargetPointer address) => new DacStreams_1_Data(target); - public DacStreams_1_Data(Target target, (TargetPointer miniMetaDataBuffAddress, uint miniMetaDataBuffMaxSize) key) + public DacStreams_1_Data(Target target) { - EEObjectToString = GetEEAddressToStringMap(target, key.miniMetaDataBuffAddress, key.miniMetaDataBuffMaxSize); + EEObjectToString = GetEEAddressToStringMap(target); } public readonly Dictionary EEObjectToString; - internal static Dictionary GetEEAddressToStringMap(Target target, TargetPointer miniMetaDataBuffAddress, uint miniMetaDataBuffMaxSize) + internal static Dictionary GetEEAddressToStringMap(Target target) { + TargetPointer miniMetaDataBuffAddress = target.ReadPointer(target.ReadGlobalPointer(Constants.Globals.MiniMetaDataBuffAddress)); + uint miniMetaDataBuffMaxSize = target.Read(target.ReadGlobalPointer(Constants.Globals.MiniMetaDataBuffMaxSize)); + Dictionary stringToAddress = new(); if (miniMetaDataBuffMaxSize < 20) { diff --git a/src/native/managed/cdacreader/src/Contracts/RuntimeTypeSystem.cs b/src/native/managed/cdacreader/src/Contracts/RuntimeTypeSystem.cs index 9550a7339b12e..b7267b5f8c23c 100644 --- a/src/native/managed/cdacreader/src/Contracts/RuntimeTypeSystem.cs +++ b/src/native/managed/cdacreader/src/Contracts/RuntimeTypeSystem.cs @@ -6,49 +6,17 @@ namespace Microsoft.Diagnostics.DataContractReader.Contracts; -// an opaque handle to a method table. See IMetadata.GetMethodTableData -internal readonly struct MethodTableHandle -{ - internal MethodTableHandle(TargetPointer address) - { - Address = address; - } - - internal TargetPointer Address { get; } -} - -internal readonly struct TypeDescHandle +// an opaque handle to a type handle. See IMetadata.GetMethodTableData +internal readonly struct TypeHandle { - internal TypeDescHandle(TargetPointer address) + internal TypeHandle(TargetPointer address) { Address = address; } internal TargetPointer Address { get; } -} - -internal readonly struct TypeHandle -{ - private readonly MethodTableHandle? _mtHandle; - private readonly TypeDescHandle? _typeDescHandle; - - internal TypeHandle(MethodTableHandle mtHandle) - { - _mtHandle = mtHandle; - } - internal TypeHandle(TypeDescHandle typeDescHandle) - { - _typeDescHandle = typeDescHandle; - } - - public static implicit operator TypeHandle(MethodTableHandle mtHandle) => new TypeHandle(mtHandle); - - public MethodTableHandle AsMethodTable => _mtHandle!.Value; - public bool IsMethodTable => _mtHandle.HasValue; - public TypeDescHandle AsTypeDesc => _typeDescHandle!.Value; - public bool IsTypeDesc => _typeDescHandle.HasValue; - public bool IsNull => !_mtHandle.HasValue; + internal bool IsNull => Address == 0; } internal enum CorElementType @@ -101,43 +69,41 @@ 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(); + public virtual uint GetTypeDefTypeAttributes(TypeHandle typeHandle) => throw new NotImplementedException(); - public virtual ReadOnlySpan GetInstantiation(MethodTableHandle methodTable) => throw new NotImplementedException(); - public virtual bool IsGenericTypeDefinition(MethodTableHandle methodTable) => throw new NotImplementedException(); - #endregion MethodTable inspection APIs + public virtual ReadOnlySpan GetInstantiation(TypeHandle typeHandle) => throw new NotImplementedException(); + public virtual bool IsGenericTypeDefinition(TypeHandle typeHandle) => throw new NotImplementedException(); - #region TypeHandle inspection APIs - public virtual TypeHandle TypeHandleFromAddress(TargetPointer address) => 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 + // 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. @@ -146,30 +112,6 @@ static IContract IContract.Create(Target target, int version) 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 - - // Default implementation is implemented in terms of other apis already on RuntimeTypeSystem - public virtual TargetPointer GetModule(TypeHandle typeHandle) - { - if (typeHandle.IsMethodTable) - return GetModule(typeHandle.AsMethodTable); - else - { - 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; - } - } - } - #endregion TypeHandle inspection APIs } diff --git a/src/native/managed/cdacreader/src/Contracts/RuntimeTypeSystem_1.cs b/src/native/managed/cdacreader/src/Contracts/RuntimeTypeSystem_1.cs index dcb6b59486bf7..9c3c3bc3f4974 100644 --- a/src/native/managed/cdacreader/src/Contracts/RuntimeTypeSystem_1.cs +++ b/src/native/managed/cdacreader/src/Contracts/RuntimeTypeSystem_1.cs @@ -5,11 +5,12 @@ 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; @@ -56,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; @@ -64,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 @@ -87,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 @@ -101,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(TypeHandle typeHandle) => !typeHandle.IsMethodTable() ? (uint)0 : _methodTables[typeHandle.Address].Flags.ComponentSize; - public uint GetComponentSize(MethodTableHandle methodTableHandle) => _methodTables[methodTableHandle.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: @@ -127,81 +153,116 @@ 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(MethodTableHandle methodTableHandle) => _methodTables[methodTableHandle.Address].Module; - public TargetPointer GetParentMethodTable(MethodTableHandle methodTableHandle) => _methodTables[methodTableHandle.Address].ParentMethodTable; + 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 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(MethodTableHandle methodTableHandle) => _methodTables[methodTableHandle.Address].NumInterfaces; + public ushort GetNumInterfaces(TypeHandle typeHandle) => !typeHandle.IsMethodTable() ? (ushort)0 : _methodTables[typeHandle.Address].NumInterfaces; - public uint GetTypeDefTypeAttributes(MethodTableHandle methodTableHandle) => GetClassData(methodTableHandle).CorTypeAttr; + public uint GetTypeDefTypeAttributes(TypeHandle typeHandle) => !typeHandle.IsMethodTable() ? (uint)0 : GetClassData(typeHandle).CorTypeAttr; - public bool IsDynamicStatics(MethodTableHandle methodTableHandle) => _methodTables[methodTableHandle.Address].Flags.IsDynamicStatics; + public bool IsDynamicStatics(TypeHandle typeHandle) => !typeHandle.IsMethodTable() ? false : _methodTables[typeHandle.Address].Flags.IsDynamicStatics; - public ReadOnlySpan GetInstantiation(MethodTableHandle methodTableHandle) + public ReadOnlySpan GetInstantiation(TypeHandle typeHandle) { - MethodTable methodTable = _methodTables[methodTableHandle.Address]; + if (!typeHandle.IsMethodTable()) + return default; + + MethodTable methodTable = _methodTables[typeHandle.Address]; if (!methodTable.Flags.HasInstantiation) return default; - TargetPointer perInstInfo = methodTable.PerInstInfo; - TargetPointer genericsDictInfo = perInstInfo - (ulong)_target.PointerSize; + return _target.ProcessedData.GetOrAdd(typeHandle.Address).TypeHandles; + } - TargetPointer dictionaryPointer = _target.ReadPointer(perInstInfo); + private class TypeInstantiation : IData + { + public static TypeInstantiation Create(Target target, TargetPointer address) => new TypeInstantiation(target, address); - int numberOfGenericArgs = _target.ProcessedData.GetOrAdd(genericsDictInfo).NumTypeArgs; - MethodTableArray instantiation = _target.ProcessedData.GetOrAdd<(TargetPointer, int), MethodTableArray> - ((dictionaryPointer, numberOfGenericArgs)); + 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); - return instantiation.Types.AsSpan(); - } + TargetPointer perInstInfo = methodTable.PerInstInfo; + TargetPointer genericsDictInfo = perInstInfo - (ulong)target.PointerSize; - public bool IsGenericTypeDefinition(MethodTableHandle methodTableHandle) => _methodTables[methodTableHandle.Address].Flags.IsGenericTypeDefinition; + TargetPointer dictionaryPointer = target.ReadPointer(perInstInfo); - public TypeHandle TypeHandleFromAddress(TargetPointer address) - { - if (address == 0) - return default; - if (((ulong)address & 2) != 0) - { - return new TypeHandle(new TypeDescHandle(address - 2)); - } - else - { - return new TypeHandle(GetMethodTableHandle(address)); + 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) + if (typeHandle.IsMethodTable()) { - MethodTable methodTable = _methodTables[typeHandle.AsMethodTable.Address]; + MethodTable methodTable = _methodTables[typeHandle.Address]; return methodTable.Flags.IsArray; } - else if (typeHandle.IsTypeDesc) + else if (typeHandle.IsTypeDesc()) { - var typeDesc = _target.ProcessedData.GetOrAdd(typeHandle.AsTypeDesc.Address); + var typeDesc = _target.ProcessedData.GetOrAdd(typeHandle.TypeDescAddress()); CorElementType elemType = (CorElementType)(typeDesc.TypeAndFlags & 0xFF); switch (elemType) { @@ -216,9 +277,9 @@ public bool HasTypeParam(TypeHandle typeHandle) public CorElementType GetSignatureCorElementType(TypeHandle typeHandle) { - if (typeHandle.IsMethodTable) + if (typeHandle.IsMethodTable()) { - MethodTable methodTable = _methodTables[typeHandle.AsMethodTable.Address]; + MethodTable methodTable = _methodTables[typeHandle.Address]; switch (methodTable.Flags.GetFlag(WFLAGS_HIGH.Category_Mask)) { @@ -231,30 +292,31 @@ public CorElementType GetSignatureCorElementType(TypeHandle typeHandle) case WFLAGS_HIGH.Category_PrimitiveValueType: return CorElementType.ValueType; case WFLAGS_HIGH.Category_TruePrimitive: - return (CorElementType)GetClassData(typeHandle.AsMethodTable).InternalCorElementType; + return (CorElementType)GetClassData(typeHandle).InternalCorElementType; default: return CorElementType.Class; } } - else if (typeHandle.IsTypeDesc) + else if (typeHandle.IsTypeDesc()) { - var typeDesc = _target.ProcessedData.GetOrAdd(typeHandle.AsTypeDesc.Address); + var typeDesc = _target.ProcessedData.GetOrAdd(typeHandle.TypeDescAddress()); return (CorElementType)(typeDesc.TypeAndFlags & 0xFF); } - return default(CorElementType); + + 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) + if (typeHandle.IsMethodTable()) { - MethodTable methodTable = _methodTables[typeHandle.AsMethodTable.Address]; + MethodTable methodTable = _methodTables[typeHandle.Address]; switch (methodTable.Flags.GetFlag(WFLAGS_HIGH.Category_Mask)) { case WFLAGS_HIGH.Category_Array: - TargetPointer clsPtr = GetClassPointer(typeHandle.AsMethodTable); + TargetPointer clsPtr = GetClassPointer(typeHandle); rank = _target.ProcessedData.GetOrAdd(clsPtr).Rank; return true; @@ -270,25 +332,25 @@ public bool IsArray(TypeHandle typeHandle, out uint rank) public TypeHandle GetTypeParam(TypeHandle typeHandle) { - if (typeHandle.IsMethodTable) + if (typeHandle.IsMethodTable()) { - MethodTable methodTable = _methodTables[typeHandle.AsMethodTable.Address]; + MethodTable methodTable = _methodTables[typeHandle.Address]; if (!methodTable.Flags.IsArray) throw new ArgumentException(nameof(typeHandle)); - return TypeHandleFromAddress(methodTable.PerInstInfo); + return GetTypeHandle(methodTable.PerInstInfo); } - else if (typeHandle.IsTypeDesc) + else if (typeHandle.IsTypeDesc()) { - var typeDesc = _target.ProcessedData.GetOrAdd(typeHandle.AsTypeDesc.Address); + 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.AsTypeDesc.Address); - return TypeHandleFromAddress(paramTypeDesc.TypeArg); + ParamTypeDesc paramTypeDesc = _target.ProcessedData.GetOrAdd(typeHandle.TypeDescAddress()); + return GetTypeHandle(paramTypeDesc.TypeArg); } } throw new ArgumentException(nameof(typeHandle)); @@ -299,16 +361,16 @@ public bool IsGenericVariable(TypeHandle typeHandle, out TargetPointer module, o module = TargetPointer.Null; token = 0; - if (!typeHandle.IsTypeDesc) + if (!typeHandle.IsTypeDesc()) return false; - var typeDesc = _target.ProcessedData.GetOrAdd(typeHandle.AsTypeDesc.Address); + 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.AsTypeDesc.Address); + TypeVarTypeDesc typeVarTypeDesc = _target.ProcessedData.GetOrAdd(typeHandle.TypeDescAddress()); module = typeVarTypeDesc.Module; token = typeVarTypeDesc.Token; return true; @@ -321,20 +383,38 @@ public bool IsFunctionPointer(TypeHandle typeHandle, out ReadOnlySpan(typeHandle.AsTypeDesc.Address); + var typeDesc = _target.ProcessedData.GetOrAdd(typeHandle.TypeDescAddress()); CorElementType elemType = (CorElementType)(typeDesc.TypeAndFlags & 0xFF); if (elemType != CorElementType.FnPtr) return false; - FnPtrTypeDesc fnPtrTypeDesc = _target.ProcessedData.GetOrAdd(typeHandle.AsTypeDesc.Address); - - TypeHandleArray retAndArgTypesArray = _target.ProcessedData.GetOrAdd<(TargetPointer, int), TypeHandleArray> - ((fnPtrTypeDesc.RetAndArgTypes, checked((int)fnPtrTypeDesc.NumArgs + 1))); - retAndArgTypes = retAndArgTypesArray.Types; + 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 0000000000000..355897000ec69 --- /dev/null +++ b/src/native/managed/cdacreader/src/Contracts/RuntimeTypeSystem_1_Helpers.cs @@ -0,0 +1,31 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using System.Reflection.Metadata.Ecma335; +using Microsoft.Diagnostics.DataContractReader.Data; +using Microsoft.Diagnostics.DataContractReader.Contracts.RuntimeTypeSystem_1_NS; + +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/IData.cs b/src/native/managed/cdacreader/src/Data/IData.cs index 8ed7785728621..65b7be805c838 100644 --- a/src/native/managed/cdacreader/src/Data/IData.cs +++ b/src/native/managed/cdacreader/src/Data/IData.cs @@ -7,8 +7,3 @@ internal interface IData where TSelf : IData { static abstract TSelf Create(Target target, TargetPointer address); } - -internal interface IData where TSelf : IData -{ - static abstract TSelf Create(Target target, TKey address); -} diff --git a/src/native/managed/cdacreader/src/Data/MethodTableArray.cs b/src/native/managed/cdacreader/src/Data/MethodTableArray.cs deleted file mode 100644 index e3b34fce83b63..0000000000000 --- a/src/native/managed/cdacreader/src/Data/MethodTableArray.cs +++ /dev/null @@ -1,33 +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 Microsoft.Diagnostics.DataContractReader.Contracts; - -namespace Microsoft.Diagnostics.DataContractReader.Data; - -internal sealed class MethodTableArray : IData -{ - public static MethodTableArray Create(Target target, (TargetPointer ptr, int size) address) - => new MethodTableArray(target, address); - - public readonly MethodTableHandle[] Types; - - public MethodTableArray(Target target, (TargetPointer ptr, int size) key) - { - Span targetPointerSpan = stackalloc TargetPointer[4]; - Types = new MethodTableHandle[key.size]; - Span instantiationSpan = Types.AsSpan(); - if (key.size > targetPointerSpan.Length) - { - targetPointerSpan = new TargetPointer[key.size]; - } - - target.ReadPointers(key.ptr, targetPointerSpan); - - for (int i = 0; i < key.size; i++) - { - instantiationSpan[i] = target.Contracts.RuntimeTypeSystem.GetMethodTableHandle(targetPointerSpan[i]); - } - } -} diff --git a/src/native/managed/cdacreader/src/Data/TypeHandleArray.cs b/src/native/managed/cdacreader/src/Data/TypeHandleArray.cs deleted file mode 100644 index f377132621436..0000000000000 --- a/src/native/managed/cdacreader/src/Data/TypeHandleArray.cs +++ /dev/null @@ -1,33 +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 Microsoft.Diagnostics.DataContractReader.Contracts; - -namespace Microsoft.Diagnostics.DataContractReader.Data; - -internal sealed class TypeHandleArray : IData -{ - public static TypeHandleArray Create(Target target, (TargetPointer ptr, int size) address) - => new TypeHandleArray(target, address); - - public readonly TypeHandle[] Types; - - public TypeHandleArray(Target target, (TargetPointer ptr, int size) key) - { - Span targetPointerSpan = stackalloc TargetPointer[4]; - Types = new TypeHandle[key.size]; - Span instantiationSpan = Types.AsSpan(); - if (key.size > targetPointerSpan.Length) - { - targetPointerSpan = new TargetPointer[key.size]; - } - - target.ReadPointers(key.ptr, targetPointerSpan); - - for (int i = 0; i < key.size; i++) - { - instantiationSpan[i] = target.Contracts.RuntimeTypeSystem.TypeHandleFromAddress(targetPointerSpan[i]); - } - } -} diff --git a/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs b/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs index d707fddaa44db..961a8c861ed97 100644 --- a/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs +++ b/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs @@ -93,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); @@ -141,7 +141,7 @@ 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; } @@ -175,7 +175,7 @@ public unsafe int GetMethodTableName(ulong mt, uint count, char* mtName, uint* p try { Contracts.IRuntimeTypeSystem typeSystemContract = _target.Contracts.RuntimeTypeSystem; - Contracts.MethodTableHandle methodTableHandle = typeSystemContract.GetMethodTableHandle(mt); + Contracts.TypeHandle methodTableHandle = typeSystemContract.GetTypeHandle(mt); if (typeSystemContract.IsFreeObjectMethodTable(methodTableHandle)) { CopyStringToTargetBuffer(mtName, count, pNeeded, "Free"); @@ -188,7 +188,7 @@ public unsafe int GetMethodTableName(ulong mt, uint count, char* mtName, uint* p try { TargetPointer modulePointer = typeSystemContract.GetModule(methodTableHandle); - TypeNameBuilder.AppendType(_target, methodTableName, new TypeHandle(methodTableHandle), TypeNameFormat.FormatNamespace | TypeNameFormat.FormatFullInst); + TypeNameBuilder.AppendType(_target, methodTableName, methodTableHandle, TypeNameFormat.FormatNamespace | TypeNameFormat.FormatFullInst); } catch { diff --git a/src/native/managed/cdacreader/src/Legacy/TypeNameBuilder.cs b/src/native/managed/cdacreader/src/Legacy/TypeNameBuilder.cs index 3f942f2469a92..510367bd12480 100644 --- a/src/native/managed/cdacreader/src/Legacy/TypeNameBuilder.cs +++ b/src/native/managed/cdacreader/src/Legacy/TypeNameBuilder.cs @@ -55,12 +55,12 @@ public static void AppendType(Target target, StringBuilder stringBuilder, Contra { AppendType(target, stringBuilder, typeHandle, default, format); } - public static void AppendType(Target target, StringBuilder stringBuilder, Contracts.TypeHandle typeHandle, ReadOnlySpan typeInstantiation, TypeNameFormat 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) + 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); @@ -77,13 +77,13 @@ private static void AppendTypeCore(ref TypeNameBuilder tnb, Contracts.TypeHandle if (elemType != Contracts.CorElementType.ValueType) { typeSystemContract.IsArray(typeHandle, out uint rank); - AppendTypeCore(ref tnb, typeSystemContract.GetTypeParam(typeHandle), default(ReadOnlySpan), (TypeNameFormat)(format & ~TypeNameFormat.FormatAssembly)); + 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); + AppendTypeCore(ref tnb, typeSystemContract.GetTypeParam(typeHandle), Array.Empty(), format & ~TypeNameFormat.FormatAssembly); } } else if (typeSystemContract.IsGenericVariable(typeHandle, out TargetPointer modulePointer, out uint genericParamToken)) @@ -141,9 +141,8 @@ private static void AppendTypeCore(ref TypeNameBuilder tnb, Contracts.TypeHandle else { // ...otherwise it's just a plain type def or an instantiated type - MethodTableHandle methodTable = typeHandle.AsMethodTable; - uint typeDefToken = typeSystemContract.GetTypeDefToken(methodTable); - Contracts.ModuleHandle moduleHandle = tnb.Target.Contracts.Loader.GetModuleHandle(typeSystemContract.GetModule(methodTable)); + uint typeDefToken = typeSystemContract.GetTypeDefToken(typeHandle); + Contracts.ModuleHandle moduleHandle = tnb.Target.Contracts.Loader.GetModuleHandle(typeSystemContract.GetModule(typeHandle)); if (EcmaMetadataReader.RidFromToken(typeDefToken) == 0) { tnb.AddName("(dynamicClass)"); @@ -158,9 +157,9 @@ private static void AppendTypeCore(ref TypeNameBuilder tnb, Contracts.TypeHandle if (format.HasFlag(TypeNameFormat.FormatNamespace) || format.HasFlag(TypeNameFormat.FormatAssembly)) { - ReadOnlySpan instantiationSpan = typeSystemContract.GetInstantiation(methodTable); + ReadOnlySpan instantiationSpan = typeSystemContract.GetInstantiation(typeHandle); - if ((instantiationSpan.Length > 0) && (!typeSystemContract.IsGenericTypeDefinition(methodTable) || toString)) + if ((instantiationSpan.Length > 0) && (!typeSystemContract.IsGenericTypeDefinition(typeHandle) || toString)) { if (instantiation.Length == 0) { @@ -185,10 +184,10 @@ private static void AppendTypeCore(ref TypeNameBuilder tnb, Contracts.TypeHandle } } - private static void AppendInst(ref TypeNameBuilder tnb, ReadOnlySpan inst, TypeNameFormat format) + private static void AppendInst(ref TypeNameBuilder tnb, ReadOnlySpan inst, TypeNameFormat format) { tnb.OpenGenericArguments(); - foreach (MethodTableHandle arg in inst) + foreach (TypeHandle arg in inst) { tnb.OpenGenericArgument(); if (format.HasFlag(TypeNameFormat.FormatFullInst) && !tnb.Target.Contracts.RuntimeTypeSystem.IsGenericVariable(arg, out _, out _)) diff --git a/src/native/managed/cdacreader/src/Target.cs b/src/native/managed/cdacreader/src/Target.cs index 9039011c4bd69..3047866204858 100644 --- a/src/native/managed/cdacreader/src/Target.cs +++ b/src/native/managed/cdacreader/src/Target.cs @@ -423,7 +423,6 @@ internal sealed class DataCache { private readonly Target _target; private readonly Dictionary<(ulong, Type), object?> _readDataByAddress = []; - private readonly Dictionary _customKeyReadDataByAddress = []; public DataCache(Target target) { @@ -444,22 +443,6 @@ public T GetOrAdd(TargetPointer address) where T : IData return result!; } - public TValue GetOrAdd(TKey key) where TValue : IData where TKey : notnull, IEquatable - { - if (TryGet(key, out TValue? result)) - return result!; - - Dictionary dictionary = (Dictionary)_customKeyReadDataByAddress[typeof(Tuple)]; - - TValue constructed = TValue.Create(_target, key); - if (dictionary.TryAdd(key, constructed)) - return constructed; - - bool found = TryGet(key, out result); - Debug.Assert(found); - return result!; - } - public bool TryGet(ulong address, [NotNullWhen(true)] out T? data) { data = default; @@ -474,17 +457,6 @@ public bool TryGet(ulong address, [NotNullWhen(true)] out T? data) return false; } - - public bool TryGet(TKey key, [NotNullWhen(true)] out TValue? data) where TValue : IData where TKey : notnull, IEquatable - { - if (!_customKeyReadDataByAddress.TryGetValue(typeof(Tuple), out var dictionaryObject)) - { - dictionaryObject = new Dictionary(); - _customKeyReadDataByAddress.Add(typeof(Tuple), dictionaryObject); - } - Dictionary dictionary = (Dictionary)dictionaryObject; - return dictionary.TryGetValue(key, out data); - } } private sealed class Reader From 30da32c88a4ea88f8f4d0eff49c3d31f32c08ef9 Mon Sep 17 00:00:00 2001 From: David Wrighton Date: Mon, 15 Jul 2024 15:51:42 -0700 Subject: [PATCH 09/15] Address concern around data structure for DynamicMetadata --- docs/design/datacontracts/Loader.md | 22 +++++++++++++--- src/coreclr/debug/daccess/daccess.cpp | 4 +-- src/coreclr/debug/daccess/dacdbiimpl.cpp | 4 +-- .../debug/runtimeinfo/datadescriptor.h | 5 ++++ src/coreclr/vm/ceeload.cpp | 9 ++++--- src/coreclr/vm/ceeload.h | 14 +++++++++++ .../cdacreader/src/Contracts/Loader_1.cs | 5 ++-- .../cdacreader/src/Data/DynamicMetadata.cs | 25 +++++++++++++++++++ src/native/managed/cdacreader/src/DataType.cs | 1 + 9 files changed, 76 insertions(+), 13 deletions(-) create mode 100644 src/native/managed/cdacreader/src/Data/DynamicMetadata.cs diff --git a/docs/design/datacontracts/Loader.md b/docs/design/datacontracts/Loader.md index 962226ec41d7e..bc45eaee7a671 100644 --- a/docs/design/datacontracts/Loader.md +++ b/docs/design/datacontracts/Loader.md @@ -114,7 +114,22 @@ 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) @@ -186,8 +201,9 @@ TargetPointer ILoader.GetReadWriteSavedMetadataAddress(ModuleHandle handle, out { Data.Module module = _target.ProcessedData.GetOrAdd(handle.Address); TargetPointer dynamicMetadata = target.ReadPointer(handle.Address + /* Module::DynamicMetadata offset */); - size = _target.Read(dynamicMetadata); - TargetPointer result = module.DynamicMetadata + (ulong)_target.PointerSize; + + size = target.Read(handle.Address + /* DynamicMetadata::Size offset */); + TargetPointer result = handle.Address + /* DynamicMetadata::Data offset */; return result; } diff --git a/src/coreclr/debug/daccess/daccess.cpp b/src/coreclr/debug/daccess/daccess.cpp index 7666e634a19a7..71314aa050617 100644 --- a/src/coreclr/debug/daccess/daccess.cpp +++ b/src/coreclr/debug/daccess/daccess.cpp @@ -6716,8 +6716,8 @@ ClrDataAccess::GetMDImport(const PEAssembly* pPEAssembly, const ReflectionModule TADDR metadataBuffer = reflectionModule->GetDynamicMetadataBuffer(); if (metadataBuffer != PTR_NULL) { - mdBaseTarget = dac_cast(metadataBuffer + sizeof(uint32_t)); - mdSize = *dac_cast(metadataBuffer); + 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 5e408eab3d73a..8dbd2cf2ca058 100644 --- a/src/coreclr/debug/daccess/dacdbiimpl.cpp +++ b/src/coreclr/debug/daccess/dacdbiimpl.cpp @@ -4287,8 +4287,8 @@ void DacDbiInterfaceImpl::GetMetadata(VMPTR_Module vmModule, TargetBuffer * pTar ReflectionModule * pReflectionModule = pModule->GetReflectionModule(); TADDR metadataBuffer = pReflectionModule->GetDynamicMetadataBuffer(); - CORDB_ADDRESS addr = PTR_TO_CORDB_ADDRESS(metadataBuffer + sizeof(TADDR)); - pTargetBuffer->Init(addr, *dac_cast(metadataBuffer)); + 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/runtimeinfo/datadescriptor.h b/src/coreclr/debug/runtimeinfo/datadescriptor.h index a5b937f51ee43..1bfc5617576e0 100644 --- a/src/coreclr/debug/runtimeinfo/datadescriptor.h +++ b/src/coreclr/debug/runtimeinfo/datadescriptor.h @@ -231,6 +231,11 @@ CDAC_TYPE_FIELD(FnPtrTypeDesc, /*uint32*/, CallConv, cdac_offsets 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() diff --git a/src/coreclr/vm/ceeload.cpp b/src/coreclr/vm/ceeload.cpp index 0efb59cc6da9e..fa48d45d6b051 100644 --- a/src/coreclr/vm/ceeload.cpp +++ b/src/coreclr/vm/ceeload.cpp @@ -3923,18 +3923,19 @@ void ReflectionModule::CaptureModuleMetaDataToMemory() IfFailThrow(hr); // Operate on local data, and then persist it into the module once we know it's valid. - int sizeInInts = (numBytes / sizeof(int32_t)) + 2; - NewArrayHolder pBuffer(new uint32_t[sizeInInts]); + 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 = (BYTE*)&pBuffer[1]; + BYTE * pRawData = &pDynamicMetadata->Data[0]; hr = pEmitter->SaveToMemory(pRawData, numBytes); - pBuffer[0] = numBytes; + pDynamicMetadata->Size = numBytes; IfFailThrow(hr); diff --git a/src/coreclr/vm/ceeload.h b/src/coreclr/vm/ceeload.h index 5fbbfd2307d63..9bcae6e145d84 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; diff --git a/src/native/managed/cdacreader/src/Contracts/Loader_1.cs b/src/native/managed/cdacreader/src/Contracts/Loader_1.cs index c5d096674462c..2c4aa6677f85c 100644 --- a/src/native/managed/cdacreader/src/Contracts/Loader_1.cs +++ b/src/native/managed/cdacreader/src/Contracts/Loader_1.cs @@ -77,8 +77,9 @@ AvailableMetadataType ILoader.GetAvailableMetadataType(ModuleHandle handle) TargetPointer ILoader.GetReadWriteSavedMetadataAddress(ModuleHandle handle, out ulong size) { Data.Module module = _target.ProcessedData.GetOrAdd(handle.Address); - TargetPointer result = module.DynamicMetadata + (ulong)_target.PointerSize; - size = _target.Read(module.DynamicMetadata); + Data.DynamicMetadata dynamicMetadata = _target.ProcessedData.GetOrAdd(module.DynamicMetadata); + TargetPointer result = dynamicMetadata.Data; + size = dynamicMetadata.Size; return result; } 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 0000000000000..ce7fa36caa4dd --- /dev/null +++ b/src/native/managed/cdacreader/src/Data/DynamicMetadata.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. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +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/DataType.cs b/src/native/managed/cdacreader/src/DataType.cs index cfcc7e93f362a..da077edbad23e 100644 --- a/src/native/managed/cdacreader/src/DataType.cs +++ b/src/native/managed/cdacreader/src/DataType.cs @@ -35,4 +35,5 @@ public enum DataType ParamTypeDesc, TypeVarTypeDesc, FnPtrTypeDesc, + DynamicMetadata, } From 143cb86a53c247473946e4392201d0e27e1b4074 Mon Sep 17 00:00:00 2001 From: David Wrighton Date: Mon, 15 Jul 2024 16:01:11 -0700 Subject: [PATCH 10/15] Fix build break --- src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs b/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs index e24f120059726..e4f2968f29f79 100644 --- a/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs +++ b/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs @@ -300,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; } From 7284f83c3e58ef46112e2467803f77f65e3ddbf3 Mon Sep 17 00:00:00 2001 From: David Wrighton Date: Tue, 16 Jul 2024 10:36:21 -0700 Subject: [PATCH 11/15] Fix musl build issue --- src/coreclr/vm/ceeload.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/coreclr/vm/ceeload.cpp b/src/coreclr/vm/ceeload.cpp index fa48d45d6b051..75e93bcdbee39 100644 --- a/src/coreclr/vm/ceeload.cpp +++ b/src/coreclr/vm/ceeload.cpp @@ -344,7 +344,7 @@ Module::Module(Assembly *pAssembly, PEAssembly *pPEAssembly) m_pAssembly = pAssembly; m_pPEAssembly = pPEAssembly; m_dwTransientFlags = CLASSES_FREED; - m_pDynamicMetadata = NULL; + m_pDynamicMetadata = (TADDR)NULL; pPEAssembly->AddRef(); } @@ -3807,7 +3807,7 @@ void ReflectionModule::Destruct() Module::Destruct(); delete (uint32_t*)m_pDynamicMetadata; - m_pDynamicMetadata = NULL; + m_pDynamicMetadata = (TADDR)NULL; m_CrstLeafLock.Destroy(); } From 54ab23e35697b8d2bf793b703e03128449cecd4b Mon Sep 17 00:00:00 2001 From: Aleksey Kliger Date: Tue, 16 Jul 2024 16:10:38 -0400 Subject: [PATCH 12/15] update tests --- .../cdacreader/tests/MethodTableTests.cs | 40 ++++++++++--------- 1 file changed, 21 insertions(+), 19 deletions(-) diff --git a/src/native/managed/cdacreader/tests/MethodTableTests.cs b/src/native/managed/cdacreader/tests/MethodTableTests.cs index dd451fa075f6a..1e2c2cf53d9fa 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)); }); } From 2a41206b5000340eb71c8af5440453b13eb50b3a Mon Sep 17 00:00:00 2001 From: David Wrighton Date: Tue, 16 Jul 2024 13:46:53 -0700 Subject: [PATCH 13/15] Remove unnecessary usings --- .../cdacreader/src/Contracts/RuntimeTypeSystem_1_Helpers.cs | 6 ------ src/native/managed/cdacreader/src/Data/DynamicMetadata.cs | 6 ------ src/native/managed/cdacreader/src/Data/EEClass.cs | 2 -- src/native/managed/cdacreader/src/Data/GenericsDictInfo.cs | 6 ------ src/native/managed/cdacreader/src/Data/MethodTable.cs | 2 -- .../managed/cdacreader/src/Data/MethodTableAuxiliaryData.cs | 2 -- src/native/managed/cdacreader/src/Data/TypeDesc.cs | 6 ------ src/native/managed/cdacreader/src/Helpers/Metadata.cs | 6 ------ src/native/managed/cdacreader/src/Target.cs | 1 - 9 files changed, 37 deletions(-) diff --git a/src/native/managed/cdacreader/src/Contracts/RuntimeTypeSystem_1_Helpers.cs b/src/native/managed/cdacreader/src/Contracts/RuntimeTypeSystem_1_Helpers.cs index 355897000ec69..af1e23df461b9 100644 --- a/src/native/managed/cdacreader/src/Contracts/RuntimeTypeSystem_1_Helpers.cs +++ b/src/native/managed/cdacreader/src/Contracts/RuntimeTypeSystem_1_Helpers.cs @@ -1,12 +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; -using System.Collections.Generic; -using System.Reflection.Metadata.Ecma335; -using Microsoft.Diagnostics.DataContractReader.Data; -using Microsoft.Diagnostics.DataContractReader.Contracts.RuntimeTypeSystem_1_NS; - namespace Microsoft.Diagnostics.DataContractReader.Contracts.RuntimeTypeSystem_1_NS; internal static class RuntimeTypeSystem_1_Helpers diff --git a/src/native/managed/cdacreader/src/Data/DynamicMetadata.cs b/src/native/managed/cdacreader/src/Data/DynamicMetadata.cs index ce7fa36caa4dd..fc1ac8c90963f 100644 --- a/src/native/managed/cdacreader/src/Data/DynamicMetadata.cs +++ b/src/native/managed/cdacreader/src/Data/DynamicMetadata.cs @@ -1,12 +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; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - namespace Microsoft.Diagnostics.DataContractReader.Data; internal class DynamicMetadata : IData diff --git a/src/native/managed/cdacreader/src/Data/EEClass.cs b/src/native/managed/cdacreader/src/Data/EEClass.cs index 531a30389e41c..ea863ce178e51 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 diff --git a/src/native/managed/cdacreader/src/Data/GenericsDictInfo.cs b/src/native/managed/cdacreader/src/Data/GenericsDictInfo.cs index a25f4a87c8471..7aad8f95301d7 100644 --- a/src/native/managed/cdacreader/src/Data/GenericsDictInfo.cs +++ b/src/native/managed/cdacreader/src/Data/GenericsDictInfo.cs @@ -1,12 +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; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - namespace Microsoft.Diagnostics.DataContractReader.Data; internal class GenericsDictInfo : IData diff --git a/src/native/managed/cdacreader/src/Data/MethodTable.cs b/src/native/managed/cdacreader/src/Data/MethodTable.cs index b1b6723d55654..6461a9521f472 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 diff --git a/src/native/managed/cdacreader/src/Data/MethodTableAuxiliaryData.cs b/src/native/managed/cdacreader/src/Data/MethodTableAuxiliaryData.cs index bdc711a510473..e04e5b56b0c54 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/TypeDesc.cs b/src/native/managed/cdacreader/src/Data/TypeDesc.cs index 5cef78482999d..d807f91a36500 100644 --- a/src/native/managed/cdacreader/src/Data/TypeDesc.cs +++ b/src/native/managed/cdacreader/src/Data/TypeDesc.cs @@ -1,12 +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; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - namespace Microsoft.Diagnostics.DataContractReader.Data; internal class TypeDesc : IData diff --git a/src/native/managed/cdacreader/src/Helpers/Metadata.cs b/src/native/managed/cdacreader/src/Helpers/Metadata.cs index f861794529346..5e683d5c72b05 100644 --- a/src/native/managed/cdacreader/src/Helpers/Metadata.cs +++ b/src/native/managed/cdacreader/src/Helpers/Metadata.cs @@ -3,13 +3,7 @@ using System; using System.Collections.Generic; -using System.Drawing; -using System.Linq; -using System.Net; -using System.Text; -using System.Threading.Tasks; using Microsoft.Diagnostics.DataContractReader.Contracts; -using static System.Net.Mime.MediaTypeNames; namespace Microsoft.Diagnostics.DataContractReader.Helpers; diff --git a/src/native/managed/cdacreader/src/Target.cs b/src/native/managed/cdacreader/src/Target.cs index 3047866204858..244b917a508d8 100644 --- a/src/native/managed/cdacreader/src/Target.cs +++ b/src/native/managed/cdacreader/src/Target.cs @@ -5,7 +5,6 @@ using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; -using System.Net; using System.Numerics; using System.Runtime.CompilerServices; using Microsoft.Diagnostics.DataContractReader.Data; From dbe3cf45447baf4a8688ce6ec095c9bca2d98152 Mon Sep 17 00:00:00 2001 From: David Wrighton Date: Tue, 16 Jul 2024 15:42:44 -0700 Subject: [PATCH 14/15] - Transform magic numbers from DacStreams implementation into constants - Add test suite for the DacStreams contract --- docs/design/datacontracts/DacStreams.md | 89 +++---- .../design/datacontracts/RuntimeTypeSystem.md | 8 +- .../cdacreader/src/Contracts/DacStreams_1.cs | 40 ++- .../cdacreader/tests/DacStreamsTests.cs | 244 ++++++++++++++++++ .../cdacreader/tests/MockMemorySpace.cs | 40 ++- 5 files changed, 339 insertions(+), 82 deletions(-) create mode 100644 src/native/managed/cdacreader/tests/DacStreamsTests.cs diff --git a/docs/design/datacontracts/DacStreams.md b/docs/design/datacontracts/DacStreams.md index aaceda4699982..4ddc1eb84ad3a 100644 --- a/docs/design/datacontracts/DacStreams.md +++ b/docs/design/datacontracts/DacStreams.md @@ -17,65 +17,50 @@ Global variables used | MiniMetaDataBuffAddress | TargetPointer | Identify where the mini metadata stream exists | | MiniMetaDataBuffMaxSize | uint | Identify where the size of the mini metadata stream | -``` csharp -string StringFromEEAddress(TargetPointer address) -{ - TargetPointer miniMetaDataBuffAddress = _target.Read(_target.ReadGlobalPointer(Constants.Globals.MiniMetaDataBuffAddress)); - uint miniMetaDataBuffMaxSize = _target.Read(_target.ReadGlobalPointer(Constants.Globals.MiniMetaDataBuffMaxSize)); +Magic numbers +| Name | Value | +| --- | --- | +| MiniMetadataSignature | 0x6d727473 | +| EENameStreamSignature | 0x614e4545 | + +The format of the MiniMetadataStream begins with a Streams header, which has 3 fields - if (miniMetaDataBuffMaxSize < 20) - { - // buffer isn't long enough to hold required headers - return null; - } +| 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 | - if (_target.Read(miniMetaDataBuffAddress) != 0x6d727473) - { - // Magic number is incorrect, data is either corrupt, or this is not a crash dump with embedded mini metadata streams - return null; - } +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. - uint totalSize = _target.Read(miniMetaDataBuffAddress + 0x4); - if (totalSize > miniMetaDataBuffMaxSize) - { - // totalSize is inconsistent with miniMetaDataBuffMaxSize - return stringToAddress; - } - uint countStreams = _target.Read(miniMetaDataBuffAddress + 0x8); - if (countStreams != 1) - { - // This implementation is only aware of 1 possible stream type, so only 1 can exist - return stringToAddress; - } - uint eeNameSig = _target.Read(miniMetaDataBuffAddress + 0xC); - if (eeNameSig != 0x614e4545) - { - // name of first stream is not 0x614e4545 == "EENa" - return stringToAddress; - } - uint countNames = _target.Read(miniMetaDataBuffAddress + 0x10); +The `EENameStream` is structured as a header, plus a series of null-terminated utf8 strings, and pointers. - uint currentOffset = 20; +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 | - for (int i = 0; i < countNames; i++) - { - if ((currentOffset + _target.PointerSize) > miniMetaDataBuffMaxSize) - break; - TargetPointer eeObjectPointer = _target.ReadPointer(miniMetaDataBuffAddress + currentOffset); - currentOffset += (uint)_target.PointerSize; - int stringLen = // Compute IndexOf null terminator starting at currentOffset, or -1 if it can't be found within miniMetaDataBuffMaxSize - if (stringLen == -1) - break; +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 | - if (eeObjectPointer != address) - { - currentOffset += stringLen + 1; - continue; - } +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. - return Encoding.UTF8.GetString(miniMdBuffer.Slice((int)currentOffset, stringLen)); - } +``` csharp +string StringFromEEAddress(TargetPointer address) +{ + TargetPointer miniMetaDataBuffAddress = _target.Read(_target.ReadGlobalPointer(Constants.Globals.MiniMetaDataBuffAddress)); + uint miniMetaDataBuffMaxSize = _target.Read(_target.ReadGlobalPointer(Constants.Globals.MiniMetaDataBuffMaxSize)); - return null; + // 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/RuntimeTypeSystem.md b/docs/design/datacontracts/RuntimeTypeSystem.md index 5399e83f5b4a3..864a225cd6bb0 100644 --- a/docs/design/datacontracts/RuntimeTypeSystem.md +++ b/docs/design/datacontracts/RuntimeTypeSystem.md @@ -11,16 +11,12 @@ Given a `TargetPointer` address, the `RuntimeTypeSystem` contract provides a `Ty ``` csharp struct TypeHandle { - // no public properties or constructors + // no public constructors public TargetPointer Address { get; } public bool IsNull => Address != 0; } -struct TypeHandle -{ -} - internal enum CorElementType { // Values defined in ECMA-335 - II.23.1.16 Element types used in signatures @@ -33,7 +29,7 @@ A `TypeHandle` is the runtime representation of the type information about a val struct TypeHandle ``` csharp - #region MethodTable inspection APIs + #region TypeHandle inspection APIs public virtual TypeHandle GetTypeHandle(TargetPointer targetPointer); public virtual TargetPointer GetModule(TypeHandle typeHandle); diff --git a/src/native/managed/cdacreader/src/Contracts/DacStreams_1.cs b/src/native/managed/cdacreader/src/Contracts/DacStreams_1.cs index 6d78b6161f90f..688ec1d5b8984 100644 --- a/src/native/managed/cdacreader/src/Contracts/DacStreams_1.cs +++ b/src/native/managed/cdacreader/src/Contracts/DacStreams_1.cs @@ -14,6 +14,18 @@ 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; @@ -43,6 +55,7 @@ internal static Dictionary GetEEAddressToStringMap(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) @@ -51,14 +64,14 @@ internal static Dictionary GetEEAddressToStringMap(Target return stringToAddress; } - if (target.Read(miniMetaDataBuffAddress) != 0x6d727473) + if (target.Read(miniMetaDataBuffAddress + MiniMetadataStream_MiniMetadataSignature_Offset) != MiniMetadataSignature) { // Magic number is incorrect return stringToAddress; } - uint totalSize = target.Read(miniMetaDataBuffAddress + 0x4); + uint totalSize = target.Read(miniMetaDataBuffAddress + MiniMetadataStream_TotalSize_Offset); if (totalSize > miniMetaDataBuffMaxSize) { // totalSize is inconsistent with miniMetaDataBuffMaxSize @@ -68,35 +81,36 @@ internal static Dictionary GetEEAddressToStringMap(Target byte[] bytes = new byte[totalSize]; ReadOnlySpan miniMdBuffer = bytes.AsSpan(); target.ReadBuffer(miniMetaDataBuffAddress, bytes); - uint countStreams = target.Read(miniMetaDataBuffAddress + 0x8); + 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; } - uint eeNameSig = target.Read(miniMetaDataBuffAddress + 0xC); - if (eeNameSig != 0x614e4545) + 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(miniMetaDataBuffAddress + 0x10); + uint countNames = target.Read(eeNameStreamAddress + EENameStream_CountOfNames_Offset); - uint currentOffset = 20; + ulong currentAddress = eeNameStreamAddress + EENameStreamHeaderSize; for (int i = 0; i < countNames; i++) { - if ((currentOffset + target.PointerSize) > miniMetaDataBuffMaxSize) + if (currentAddress >= miniMetaDataBuffEnd) break; - TargetPointer eeObjectPointer = target.ReadPointer(miniMetaDataBuffAddress + currentOffset); - currentOffset += (uint)target.PointerSize; - int stringLen = miniMdBuffer.Slice((int)currentOffset).IndexOf((byte)0); + 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)currentOffset, stringLen)); + string name = Encoding.UTF8.GetString(miniMdBuffer.Slice((int)(currentAddress - miniMetaDataBuffAddress), stringLen)); stringToAddress.Add(eeObjectPointer, name); } catch @@ -104,7 +118,7 @@ internal static Dictionary GetEEAddressToStringMap(Target // Tolerate malformed strings without causing all lookups to fail } - currentOffset += (uint)stringLen + 1; + currentAddress += (uint)stringLen + 1; } return stringToAddress; diff --git a/src/native/managed/cdacreader/tests/DacStreamsTests.cs b/src/native/managed/cdacreader/tests/DacStreamsTests.cs new file mode 100644 index 0000000000000..9dd20059a5516 --- /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/MockMemorySpace.cs b/src/native/managed/cdacreader/tests/MockMemorySpace.cs index c74771a7f5b5c..f2e2419a8ba4b 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; } } From 3a35f41bde13466028bf7087ade57141f88124c1 Mon Sep 17 00:00:00 2001 From: David Wrighton Date: Tue, 16 Jul 2024 15:51:14 -0700 Subject: [PATCH 15/15] Feedback --- docs/design/datacontracts/RuntimeTypeSystem.md | 7 +++---- src/coreclr/debug/runtimeinfo/contracts.jsonc | 4 ++-- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/docs/design/datacontracts/RuntimeTypeSystem.md b/docs/design/datacontracts/RuntimeTypeSystem.md index 864a225cd6bb0..41d01f6e7db47 100644 --- a/docs/design/datacontracts/RuntimeTypeSystem.md +++ b/docs/design/datacontracts/RuntimeTypeSystem.md @@ -20,13 +20,12 @@ struct TypeHandle 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 separate from a live process. + // + + 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 either a `TypeHandle` or -struct TypeHandle +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 TypeHandle inspection APIs diff --git a/src/coreclr/debug/runtimeinfo/contracts.jsonc b/src/coreclr/debug/runtimeinfo/contracts.jsonc index 48ffcae518250..7d477b00cd858 100644 --- a/src/coreclr/debug/runtimeinfo/contracts.jsonc +++ b/src/coreclr/debug/runtimeinfo/contracts.jsonc @@ -9,9 +9,9 @@ // 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, - "Thread": 1, - "DacStreams": 1 + "Thread": 1 }