From 255da992603928e801f68bb8595ed47ba9f6b666 Mon Sep 17 00:00:00 2001 From: David Wrighton Date: Tue, 9 Jul 2024 08:52:03 -0700 Subject: [PATCH 01/38] 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 00000000000000..93cdb6bd44208e --- /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 00000000000000..b852acb8a5dc87 --- /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 21f46e481ecd97..fef95df1364f15 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 8d37ca5441fcd3..80c12d8ba1c374 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 3567f3b8571f30..9e2cbb22f637d4 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 00000000000000..a37f2641eb319f --- /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 06b201d3957d55..f2745a5e26e8e5 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/38] 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 2d2aad5bd1f965..2955fb4d3ec613 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 291d048eed7ed7..9c779136fa3c51 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 d1b5443e497b59..248ff7822a1ed0 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 74c66714555f36..a5bd9bd8be6620 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 dde37703a384ce..26176d68328845 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 80abb784df1faa..437d7ba8cfe2d1 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 e8d7526bd0b16c..2518f86d5adafb 100644 --- a/src/coreclr/vm/typedesc.h +++ b/src/coreclr/vm/typedesc.h @@ -204,8 +204,14 @@ class TypeDesc // internal RuntimeType object handle RUNTIMETYPEHANDLE m_hExposedClassObject; + template friend struct ::cdac_offsets; }; +template<> +struct cdac_offsets +{ + static constexpr size_t TypeAndFlags = offsetof(TypeDesc, m_typeAndFlags); +}; /*************************************************************************/ // This variant is used for parameterized types that have exactly one argument @@ -263,6 +269,13 @@ class ParamTypeDesc : public TypeDesc { // The type that is being modified TypeHandle m_Arg; + template friend struct ::cdac_offsets; +}; + +template<> +struct cdac_offsets +{ + static constexpr size_t TypeArg = offsetof(ParamTypeDesc, m_Arg); }; /*************************************************************************/ @@ -381,6 +394,15 @@ class TypeVarTypeDesc : public TypeDesc // index within declaring type or method, numbered from zero unsigned int m_index; + + template friend struct ::cdac_offsets; +}; + +template<> +struct cdac_offsets +{ + static constexpr size_t Module = offsetof(TypeVarTypeDesc, m_pModule); + static constexpr size_t Token = offsetof(TypeVarTypeDesc, m_token); }; /*************************************************************************/ @@ -467,6 +489,16 @@ class FnPtrTypeDesc : public TypeDesc // Return type first, then argument types TypeHandle m_RetAndArgTypes[1]; + + template friend struct ::cdac_offsets; }; // class FnPtrTypeDesc +template<> +struct cdac_offsets +{ + static constexpr size_t NumArgs = offsetof(FnPtrTypeDesc, m_NumArgs); + static constexpr size_t RetAndArgTypes = offsetof(FnPtrTypeDesc, m_RetAndArgTypes); + static constexpr size_t CallConv = offsetof(FnPtrTypeDesc, m_CallConv); +}; + #endif // TYPEDESC_H diff --git a/src/native/managed/cdacreader/src/Contracts/Metadata.cs b/src/native/managed/cdacreader/src/Contracts/Metadata.cs index 93cdb6bd44208e..4d3db918ee8f50 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 b852acb8a5dc87..89d2a462746fe1 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 80c12d8ba1c374..9550a7339b12ef 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 e6f98f0d8e2f08..d0c92864ad7941 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 da2ea6c8b01b3e..4670b2535028be 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 e697b3f40756c8..726c6064d427bc 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 00000000000000..e5b96de528701e --- /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 65b7be805c838e..8ed77857286210 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 3319c6547f0568..b1b6723d556541 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 00000000000000..93396fb8fa6357 --- /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 00000000000000..5cef78482999de --- /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 00000000000000..f377132621436f --- /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 88e973c82ee149..cfcc7e93f362ad 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 00000000000000..c6d5f2894fd656 --- /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 00000000000000..8f6fc03d79b8ff --- /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 00000000000000..82157a35e75124 --- /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 00000000000000..2a7c87f47c2abb --- /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 a37f2641eb319f..28d0b1d805d222 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 f2745a5e26e8e5..ae2202b84d3803 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/38] 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 6dd0f52fa2e55f..7666e634a19a77 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 a6dda591278557..5e408eab3d73a6 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 9c779136fa3c51..324c279f053136 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 248ff7822a1ed0..2b7887606a6ca8 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 eb49ed746edc2a..0c6df0e1d3c267 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 cb370bae893e0e..5fbbfd2307d639 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 437d7ba8cfe2d1..b1ce54ab274646 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 e30633fdf2a25a..508fd8e56ad836 100644 --- a/src/native/managed/cdacreader/src/Contracts/Loader.cs +++ b/src/native/managed/cdacreader/src/Contracts/Loader.cs @@ -31,6 +31,75 @@ internal record struct ModuleLookupTables( TargetPointer TypeDefToMethodTable, TargetPointer TypeRefToMethodTable); +internal struct EcmaMetadataSchema +{ + public EcmaMetadataSchema(string metadataVersion, bool largeStringHeap, bool largeBlobHeap, bool largeGuidHeap, int[] rowCount, bool[] isSorted, bool variableSizedColumnsAre4BytesLong) + { + MetadataVersion = metadataVersion; + LargeStringHeap = largeStringHeap; + LargeBlobHeap = largeBlobHeap; + LargeGuidHeap = largeGuidHeap; + + _rowCount = rowCount; + _isSorted = isSorted; + + VariableSizedColumnsAreAll4BytesLong = variableSizedColumnsAre4BytesLong; + } + + public readonly string MetadataVersion; + + public readonly bool LargeStringHeap; + public readonly bool LargeBlobHeap; + public readonly bool LargeGuidHeap; + + // Table data, these structures hold MetadataTable.Count entries + private readonly int[] _rowCount; + public readonly ReadOnlySpan RowCount => _rowCount; + + private readonly bool[] _isSorted; + public readonly ReadOnlySpan IsSorted => _isSorted; + + // In certain scenarios the size of the tables is forced to be the maximum size + // Otherwise the size of columns should be computed based on RowSize/the various heap flags + public readonly bool VariableSizedColumnsAreAll4BytesLong; +} + +internal class TargetEcmaMetadata +{ + public TargetEcmaMetadata(EcmaMetadataSchema schema, + TargetSpan[] tables, + TargetSpan stringHeap, + TargetSpan userStringHeap, + TargetSpan blobHeap, + TargetSpan guidHeap) + { + Schema = schema; + _tables = tables; + StringHeap = stringHeap; + UserStringHeap = userStringHeap; + BlobHeap = blobHeap; + GuidHeap = guidHeap; + } + + public EcmaMetadataSchema Schema { get; init; } + + private TargetSpan[] _tables; + public ReadOnlySpan Tables => _tables; + public TargetSpan StringHeap { get; init; } + public TargetSpan UserStringHeap { get; init; } + public TargetSpan BlobHeap { get; init; } + public TargetSpan GuidHeap { get; init; } +} + +[Flags] +internal enum AvailableMetadataType +{ + None = 0, + ReadOnly = 1, + ReadWriteSavedCopy = 2, + ReadWrite = 4 +} + internal interface ILoader : IContract { static string IContract.Name => nameof(Loader); @@ -51,8 +120,15 @@ static IContract IContract.Create(Target target, int version) public virtual TargetPointer GetThunkHeap(ModuleHandle handle) => throw new NotImplementedException(); public virtual TargetPointer GetILBase(ModuleHandle handle) => throw new NotImplementedException(); + public virtual TargetPointer GetMetadataAddress(ModuleHandle handle, out ulong size) => throw new NotImplementedException(); + public virtual AvailableMetadataType GetAvailableMetadataType(ModuleHandle handle) => throw new NotImplementedException(); + + public virtual TargetPointer GetReadWriteSavedMetadataAddress(ModuleHandle handle, out ulong size) => throw new NotImplementedException(); + + public virtual TargetEcmaMetadata GetReadWriteMetadata(ModuleHandle handle) => throw new NotImplementedException(); + public virtual ModuleLookupTables GetLookupTables(ModuleHandle handle) => throw new NotImplementedException(); } diff --git a/src/native/managed/cdacreader/src/Contracts/Loader_1.cs b/src/native/managed/cdacreader/src/Contracts/Loader_1.cs index 533dbc467d34e1..531633f3141534 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 89d2a462746fe1..00000000000000 --- 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 fef95df1364f15..21f46e481ecd97 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 4670b2535028be..da11b5f9d4cee5 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 e5b96de528701e..a25f4a87c8471b 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 e462a201241ce7..eb8746ea64c8eb 100644 --- a/src/native/managed/cdacreader/src/Data/Module.cs +++ b/src/native/managed/cdacreader/src/Data/Module.cs @@ -22,6 +22,7 @@ public Module(Target target, TargetPointer address) Base = target.ReadPointer(address + (ulong)type.Fields[nameof(Base)].Offset); LoaderAllocator = target.ReadPointer(address + (ulong)type.Fields[nameof(LoaderAllocator)].Offset); ThunkHeap = target.ReadPointer(address + (ulong)type.Fields[nameof(ThunkHeap)].Offset); + DynamicMetadata = target.ReadPointer(address + (ulong)type.Fields[nameof(DynamicMetadata)].Offset); FieldDefToDescMap = target.ReadPointer(address + (ulong)type.Fields[nameof(FieldDefToDescMap)].Offset); ManifestModuleReferencesMap = target.ReadPointer(address + (ulong)type.Fields[nameof(ManifestModuleReferencesMap)].Offset); @@ -36,6 +37,7 @@ public Module(Target target, TargetPointer address) public TargetPointer Base { get; init; } public TargetPointer LoaderAllocator { get; init; } public TargetPointer ThunkHeap { get; init; } + public TargetPointer DynamicMetadata { get; init; } public TargetPointer FieldDefToDescMap { get; init; } public TargetPointer ManifestModuleReferencesMap { get; init; } diff --git a/src/native/managed/cdacreader/src/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 4d3db918ee8f50..b3c46933000bc7 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 28d0b1d805d222..e093ae1c59320b 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 ae2202b84d3803..da5d57334dc88c 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/38] 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 0c6df0e1d3c267..0efb59cc6da9e3 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 531633f3141534..c5d096674462c1 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 da11b5f9d4cee5..aa5bb80978ed70 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 93396fb8fa6357..e3b34fce83b634 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 c6d5f2894fd656..1fe072ce1e926d 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 8f6fc03d79b8ff..851a10d0f78346 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 82157a35e75124..07c8ef21fea9ad 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 2a7c87f47c2abb..d358ccc15d5240 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 b3c46933000bc7..f861794529346b 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 e093ae1c59320b..3f942f2469a927 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 da5d57334dc88c..9039011c4bd695 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/38] 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 4291c96569b278..962226ec41d7ea 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 f8382ee4fdc599..085766f0896516 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 d0c92864ad7941..6e02d241b994c4 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/38] 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 085766f0896516..ab4f3bb81d760e 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 aa5bb80978ed70..dcb6b59486bf78 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 726c6064d427bc..531a30389e41c0 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/38] 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 00000000000000..aaceda4699982d --- /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 324c279f053136..abc2b8defe4c8f 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 f40991da45b674..48ffcae5182507 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 2b7887606a6ca8..a5b937f51ee43c 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 5c95db68d227f9..6ca10e46a949bb 100644 --- a/src/native/managed/cdacreader/src/Constants.cs +++ b/src/native/managed/cdacreader/src/Constants.cs @@ -17,5 +17,8 @@ internal static class Globals internal const string SOSBreakingChangeVersion = nameof(SOSBreakingChangeVersion); internal const string FreeObjectMethodTable = nameof(FreeObjectMethodTable); + + internal const string MiniMetaDataBuffAddress = nameof(MiniMetaDataBuffAddress); + internal const string MiniMetaDataBuffMaxSize = nameof(MiniMetaDataBuffMaxSize); } } diff --git a/src/native/managed/cdacreader/src/Contracts/DacStreams.cs b/src/native/managed/cdacreader/src/Contracts/DacStreams.cs new file mode 100644 index 00000000000000..167c7f89ad2bfd --- /dev/null +++ b/src/native/managed/cdacreader/src/Contracts/DacStreams.cs @@ -0,0 +1,30 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Microsoft.Diagnostics.DataContractReader.Contracts; + +internal interface IDacStreams : IContract +{ + static string IContract.Name { get; } = nameof(DacStreams); + static IContract IContract.Create(Target target, int version) + { + return version switch + { + 1 => new DacStreams_1(target), + _ => default(DacStreams), + }; + } + + public virtual string? StringFromEEAddress(TargetPointer address) => throw new NotImplementedException(); +} + +internal readonly struct DacStreams : IDacStreams +{ + // Everything throws NotImplementedException +} diff --git a/src/native/managed/cdacreader/src/Contracts/DacStreams_1.cs b/src/native/managed/cdacreader/src/Contracts/DacStreams_1.cs new file mode 100644 index 00000000000000..cd43407ffc1f02 --- /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 21f46e481ecd97..6a5bf35f64d648 100644 --- a/src/native/managed/cdacreader/src/Contracts/Registry.cs +++ b/src/native/managed/cdacreader/src/Contracts/Registry.cs @@ -22,6 +22,7 @@ public Registry(Target target) public ILoader Loader => GetContract(); public IThread Thread => GetContract(); public IRuntimeTypeSystem RuntimeTypeSystem => GetContract(); + public IDacStreams DacStreams => GetContract(); private T GetContract() where T : IContract { diff --git a/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs b/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs index 9e2cbb22f637d4..d707fddaa44db7 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/38] - 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 ab4f3bb81d760e..5399e83f5b4a37 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 cd43407ffc1f02..6d78b6161f90f8 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 9550a7339b12ef..b7267b5f8c23cb 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 dcb6b59486bf78..9c3c3bc3f4974d 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 00000000000000..355897000ec69b --- /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 8ed77857286210..65b7be805c838e 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 e3b34fce83b634..00000000000000 --- 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 f377132621436f..00000000000000 --- 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 d707fddaa44db7..961a8c861ed97b 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 3f942f2469a927..510367bd12480f 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 9039011c4bd695..30478662048588 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/38] 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 962226ec41d7ea..bc45eaee7a671c 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 7666e634a19a77..71314aa050617b 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 5e408eab3d73a6..8dbd2cf2ca0588 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 a5b937f51ee43c..1bfc5617576e03 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 0efb59cc6da9e3..fa48d45d6b0510 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 5fbbfd2307d639..9bcae6e145d843 100644 --- a/src/coreclr/vm/ceeload.h +++ b/src/coreclr/vm/ceeload.h @@ -91,6 +91,20 @@ typedef DPTR(JITInlineTrackingMap) PTR_JITInlineTrackingMap; typedef DPTR(struct LookupMapBase) PTR_LookupMapBase; +struct DynamicMetadata +{ + uint32_t Size; + BYTE Data[0]; + template friend struct ::cdac_offsets; +}; + +template<> +struct cdac_offsets +{ + static constexpr size_t Size = offsetof(DynamicMetadata, Size); + static constexpr size_t Data = offsetof(DynamicMetadata, Data); +}; + struct LookupMapBase { DPTR(LookupMapBase) pNext; diff --git a/src/native/managed/cdacreader/src/Contracts/Loader_1.cs b/src/native/managed/cdacreader/src/Contracts/Loader_1.cs index c5d096674462c1..2c4aa6677f85c0 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 00000000000000..ce7fa36caa4dd5 --- /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 cfcc7e93f362ad..da077edbad23e5 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/38] 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 e24f1200597263..e4f2968f29f797 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/38] 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 fa48d45d6b0510..75e93bcdbee395 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 2d5ec4f29b64b344b58b3d4dff2c11704aca644f Mon Sep 17 00:00:00 2001 From: Aleksey Kliger Date: Tue, 16 Jul 2024 15:28:55 -0400 Subject: [PATCH 12/38] Cherrypick RuntimeTypeSystem changes from #104759 Co-Authored-By: David Wrighton --- .../design/datacontracts/RuntimeTypeSystem.md | 392 ++++++++++++++++-- .../debug/runtimeinfo/datadescriptor.h | 35 ++ src/coreclr/vm/class.h | 6 + src/coreclr/vm/methodtable.cpp | 14 +- src/coreclr/vm/methodtable.h | 7 + src/coreclr/vm/typedesc.h | 32 ++ .../src/Contracts/RuntimeTypeSystem.cs | 94 ++++- .../RuntimeTypeSystem_1.MethodTableFlags.cs | 8 + .../src/Contracts/RuntimeTypeSystem_1.cs | 321 ++++++++++++-- .../Contracts/RuntimeTypeSystem_1_Helpers.cs | 31 ++ .../managed/cdacreader/src/Data/EEClass.cs | 23 + .../cdacreader/src/Data/GenericsDictInfo.cs | 23 + .../cdacreader/src/Data/MethodTable.cs | 2 + .../managed/cdacreader/src/Data/TypeDesc.cs | 78 ++++ src/native/managed/cdacreader/src/DataType.cs | 6 + .../cdacreader/src/Legacy/SOSDacImpl.cs | 22 +- src/native/managed/cdacreader/src/Target.cs | 39 ++ 17 files changed, 1023 insertions(+), 110 deletions(-) create mode 100644 src/native/managed/cdacreader/src/Contracts/RuntimeTypeSystem_1_Helpers.cs create mode 100644 src/native/managed/cdacreader/src/Data/GenericsDictInfo.cs create mode 100644 src/native/managed/cdacreader/src/Data/TypeDesc.cs diff --git a/docs/design/datacontracts/RuntimeTypeSystem.md b/docs/design/datacontracts/RuntimeTypeSystem.md index f8382ee4fdc599..5399e83f5b4a37 100644 --- a/docs/design/datacontracts/RuntimeTypeSystem.md +++ b/docs/design/datacontracts/RuntimeTypeSystem.md @@ -4,46 +4,80 @@ This contract is for exploring the properties of the runtime types of values on ## APIs of contract -A `MethodTable` is the runtime representation of the type information about a value. Given a `TargetPointer` address, the `RuntimeTypeSystem` contract provides a `MethodTableHandle` for querying the `MethodTable`. +A `TypeHandle` is the runtime representation of the type information about a value which is represented as a TypeHandle. +Given a `TargetPointer` address, the `RuntimeTypeSystem` contract provides a `TypeHandle` for querying the details of the `TypeHandle`. + ``` csharp -struct MethodTableHandle +struct TypeHandle { // no public properties or constructors - internal TargetPointer Address { get; } + 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 + // + + 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. } ``` +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); - #endregion MethodTable inspection APIs + public virtual uint GetTypeDefTypeAttributes(TypeHandle typeHandle); + public virtual ReadOnlySpan GetInstantiation(TypeHandle typeHandle); + public virtual bool IsGenericTypeDefinition(TypeHandle typeHandle); + + public virtual TypeHandle TypeHandleFromAddress(TargetPointer address); + public virtual bool HasTypeParam(TypeHandle typeHandle); + + // Element type of the type. NOTE: this drops the CorElementType.GenericInst, and CorElementType.String is returned as CorElementType.Class. + // NOTE: If this returns CorElementType.ValueType it may be a normal valuetype or a "NATIVE" valuetype used to represent an interop view of a structure + // HasTypeParam will return true for cases where this is the interop view, and false for normal valuetypes. + public virtual CorElementType GetSignatureCorElementType(TypeHandle typeHandle); + + // return true if the TypeHandle represents an array, and set the rank to either 0 (if the type is not an array), or the rank number if it is. + public virtual bool IsArray(TypeHandle typeHandle, out uint rank); + public virtual TypeHandle GetTypeParam(TypeHandle typeHandle); + public virtual bool IsGenericVariable(TypeHandle typeHandle, out TargetPointer module, out uint token); + public virtual bool IsFunctionPointer(TypeHandle typeHandle, out ReadOnlySpan retAndArgTypes, out byte callConv); + + #endregion TypeHandle inspection APIs ``` ## Version 1 @@ -60,6 +94,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 +104,17 @@ internal partial struct RuntimeTypeSystem_1 internal enum WFLAGS_HIGH : uint { Category_Mask = 0x000F0000, - Category_Array = 0x00080000, + Category_ElementType_Mask = 0x000E0000, Category_Array_Mask = 0x000C0000, + + Category_IfArrayThenSzArray = 0x00020000, + Category_Array = 0x00080000, + Category_ValueType = 0x00040000, + Category_Nullable = 0x00050000, + Category_PrimitiveValueType = 0x00060000, + Category_TruePrimitive = 0x00070000, Category_Interface = 0x000C0000, + ContainsGCPointers = 0x01000000, HasComponentSize = 0x80000000, // This is set if lower 16 bits is used for the component size, // otherwise the lower bits are used for WFLAGS_LOW @@ -118,6 +161,7 @@ internal partial struct RuntimeTypeSystem_1 public bool HasInstantiation => !TestFlagWithMask(WFLAGS_LOW.GenericsMask, WFLAGS_LOW.GenericsMask_NonGeneric); public bool ContainsGCPointers => GetFlag(WFLAGS_HIGH.ContainsGCPointers) != 0; public bool IsDynamicStatics => GetFlag(WFLAGS2_ENUM.DynamicStatics) != 0; + public bool IsGenericTypeDefinition => TestFlagWithMask(WFLAGS_LOW.GenericsMask, WFLAGS_LOW.GenericsMask_TypicalInstantiation); } [Flags] @@ -127,6 +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, + } } ``` @@ -141,6 +195,7 @@ internal struct MethodTable_1 internal TargetPointer ParentMethodTable { get; } internal TargetPointer Module { get; } internal TargetPointer EEClassOrCanonMT { get; } + internal TargetPointer PerInstInfo { get; } internal MethodTable_1(Data.MethodTable data) { Flags = new RuntimeTypeSystem_1.MethodTableFlags @@ -154,24 +209,77 @@ internal struct MethodTable_1 EEClassOrCanonMT = data.EEClassOrCanonMT; Module = data.Module; ParentMethodTable = data.ParentMethodTable; + PerInstInfo = data.PerInstInfo; + } +} +``` + +Internally the contract uses extension methods on the `TypeHandle` api so that it can distinguish between `MethodTable` and `TypeDesc` +```csharp +static class RuntimeTypeSystem_1_Helpers +{ + public static bool IsTypeDesc(this TypeHandle type) + { + return type.Address != 0 && (type.Address & TypeHandleBits.ValidMask) == TypeHandleBits.TypeDesc; + } + + public static bool IsMethodTable(this TypeHandle type) + { + return type.Address != 0 && (type.Address & TypeHandleBits.ValidMask) == TypeHandleBits.MethodTable; + } + + public static TargetPointer TypeDescAddress(this TypeHandle type) + { + if (!type.IsTypeDesc()) + return 0; + + return (ulong)type.Address & ~(ulong)TypeHandleBits.ValidMask; } } ``` The contract depends on the global pointer value `FreeObjectMethodTablePointer`. -The contract additionally depends on the `EEClass` data descriptor. + +The contract additionally depends on these data descriptors + +| Data Descriptor Name | Field | Meaning | +| --- | --- | --- | +| `MethodTable` | `MTFlags` | One of the flags fields on `MethodTable` | +| `MethodTable` | `MTFlags2` | One of the flags fields on `MethodTable` | +| `MethodTable` | `BaseSize` | BaseSize of a `MethodTable` | +| `MethodTable` | `EEClassOrCanonMT` | Path to both EEClass and canonical MethodTable of a MethodTable | +| `MethodTable` | `Module` | Module for `MethodTable` | +| `MethodTable` | `ParentMethodTable` | Parent type pointer of `MethodTable` | +| `MethodTable` | `NumInterfaces` | Number of interfaces of `MethodTable` | +| `MethodTable` | `NumVirtuals` | Number of virtual methods in `MethodTable` | +| `MethodTable` | `PerInstInfo` | Either the array element type, or pointer to generic information for `MethodTable` | +| `EEClass` | `InternalCorElementType` | An InternalCorElementType uses the enum values of a CorElementType to indicate some of the information about the type of the type which uses the EEClass In particular, all reference types are CorElementType.Class, Enums are the element type of their underlying type and ValueTypes which can exactly be represented as an element type are represented as such, all other values types are represented as CorElementType.ValueType. | +| `EEClass` | `MethodTable` | Pointer to the canonical MethodTable of this type | +| `EEClass` | `NumMethods` | Count of methods attached to the EEClass | +| `EEClass` | `CorTypeAttr` | Various flags | +| `ArrayClass` | `Rank` | Rank of the associated array MethodTable | +| `TypeDesc` | `TypeAndFlags` | The lower 8 bits are the CorElementType of the `TypeDesc`, the upper 24 bits are reserved for flags | +| `ParamTypeDesc` | `TypeArg` | Associated type argument | +| `TypeVarTypeDesc` | `Module` | Pointer to module which defines the type variable | +| `TypeVarTypeDesc` | `Token` | Token of the type variable | +| `FnPtrTypeDesc` | `NumArgs` | Number of arguments to the function described by the `TypeDesc` | +| `FnPtrTypeDesc` | `CallConv` | Lower 8 bits is the calling convention bit as extracted by the signature that defines this `TypeDesc` | +| `FnPtrTypeDesc` | `RetAndArgTypes` | Pointer to an array of TypeHandle addresses. This length of this is 1 more than `NumArgs` | +| `GenericsDictInfo` | `NumTypeArgs` | Number of type arguments in the type or method instantiation described by this `GenericsDictInfo` | + ```csharp private readonly Dictionary _methodTables; internal TargetPointer FreeObjectMethodTablePointer {get; } - public MethodTableHandle GetMethodTableHandle(TargetPointer methodTablePointer) + public TypeHandle GetTypeHandle(TargetPointer typeHandlePointer) { - ... // validate that methodTablePointer points to something that looks like a MethodTable. - ... // read Data.MethodTable from methodTablePointer. - ... // create a MethodTable_1 and add it to _methodTables. - return MethodTableHandle { Address = methodTablePointer } + ... // validate that typeHandlePointer points to something that looks like a MethodTable or a TypeDesc. + ... // If this is a MethodTable + ... // read Data.MethodTable from typeHandlePointer. + ... // create a MethodTable_1 and add it to _methodTables. + return TypeHandle { Address = typeHandlePointer } } internal static EEClassOrCanonMTBits GetEEClassOrCanonMTBits(TargetPointer eeClassOrCanonMTPtr) @@ -179,11 +287,11 @@ The contract additionally depends on the `EEClass` data descriptor. return (EEClassOrCanonMTBits)(eeClassOrCanonMTPtr & (ulong)EEClassOrCanonMTBits.Mask); } - public uint GetBaseSize(MethodTableHandle methodTableHandle) => _methodTables[methodTableHandle.Address].Flags.BaseSize; + public uint GetBaseSize(TypeHandle TypeHandle) => !typeHandle.IsMethodTable() ? (uint)0 : _methodTables[TypeHandle.Address].Flags.BaseSize; - public uint GetComponentSize(MethodTableHandle methodTableHandle) => GetComponentSize(_methodTables[methodTableHandle.Address]); + public uint GetComponentSize(TypeHandle TypeHandle) =>!typeHandle.IsMethodTable() ? (uint)0 : GetComponentSize(_methodTables[TypeHandle.Address]); - private TargetPointer GetClassPointer(MethodTableHandle methodTableHandle) + private TargetPointer GetClassPointer(TypeHandle TypeHandle) { ... // if the MethodTable stores a pointer to the EEClass, return it // otherwise the MethodTable stores a pointer to the canonical MethodTable @@ -191,34 +299,232 @@ The contract additionally depends on the `EEClass` data descriptor. // Canonical MethodTables always store an EEClass pointer. } - private Data.EEClass GetClassData(MethodTableHandle methodTableHandle) + private Data.EEClass GetClassData(TypeHandle TypeHandle) { - TargetPointer eeClassPtr = GetClassPointer(methodTableHandle); + TargetPointer eeClassPtr = GetClassPointer(TypeHandle); ... // read Data.EEClass data from eeClassPtr } - public TargetPointer GetCanonicalMethodTable(MethodTableHandle methodTableHandle) => GetClassData(methodTableHandle).MethodTable; + public TargetPointer GetCanonicalMethodTable(TypeHandle TypeHandle) => !typeHandle.IsMethodTable() ? TargetPointer.Null : GetClassData(TypeHandle).MethodTable; + + public TargetPointer GetModule(TypeHandle TypeHandle) + { + if (typeHandle.IsMethodTable()) + { + return _methodTables[TypeHandle.Address].Module; + } + else if (typeHandle.IsTypeDesc()) + { + if (HasTypeParam(typeHandle)) + { + return GetModule(GetTypeParam(typeHandle)); + } + else if (IsGenericVariable(typeHandle, out TargetPointer genericParamModule, out _)) + { + return genericParamModule; + } + } + return TargetPointer.Null; + } - public TargetPointer GetModule(MethodTableHandle methodTableHandle) => _methodTables[methodTableHandle.Address].Module; - public TargetPointer GetParentMethodTable(MethodTableHandle methodTableHandle) => _methodTables[methodTableHandle.Address].ParentMethodTable; + public TargetPointer GetParentMethodTable(TypeHandle TypeHandle) => !typeHandle.IsMethodTable() ? TargetPointer.Null : _methodTables[TypeHandle.Address].ParentMethodTable; - public bool IsFreeObjectMethodTable(MethodTableHandle methodTableHandle) => FreeObjectMethodTablePointer == methodTableHandle.Address; + public bool IsFreeObjectMethodTable(TypeHandle TypeHandle) => FreeObjectMethodTablePointer == TypeHandle.Address; - public bool IsString(MethodTableHandle methodTableHandle) => _methodTables[methodTableHandle.Address].Flags.IsString; - public bool ContainsGCPointers(MethodTableHandle methodTableHandle) => _methodTables[methodTableHandle.Address].Flags.ContainsGCPointers; + public bool IsString(TypeHandle TypeHandle) => !typeHandle.IsMethodTable() ? false : _methodTables[TypeHandle.Address].Flags.IsString; + public bool ContainsGCPointers(TypeHandle TypeHandle) => !typeHandle.IsMethodTable() ? false : _methodTables[TypeHandle.Address].Flags.ContainsGCPointers; - public uint GetTypeDefToken(MethodTableHandle methodTableHandle) + public uint GetTypeDefToken(TypeHandle TypeHandle) { - MethodTable_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(TypeHandle TypeHandle) => !typeHandle.IsMethodTable() ? 0 : _methodTables[TypeHandle.Address].NumInterfaces; + + public uint GetTypeDefTypeAttributes(TypeHandle TypeHandle) => !typeHandle.IsMethodTable() ? 0 : GetClassData(TypeHandle).CorTypeAttr; - public ushort GetNumInterfaces(MethodTableHandle methodTableHandle) => _methodTables[methodTableHandle.Address].NumInterfaces; + public bool IsDynamicStatics(TypeHandle TypeHandle) => !typeHandle.IsMethodTable() ? false : _methodTables[TypeHandle.Address].Flags.IsDynamicStatics; + + public ReadOnlySpan GetInstantiation(TypeHandle TypeHandle) + { + if (!typeHandle.IsMethodTable()) + return default; + + MethodTable_1 typeHandle = _methodTables[TypeHandle.Address]; + if (!typeHandle.Flags.HasInstantiation) + return default; + + TargetPointer perInstInfo = typeHandle.PerInstInfo; + TargetPointer genericsDictInfo = perInstInfo - (ulong)_target.PointerSize; + TargetPointer dictionaryPointer = _target.ReadPointer(perInstInfo); + + int NumTypeArgs = // Read NumTypeArgs from genericsDictInfo using GenericsDictInfo contract + TypeHandle[] instantiation = new TypeHandle[NumTypeArgs]; + for (int i = 0; i < NumTypeArgs; i++) + instantiation[i] = GetTypeHandle(_target.ReadPointer(dictionaryPointer + _target.PointerSize * i)); + + return instantiation; + } - public uint GetTypeDefTypeAttributes(MethodTableHandle methodTableHandle) => GetClassData(methodTableHandle).CorTypeAttr; + public bool IsDynamicStatics(TypeHandle TypeHandle) => !typeHandle.IsMethodTable() ? false : _methodTables[TypeHandle.Address].Flags.IsDynamicStatics; - public bool IsDynamicStatics(MethodTableHandle methodTableHandle) => _methodTables[methodTableHandle.Address].Flags.IsDynamicStatics; + public bool HasTypeParam(TypeHandle typeHandle) + { + if (typeHandle.IsMethodTable()) + { + MethodTable typeHandle = _methodTables[typeHandle.Address]; + return typeHandle.Flags.IsArray; + } + else if (typeHandle.IsTypeDesc()) + { + int TypeAndFlags = // Read TypeAndFlags field from TypeDesc contract using address typeHandle.TypeDescAddress() + CorElementType elemType = (CorElementType)(TypeAndFlags & 0xFF); + switch (elemType) + { + case CorElementType.ValueType: + case CorElementType.Byref: + case CorElementType.Ptr: + return true; + } + } + return false; + } + + public CorElementType GetSignatureCorElementType(TypeHandle typeHandle) + { + if (typeHandle.IsMethodTable()) + { + MethodTable typeHandle = _methodTables[typeHandle.Address]; + + switch (typeHandle.Flags.GetFlag(WFLAGS_HIGH.Category_Mask)) + { + case WFLAGS_HIGH.Category_Array: + return CorElementType.Array; + case WFLAGS_HIGH.Category_Array | WFLAGS_HIGH.Category_IfArrayThenSzArray: + return CorElementType.SzArray; + case WFLAGS_HIGH.Category_ValueType: + case WFLAGS_HIGH.Category_Nullable: + case WFLAGS_HIGH.Category_PrimitiveValueType: + return CorElementType.ValueType; + case WFLAGS_HIGH.Category_TruePrimitive: + return (CorElementType)GetClassData(typeHandle).InternalCorElementType; + default: + return CorElementType.Class; + } + } + else if (typeHandle.IsTypeDesc()) + { + int TypeAndFlags = // Read TypeAndFlags field from TypeDesc contract using address typeHandle.TypeDescAddress() + return (CorElementType)(TypeAndFlags & 0xFF); + } + return default(CorElementType); + } + + // return true if the TypeHandle represents an array, and set the rank to either 0 (if the type is not an array), or the rank number if it is. + public bool IsArray(TypeHandle typeHandle, out uint rank) + { + if (typeHandle.IsMethodTable()) + { + MethodTable typeHandle = _methodTables[typeHandle.Address]; + + switch (typeHandle.Flags.GetFlag(WFLAGS_HIGH.Category_Mask)) + { + case WFLAGS_HIGH.Category_Array: + TargetPointer clsPtr = GetClassPointer(typeHandle); + rank = // Read Rank field from ArrayClass contract using address clsPtr + return true; + + case WFLAGS_HIGH.Category_Array | WFLAGS_HIGH.Category_IfArrayThenSzArray: + rank = 1; + return true; + } + } + + rank = 0; + return false; + } + + public TypeHandle GetTypeParam(TypeHandle typeHandle) + { + if (typeHandle.IsMethodTable()) + { + MethodTable typeHandle = _methodTables[typeHandle.Address]; + + // Validate that this is an array + if (!typeHandle.Flags.IsArray) + throw new ArgumentException(nameof(typeHandle)); + + return TypeHandleFromAddress(typeHandle.PerInstInfo); + } + else if (typeHandle.IsTypeDesc()) + { + int TypeAndFlags = // Read TypeAndFlags field from TypeDesc contract using address typeHandle.TypeDescAddress() + CorElementType elemType = (CorElementType)(typeDesc.TypeAndFlags & 0xFF); + + switch (elemType) + { + case CorElementType.ValueType: + case CorElementType.Byref: + case CorElementType.Ptr: + TargetPointer typeArgPointer = // Read TypeArg field from ParamTypeDesc contract using address typeHandle.TypeDescAddress() + return TypeHandleFromAddress(typeArgPointer); + } + } + throw new ArgumentException(nameof(typeHandle)); + } + + public 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.TypeDescAddress() + CorElementType elemType = (CorElementType)(typeDesc.TypeAndFlags & 0xFF); + + switch (elemType) + { + case CorElementType.MVar: + case CorElementType.Var: + module = // Read Module field from TypeVarTypeDesc contract using address typeHandle.TypeDescAddress() + token = // Read Module field from TypeVarTypeDesc contract using address typeHandle.TypeDescAddress() + return true; + } + return false; + } + + public bool IsFunctionPointer(TypeHandle typeHandle, out ReadOnlySpan retAndArgTypes, out byte callConv) + { + retAndArgTypes = default; + callConv = default; + + if (!typeHandle.IsTypeDesc()) + return false; + + int TypeAndFlags = // Read TypeAndFlags field from TypeDesc contract using address typeHandle.TypeDescAddress() + CorElementType elemType = (CorElementType)(typeDesc.TypeAndFlags & 0xFF); + + if (elemType != CorElementType.FnPtr) + return false; + + int NumArgs = // Read NumArgs field from FnPtrTypeDesc contract using address typeHandle.TypeDescAddress() + TargetPointer RetAndArgTypes = // Read NumArgs field from FnPtrTypeDesc contract using address typeHandle.TypeDescAddress() + + TypeHandle[] retAndArgTypesArray = new TypeHandle[NumTypeArgs + 1]; + for (int i = 0; i <= NumTypeArgs; i++) + retAndArgTypesArray[i] = GetTypeHandle(_target.ReadPointer(RetAndArgTypes + _target.PointerSize * i)); + + retAndArgTypes = retAndArgTypesArray; + callConv = (byte) // Read CallConv field from FnPtrTypeDesc contract using address typeHandle.TypeDescAddress(), and then ignore all but the low 8 bits. + return true; + } ``` diff --git a/src/coreclr/debug/runtimeinfo/datadescriptor.h b/src/coreclr/debug/runtimeinfo/datadescriptor.h index a0c71736fd6f9a..6bc19b44bddefb 100644 --- a/src/coreclr/debug/runtimeinfo/datadescriptor.h +++ b/src/coreclr/debug/runtimeinfo/datadescriptor.h @@ -202,6 +202,7 @@ CDAC_TYPE_FIELD(MethodTable, /*pointer*/, Module, cdac_offsets::Mod CDAC_TYPE_FIELD(MethodTable, /*pointer*/, ParentMethodTable, cdac_offsets::ParentMethodTable) CDAC_TYPE_FIELD(MethodTable, /*uint16*/, NumInterfaces, cdac_offsets::NumInterfaces) CDAC_TYPE_FIELD(MethodTable, /*uint16*/, NumVirtuals, cdac_offsets::NumVirtuals) +CDAC_TYPE_FIELD(MethodTable, /*pointer*/, PerInstInfo, cdac_offsets::PerInstInfo) CDAC_TYPE_END(MethodTable) CDAC_TYPE_BEGIN(EEClass) @@ -209,8 +210,42 @@ CDAC_TYPE_INDETERMINATE(EEClass) CDAC_TYPE_FIELD(EEClass, /*pointer*/, MethodTable, cdac_offsets::MethodTable) CDAC_TYPE_FIELD(EEClass, /*uint16*/, NumMethods, cdac_offsets::NumMethods) CDAC_TYPE_FIELD(EEClass, /*uint32*/, CorTypeAttr, cdac_offsets::CorTypeAttr) +CDAC_TYPE_FIELD(EEClass, /*uint8*/, InternalCorElementType, cdac_offsets::InternalCorElementType) CDAC_TYPE_END(EEClass) +CDAC_TYPE_BEGIN(ArrayClass) +CDAC_TYPE_INDETERMINATE(ArrayClass) +CDAC_TYPE_FIELD(ArrayClass, /*uint8*/, Rank, cdac_offsets::Rank) +CDAC_TYPE_END(ArrayClass) + +CDAC_TYPE_BEGIN(GenericsDictInfo) +CDAC_TYPE_INDETERMINATE(GenericsDictInfo) +CDAC_TYPE_FIELD(GenericsDictInfo, /*uint16*/, NumTypeArgs, cdac_offsets::NumTypeArgs) +CDAC_TYPE_END(GenericsDictInfo) + +CDAC_TYPE_BEGIN(TypeDesc) +CDAC_TYPE_INDETERMINATE(TypeDesc) +CDAC_TYPE_FIELD(TypeDesc, /*uint32*/, TypeAndFlags, cdac_offsets::TypeAndFlags) +CDAC_TYPE_END(TypeDesc) + +CDAC_TYPE_BEGIN(ParamTypeDesc) +CDAC_TYPE_INDETERMINATE(ParamTypeDesc) +CDAC_TYPE_FIELD(ParamTypeDesc, /*pointer*/, TypeArg, cdac_offsets::TypeArg) +CDAC_TYPE_END(ParamTypeDesc) + +CDAC_TYPE_BEGIN(TypeVarTypeDesc) +CDAC_TYPE_INDETERMINATE(TypeVarTypeDesc) +CDAC_TYPE_FIELD(TypeVarTypeDesc, /*pointer*/, Module, cdac_offsets::Module) +CDAC_TYPE_FIELD(TypeVarTypeDesc, /*uint32*/, Token, cdac_offsets::Token) +CDAC_TYPE_END(TypeVarTypeDesc) + +CDAC_TYPE_BEGIN(FnPtrTypeDesc) +CDAC_TYPE_INDETERMINATE(FnPtrTypeDesc) +CDAC_TYPE_FIELD(FnPtrTypeDesc, /*uint32*/, NumArgs, cdac_offsets::NumArgs) +CDAC_TYPE_FIELD(FnPtrTypeDesc, /*uint32*/, CallConv, cdac_offsets::CallConv) +CDAC_TYPE_FIELD(FnPtrTypeDesc, /*uint32*/, RetAndArgTypes, cdac_offsets::RetAndArgTypes) +CDAC_TYPE_END(FnPtrTypeDesc) + CDAC_TYPES_END() CDAC_GLOBALS_BEGIN() diff --git a/src/coreclr/vm/class.h b/src/coreclr/vm/class.h index 1eff90b672bb0b..e281b8e365e7ed 100644 --- a/src/coreclr/vm/class.h +++ b/src/coreclr/vm/class.h @@ -1803,6 +1803,7 @@ class EEClass // DO NOT CREATE A NEW EEClass USING NEW! template<> struct cdac_offsets { + static constexpr size_t InternalCorElementType = offsetof(EEClass, m_NormType); static constexpr size_t MethodTable = offsetof(EEClass, m_pMethodTable); static constexpr size_t NumMethods = offsetof(EEClass, m_NumMethods); static constexpr size_t CorTypeAttr = offsetof(EEClass, m_dwAttrClass); @@ -1995,7 +1996,12 @@ class ArrayClass : public EEClass BOOL fForStubAsIL ); + template friend struct ::cdac_offsets; +}; +template<> struct cdac_offsets +{ + static constexpr size_t Rank = offsetof(ArrayClass, m_rank); }; inline EEClassLayoutInfo *EEClass::GetLayoutInfo() diff --git a/src/coreclr/vm/methodtable.cpp b/src/coreclr/vm/methodtable.cpp index e6c78910ded7cc..8f15f3032ddc37 100644 --- a/src/coreclr/vm/methodtable.cpp +++ b/src/coreclr/vm/methodtable.cpp @@ -4832,7 +4832,7 @@ CorElementType MethodTable::GetSignatureCorElementType() // common cases of ELEMENT_TYPE_CLASS and ELEMENT_TYPE_VALUETYPE. CorElementType ret; - switch (GetFlag(enum_flag_Category_ElementTypeMask)) + switch (GetFlag(enum_flag_Category_Mask)) { case enum_flag_Category_Array: ret = ELEMENT_TYPE_ARRAY; @@ -4843,17 +4843,13 @@ CorElementType MethodTable::GetSignatureCorElementType() break; case enum_flag_Category_ValueType: + case enum_flag_Category_Nullable: + case enum_flag_Category_PrimitiveValueType: ret = ELEMENT_TYPE_VALUETYPE; break; - case enum_flag_Category_PrimitiveValueType: - // - // This is the only difference from MethodTable::GetInternalCorElementType() - // - if (IsTruePrimitive()) - ret = GetClass()->GetInternalCorElementType(); - else - ret = ELEMENT_TYPE_VALUETYPE; + case enum_flag_Category_TruePrimitive: + ret = GetClass()->GetInternalCorElementType(); break; default: diff --git a/src/coreclr/vm/methodtable.h b/src/coreclr/vm/methodtable.h index 66ecee9073d80c..47bdd53cfe27f6 100644 --- a/src/coreclr/vm/methodtable.h +++ b/src/coreclr/vm/methodtable.h @@ -293,9 +293,15 @@ struct GenericsDictInfo // Number of type parameters (NOT including those of superclasses). WORD m_wNumTyPars; + template friend struct ::cdac_offsets; }; // struct GenericsDictInfo typedef DPTR(GenericsDictInfo) PTR_GenericsDictInfo; +template<> +struct cdac_offsets +{ + static constexpr size_t NumTypeArgs = offsetof(GenericsDictInfo, m_wNumTyPars); +}; // These various statics structures exist directly before the MethodTableAuxiliaryData @@ -3943,6 +3949,7 @@ template<> struct cdac_offsets static constexpr size_t ParentMethodTable = offsetof(MethodTable, m_pParentMethodTable); static constexpr size_t NumInterfaces = offsetof(MethodTable, m_wNumInterfaces); static constexpr size_t NumVirtuals = offsetof(MethodTable, m_wNumVirtuals); + static constexpr size_t PerInstInfo = offsetof(MethodTable, m_pPerInstInfo); }; #ifndef CROSSBITNESS_COMPILE diff --git a/src/coreclr/vm/typedesc.h b/src/coreclr/vm/typedesc.h index e8d7526bd0b16c..2518f86d5adafb 100644 --- a/src/coreclr/vm/typedesc.h +++ b/src/coreclr/vm/typedesc.h @@ -204,8 +204,14 @@ class TypeDesc // internal RuntimeType object handle RUNTIMETYPEHANDLE m_hExposedClassObject; + template friend struct ::cdac_offsets; }; +template<> +struct cdac_offsets +{ + static constexpr size_t TypeAndFlags = offsetof(TypeDesc, m_typeAndFlags); +}; /*************************************************************************/ // This variant is used for parameterized types that have exactly one argument @@ -263,6 +269,13 @@ class ParamTypeDesc : public TypeDesc { // The type that is being modified TypeHandle m_Arg; + template friend struct ::cdac_offsets; +}; + +template<> +struct cdac_offsets +{ + static constexpr size_t TypeArg = offsetof(ParamTypeDesc, m_Arg); }; /*************************************************************************/ @@ -381,6 +394,15 @@ class TypeVarTypeDesc : public TypeDesc // index within declaring type or method, numbered from zero unsigned int m_index; + + template friend struct ::cdac_offsets; +}; + +template<> +struct cdac_offsets +{ + static constexpr size_t Module = offsetof(TypeVarTypeDesc, m_pModule); + static constexpr size_t Token = offsetof(TypeVarTypeDesc, m_token); }; /*************************************************************************/ @@ -467,6 +489,16 @@ class FnPtrTypeDesc : public TypeDesc // Return type first, then argument types TypeHandle m_RetAndArgTypes[1]; + + template friend struct ::cdac_offsets; }; // class FnPtrTypeDesc +template<> +struct cdac_offsets +{ + static constexpr size_t NumArgs = offsetof(FnPtrTypeDesc, m_NumArgs); + static constexpr size_t RetAndArgTypes = offsetof(FnPtrTypeDesc, m_RetAndArgTypes); + static constexpr size_t CallConv = offsetof(FnPtrTypeDesc, m_CallConv); +}; + #endif // TYPEDESC_H diff --git a/src/native/managed/cdacreader/src/Contracts/RuntimeTypeSystem.cs b/src/native/managed/cdacreader/src/Contracts/RuntimeTypeSystem.cs index 8d37ca5441fcd3..b7267b5f8c23cb 100644 --- a/src/native/managed/cdacreader/src/Contracts/RuntimeTypeSystem.cs +++ b/src/native/managed/cdacreader/src/Contracts/RuntimeTypeSystem.cs @@ -6,15 +6,53 @@ namespace Microsoft.Diagnostics.DataContractReader.Contracts; -// an opaque handle to a method table. See IMetadata.GetMethodTableData -internal readonly struct MethodTableHandle +// an opaque handle to a type handle. See IMetadata.GetMethodTableData +internal readonly struct TypeHandle { - internal MethodTableHandle(TargetPointer address) + internal TypeHandle(TargetPointer address) { Address = address; } internal TargetPointer Address { get; } + + internal bool IsNull => Address == 0; +} + +internal enum CorElementType +{ + Void = 1, + Boolean = 2, + Char = 3, + I1 = 4, + U1 = 5, + I2 = 6, + U2 = 7, + I4 = 8, + U4 = 9, + I8 = 0xa, + U8 = 0xb, + R4 = 0xc, + R8 = 0xd, + String = 0xe, + Ptr = 0xf, + Byref = 0x10, + ValueType = 0x11, + Class = 0x12, + Var = 0x13, + Array = 0x14, + GenericInst = 0x15, + TypedByRef = 0x16, + I = 0x18, + U = 0x19, + FnPtr = 0x1b, + Object = 0x1c, + SzArray = 0x1d, + MVar = 0x1e, + CModReqd = 0x1f, + CModOpt = 0x20, + Internal = 0x21, + Sentinel = 0x41, } internal interface IRuntimeTypeSystem : IContract @@ -31,34 +69,50 @@ static IContract IContract.Create(Target target, int version) }; } - #region MethodTable inspection APIs - public virtual MethodTableHandle GetMethodTableHandle(TargetPointer targetPointer) => throw new NotImplementedException(); - - public virtual TargetPointer GetModule(MethodTableHandle methodTable) => throw new NotImplementedException(); + #region TypeHandle inspection APIs + public virtual TypeHandle GetTypeHandle(TargetPointer address) => throw new NotImplementedException(); + public virtual TargetPointer GetModule(TypeHandle typeHandle) => throw new NotImplementedException(); // A canonical method table is either the MethodTable itself, or in the case of a generic instantiation, it is the // MethodTable of the prototypical instance. - public virtual TargetPointer GetCanonicalMethodTable(MethodTableHandle methodTable) => throw new NotImplementedException(); - public virtual TargetPointer GetParentMethodTable(MethodTableHandle methodTable) => throw new NotImplementedException(); + public virtual TargetPointer GetCanonicalMethodTable(TypeHandle typeHandle) => throw new NotImplementedException(); + public virtual TargetPointer GetParentMethodTable(TypeHandle typeHandle) => throw new NotImplementedException(); - public virtual uint GetBaseSize(MethodTableHandle methodTable) => throw new NotImplementedException(); + public virtual uint GetBaseSize(TypeHandle typeHandle) => throw new NotImplementedException(); // The component size is only available for strings and arrays. It is the size of the element type of the array, or the size of an ECMA 335 character (2 bytes) - public virtual uint GetComponentSize(MethodTableHandle methodTable) => throw new NotImplementedException(); + public virtual uint GetComponentSize(TypeHandle typeHandle) => throw new NotImplementedException(); // True if the MethodTable is the sentinel value associated with unallocated space in the managed heap - public virtual bool IsFreeObjectMethodTable(MethodTableHandle methodTable) => throw new NotImplementedException(); - public virtual bool IsString(MethodTableHandle methodTable) => throw new NotImplementedException(); + public virtual bool IsFreeObjectMethodTable(TypeHandle typeHandle) => throw new NotImplementedException(); + public virtual bool IsString(TypeHandle typeHandle) => throw new NotImplementedException(); // True if the MethodTable represents a type that contains managed references - public virtual bool ContainsGCPointers(MethodTableHandle methodTable) => throw new NotImplementedException(); - public virtual bool IsDynamicStatics(MethodTableHandle methodTable) => throw new NotImplementedException(); - public virtual ushort GetNumMethods(MethodTableHandle methodTable) => throw new NotImplementedException(); - public virtual ushort GetNumInterfaces(MethodTableHandle methodTable) => throw new NotImplementedException(); + public virtual bool ContainsGCPointers(TypeHandle typeHandle) => throw new NotImplementedException(); + public virtual bool IsDynamicStatics(TypeHandle typeHandle) => throw new NotImplementedException(); + public virtual ushort GetNumMethods(TypeHandle typeHandle) => throw new NotImplementedException(); + public virtual ushort GetNumInterfaces(TypeHandle typeHandle) => throw new NotImplementedException(); // Returns an ECMA-335 TypeDef table token for this type, or for its generic type definition if it is a generic instantiation - public virtual uint GetTypeDefToken(MethodTableHandle methodTable) => throw new NotImplementedException(); + public virtual uint GetTypeDefToken(TypeHandle typeHandle) => throw new NotImplementedException(); // Returns the ECMA 335 TypeDef table Flags value (a bitmask of TypeAttributes) for this type, // or for its generic type definition if it is a generic instantiation - public virtual uint GetTypeDefTypeAttributes(MethodTableHandle methodTable) => throw new NotImplementedException(); - #endregion MethodTable inspection APIs + public virtual uint GetTypeDefTypeAttributes(TypeHandle typeHandle) => throw new NotImplementedException(); + + public virtual ReadOnlySpan GetInstantiation(TypeHandle typeHandle) => throw new NotImplementedException(); + public virtual bool IsGenericTypeDefinition(TypeHandle typeHandle) => throw new NotImplementedException(); + + public virtual bool HasTypeParam(TypeHandle typeHandle) => throw new NotImplementedException(); + + // Element type of the type. NOTE: this drops the CorElementType.GenericInst, and CorElementType.String is returned as CorElementType.Class. + // If this returns CorElementType.ValueType it may be a normal valuetype or a "NATIVE" valuetype used to represent an interop view on a structure + // HasTypeParam will return true for cases where this is the interop view + public virtual CorElementType GetSignatureCorElementType(TypeHandle typeHandle) => throw new NotImplementedException(); + + // return true if the TypeHandle represents an array, and set the rank to either 0 (if the type is not an array), or the rank number if it is. + public virtual bool IsArray(TypeHandle typeHandle, out uint rank) => throw new NotImplementedException(); + public virtual TypeHandle GetTypeParam(TypeHandle typeHandle) => throw new NotImplementedException(); + public virtual bool IsGenericVariable(TypeHandle typeHandle, out TargetPointer module, out uint token) => throw new NotImplementedException(); + public virtual bool IsFunctionPointer(TypeHandle typeHandle, out ReadOnlySpan retAndArgTypes, out byte callConv) => throw new NotImplementedException(); + // Returns null if the TypeHandle is not a class/struct/generic variable + #endregion TypeHandle inspection APIs } internal struct RuntimeTypeSystem : IRuntimeTypeSystem diff --git a/src/native/managed/cdacreader/src/Contracts/RuntimeTypeSystem_1.MethodTableFlags.cs b/src/native/managed/cdacreader/src/Contracts/RuntimeTypeSystem_1.MethodTableFlags.cs index e6f98f0d8e2f08..6e02d241b994c4 100644 --- a/src/native/managed/cdacreader/src/Contracts/RuntimeTypeSystem_1.MethodTableFlags.cs +++ b/src/native/managed/cdacreader/src/Contracts/RuntimeTypeSystem_1.MethodTableFlags.cs @@ -14,6 +14,7 @@ internal enum WFLAGS_LOW : uint { GenericsMask = 0x00000030, GenericsMask_NonGeneric = 0x00000000, // no instantiation + GenericsMask_TypicalInstantiation = 0x00000030, // the type instantiated at its formal parameters, e.g. List StringArrayValues = GenericsMask_NonGeneric | @@ -26,7 +27,13 @@ internal enum WFLAGS_HIGH : uint { Category_Mask = 0x000F0000, Category_Array = 0x00080000, + Category_IfArrayThenSzArray = 0x00020000, Category_Array_Mask = 0x000C0000, + Category_ElementType_Mask = 0x000E0000, + Category_ValueType = 0x00040000, + Category_Nullable = 0x00050000, + Category_PrimitiveValueType = 0x00060000, + Category_TruePrimitive = 0x00070000, Category_Interface = 0x000C0000, ContainsGCPointers = 0x01000000, HasComponentSize = 0x80000000, // This is set if lower 16 bits is used for the component size, @@ -83,5 +90,6 @@ private bool TestFlagWithMask(WFLAGS2_ENUM mask, WFLAGS2_ENUM flag) public bool HasInstantiation => !TestFlagWithMask(WFLAGS_LOW.GenericsMask, WFLAGS_LOW.GenericsMask_NonGeneric); public bool ContainsGCPointers => GetFlag(WFLAGS_HIGH.ContainsGCPointers) != 0; public bool IsDynamicStatics => GetFlag(WFLAGS2_ENUM.DynamicStatics) != 0; + public bool IsGenericTypeDefinition => TestFlagWithMask(WFLAGS_LOW.GenericsMask, WFLAGS_LOW.GenericsMask_TypicalInstantiation); } } diff --git a/src/native/managed/cdacreader/src/Contracts/RuntimeTypeSystem_1.cs b/src/native/managed/cdacreader/src/Contracts/RuntimeTypeSystem_1.cs index da2ea6c8b01b3e..9c3c3bc3f4974d 100644 --- a/src/native/managed/cdacreader/src/Contracts/RuntimeTypeSystem_1.cs +++ b/src/native/managed/cdacreader/src/Contracts/RuntimeTypeSystem_1.cs @@ -4,11 +4,13 @@ using System; using System.Collections.Generic; using System.Reflection.Metadata.Ecma335; +using Microsoft.Diagnostics.DataContractReader.Data; +using Microsoft.Diagnostics.DataContractReader.Contracts.RuntimeTypeSystem_1_NS; +using System.Security.Cryptography.X509Certificates; +using System.Diagnostics; namespace Microsoft.Diagnostics.DataContractReader.Contracts; - - internal partial struct RuntimeTypeSystem_1 : IRuntimeTypeSystem { private readonly Target _target; @@ -27,6 +29,7 @@ internal struct MethodTable internal TargetPointer ParentMethodTable { get; } internal TargetPointer Module { get; } internal TargetPointer EEClassOrCanonMT { get; } + internal TargetPointer PerInstInfo { get; } internal MethodTable(Data.MethodTable data) { Flags = new MethodTableFlags @@ -40,6 +43,7 @@ internal MethodTable(Data.MethodTable data) EEClassOrCanonMT = data.EEClassOrCanonMT; Module = data.Module; ParentMethodTable = data.ParentMethodTable; + PerInstInfo = data.PerInstInfo; } } @@ -53,6 +57,16 @@ internal enum EEClassOrCanonMTBits Mask = 1, } + // Low order bits of TypeHandle address. + // If the low bits contain a 2, then it is a TypeDesc + [Flags] + internal enum TypeHandleBits + { + MethodTable = 0, + TypeDesc = 2, + ValidMask = 2, + } + internal RuntimeTypeSystem_1(Target target, TargetPointer freeObjectMethodTablePointer) { _target = target; @@ -61,21 +75,37 @@ internal RuntimeTypeSystem_1(Target target, TargetPointer freeObjectMethodTableP internal TargetPointer FreeObjectMethodTablePointer => _freeObjectMethodTablePointer; - - public MethodTableHandle GetMethodTableHandle(TargetPointer methodTablePointer) + public TypeHandle GetTypeHandle(TargetPointer typeHandlePointer) { + TypeHandleBits addressLowBits = (TypeHandleBits)((ulong)typeHandlePointer & ((ulong)_target.PointerSize - 1)); + + if ((addressLowBits != TypeHandleBits.MethodTable) && (addressLowBits != TypeHandleBits.TypeDesc)) + { + throw new InvalidOperationException("Invalid type handle pointer"); + } + // if we already validated this address, return a handle - if (_methodTables.ContainsKey(methodTablePointer)) + if (_methodTables.ContainsKey(typeHandlePointer)) + { + return new TypeHandle(typeHandlePointer); + } + + // Check for a TypeDesc + if (addressLowBits == TypeHandleBits.TypeDesc) { - return new MethodTableHandle(methodTablePointer); + // This is a TypeDesc + return new TypeHandle(typeHandlePointer); } + + TargetPointer methodTablePointer = typeHandlePointer; + // Check if we cached the underlying data already if (_target.ProcessedData.TryGet(methodTablePointer, out Data.MethodTable? methodTableData)) { // we already cached the data, we must have validated the address, create the representation struct for our use MethodTable trustedMethodTable = new MethodTable(methodTableData); _ = _methodTables.TryAdd(methodTablePointer, trustedMethodTable); - return new MethodTableHandle(methodTablePointer); + return new TypeHandle(methodTablePointer); } // If it's the free object method table, we trust it to be valid @@ -84,7 +114,7 @@ public MethodTableHandle GetMethodTableHandle(TargetPointer methodTablePointer) Data.MethodTable freeObjectMethodTableData = _target.ProcessedData.GetOrAdd(methodTablePointer); MethodTable trustedMethodTable = new MethodTable(freeObjectMethodTableData); _ = _methodTables.TryAdd(methodTablePointer, trustedMethodTable); - return new MethodTableHandle(methodTablePointer); + return new TypeHandle(methodTablePointer); } // Otherwse, get ready to validate @@ -98,24 +128,23 @@ public MethodTableHandle GetMethodTableHandle(TargetPointer methodTablePointer) Data.MethodTable trustedMethodTableData = _target.ProcessedData.GetOrAdd(methodTablePointer); MethodTable trustedMethodTableF = new MethodTable(trustedMethodTableData); _ = _methodTables.TryAdd(methodTablePointer, trustedMethodTableF); - return new MethodTableHandle(methodTablePointer); + return new TypeHandle(methodTablePointer); } + public uint GetBaseSize(TypeHandle typeHandle) => !typeHandle.IsMethodTable() ? (uint)0 : _methodTables[typeHandle.Address].Flags.BaseSize; - public uint GetBaseSize(MethodTableHandle methodTableHandle) => _methodTables[methodTableHandle.Address].Flags.BaseSize; - - public uint GetComponentSize(MethodTableHandle methodTableHandle) => _methodTables[methodTableHandle.Address].Flags.ComponentSize; + public uint GetComponentSize(TypeHandle typeHandle) => !typeHandle.IsMethodTable() ? (uint)0 : _methodTables[typeHandle.Address].Flags.ComponentSize; - private TargetPointer GetClassPointer(MethodTableHandle methodTableHandle) + private TargetPointer GetClassPointer(TypeHandle typeHandle) { - MethodTable methodTable = _methodTables[methodTableHandle.Address]; + MethodTable methodTable = _methodTables[typeHandle.Address]; switch (GetEEClassOrCanonMTBits(methodTable.EEClassOrCanonMT)) { case EEClassOrCanonMTBits.EEClass: return methodTable.EEClassOrCanonMT; case EEClassOrCanonMTBits.CanonMT: TargetPointer canonMTPtr = new TargetPointer((ulong)methodTable.EEClassOrCanonMT & ~(ulong)RuntimeTypeSystem_1.EEClassOrCanonMTBits.Mask); - MethodTableHandle canonMTHandle = GetMethodTableHandle(canonMTPtr); + TypeHandle canonMTHandle = GetTypeHandle(canonMTPtr); MethodTable canonMT = _methodTables[canonMTHandle.Address]; return canonMT.EEClassOrCanonMT; // canonical method table EEClassOrCanonMT is always EEClass default: @@ -124,34 +153,268 @@ private TargetPointer GetClassPointer(MethodTableHandle methodTableHandle) } // only called on validated method tables, so we don't need to re-validate the EEClass - private Data.EEClass GetClassData(MethodTableHandle methodTableHandle) + private Data.EEClass GetClassData(TypeHandle typeHandle) { - TargetPointer clsPtr = GetClassPointer(methodTableHandle); + TargetPointer clsPtr = GetClassPointer(typeHandle); return _target.ProcessedData.GetOrAdd(clsPtr); } - public TargetPointer GetCanonicalMethodTable(MethodTableHandle methodTableHandle) => GetClassData(methodTableHandle).MethodTable; + public TargetPointer GetCanonicalMethodTable(TypeHandle typeHandle) => !typeHandle.IsMethodTable() ? TargetPointer.Null : GetClassData(typeHandle).MethodTable; + + public TargetPointer GetModule(TypeHandle typeHandle) + { + if (typeHandle.IsMethodTable()) + { + return _methodTables[typeHandle.Address].Module; + } + else if (typeHandle.IsTypeDesc()) + { + if (HasTypeParam(typeHandle)) + { + return GetModule(GetTypeParam(typeHandle)); + } + else if (IsGenericVariable(typeHandle, out TargetPointer genericParamModule, out _)) + { + return genericParamModule; + } + else + { + System.Diagnostics.Debug.Assert(IsFunctionPointer(typeHandle, out _, out _)); + return TargetPointer.Null; + } + } + else + { + return TargetPointer.Null; + } + } - public TargetPointer GetModule(MethodTableHandle methodTableHandle) => _methodTables[methodTableHandle.Address].Module; - public TargetPointer GetParentMethodTable(MethodTableHandle methodTableHandle) => _methodTables[methodTableHandle.Address].ParentMethodTable; + public TargetPointer GetParentMethodTable(TypeHandle typeHandle) => !typeHandle.IsMethodTable() ? TargetPointer.Null : _methodTables[typeHandle.Address].ParentMethodTable; - public bool IsFreeObjectMethodTable(MethodTableHandle methodTableHandle) => FreeObjectMethodTablePointer == methodTableHandle.Address; + public bool IsFreeObjectMethodTable(TypeHandle typeHandle) => FreeObjectMethodTablePointer == typeHandle.Address; - public bool IsString(MethodTableHandle methodTableHandle) => _methodTables[methodTableHandle.Address].Flags.IsString; - public bool ContainsGCPointers(MethodTableHandle methodTableHandle) => _methodTables[methodTableHandle.Address].Flags.ContainsGCPointers; + public bool IsString(TypeHandle typeHandle) => !typeHandle.IsMethodTable() ? false : _methodTables[typeHandle.Address].Flags.IsString; + public bool ContainsGCPointers(TypeHandle typeHandle) => !typeHandle.IsMethodTable() ? false : _methodTables[typeHandle.Address].Flags.ContainsGCPointers; - public uint GetTypeDefToken(MethodTableHandle methodTableHandle) + public uint GetTypeDefToken(TypeHandle typeHandle) { - MethodTable methodTable = _methodTables[methodTableHandle.Address]; + if (!typeHandle.IsMethodTable()) + return 0; + MethodTable methodTable = _methodTables[typeHandle.Address]; return (uint)(methodTable.Flags.GetTypeDefRid() | ((int)TableIndex.TypeDef << 24)); } - public ushort GetNumMethods(MethodTableHandle methodTableHandle) => GetClassData(methodTableHandle).NumMethods; + public ushort GetNumMethods(TypeHandle typeHandle) => !typeHandle.IsMethodTable() ? (ushort)0 : GetClassData(typeHandle).NumMethods; + + public ushort GetNumInterfaces(TypeHandle typeHandle) => !typeHandle.IsMethodTable() ? (ushort)0 : _methodTables[typeHandle.Address].NumInterfaces; + + public uint GetTypeDefTypeAttributes(TypeHandle typeHandle) => !typeHandle.IsMethodTable() ? (uint)0 : GetClassData(typeHandle).CorTypeAttr; + + public bool IsDynamicStatics(TypeHandle typeHandle) => !typeHandle.IsMethodTable() ? false : _methodTables[typeHandle.Address].Flags.IsDynamicStatics; + + public ReadOnlySpan GetInstantiation(TypeHandle typeHandle) + { + if (!typeHandle.IsMethodTable()) + return default; + + MethodTable methodTable = _methodTables[typeHandle.Address]; + if (!methodTable.Flags.HasInstantiation) + return default; + + return _target.ProcessedData.GetOrAdd(typeHandle.Address).TypeHandles; + } + + private class TypeInstantiation : IData + { + public static TypeInstantiation Create(Target target, TargetPointer address) => new TypeInstantiation(target, address); + + public TypeHandle[] TypeHandles { get; } + private TypeInstantiation(Target target, TargetPointer typePointer) + { + RuntimeTypeSystem_1 rts = (RuntimeTypeSystem_1)target.Contracts.RuntimeTypeSystem; + MethodTable methodTable = rts._methodTables[typePointer]; + Debug.Assert(methodTable.Flags.HasInstantiation); + + TargetPointer perInstInfo = methodTable.PerInstInfo; + TargetPointer genericsDictInfo = perInstInfo - (ulong)target.PointerSize; + + TargetPointer dictionaryPointer = target.ReadPointer(perInstInfo); + + + int numberOfGenericArgs = target.ProcessedData.GetOrAdd(genericsDictInfo).NumTypeArgs; + + TypeHandles = new TypeHandle[numberOfGenericArgs]; + for (int i = 0; i < numberOfGenericArgs; i++) + { + TypeHandles[i] = rts.GetTypeHandle(target.ReadPointer(dictionaryPointer + (ulong)target.PointerSize * (ulong)i)); + } + } + } + + public bool IsGenericTypeDefinition(TypeHandle typeHandle) => !typeHandle.IsMethodTable() ? false : _methodTables[typeHandle.Address].Flags.IsGenericTypeDefinition; + + public bool HasTypeParam(TypeHandle typeHandle) + { + if (typeHandle.IsMethodTable()) + { + MethodTable methodTable = _methodTables[typeHandle.Address]; + return methodTable.Flags.IsArray; + } + else if (typeHandle.IsTypeDesc()) + { + var typeDesc = _target.ProcessedData.GetOrAdd(typeHandle.TypeDescAddress()); + CorElementType elemType = (CorElementType)(typeDesc.TypeAndFlags & 0xFF); + switch (elemType) + { + case CorElementType.ValueType: + case CorElementType.Byref: + case CorElementType.Ptr: + return true; + } + } + return false; + } + + public CorElementType GetSignatureCorElementType(TypeHandle typeHandle) + { + if (typeHandle.IsMethodTable()) + { + MethodTable methodTable = _methodTables[typeHandle.Address]; + + switch (methodTable.Flags.GetFlag(WFLAGS_HIGH.Category_Mask)) + { + case WFLAGS_HIGH.Category_Array: + return CorElementType.Array; + case WFLAGS_HIGH.Category_Array | WFLAGS_HIGH.Category_IfArrayThenSzArray: + return CorElementType.SzArray; + case WFLAGS_HIGH.Category_ValueType: + case WFLAGS_HIGH.Category_Nullable: + case WFLAGS_HIGH.Category_PrimitiveValueType: + return CorElementType.ValueType; + case WFLAGS_HIGH.Category_TruePrimitive: + return (CorElementType)GetClassData(typeHandle).InternalCorElementType; + default: + return CorElementType.Class; + } + } + else if (typeHandle.IsTypeDesc()) + { + var typeDesc = _target.ProcessedData.GetOrAdd(typeHandle.TypeDescAddress()); + return (CorElementType)(typeDesc.TypeAndFlags & 0xFF); + } + + return default; + } + + // return true if the TypeHandle represents an array, and set the rank to either 0 (if the type is not an array), or the rank number if it is. + public bool IsArray(TypeHandle typeHandle, out uint rank) + { + if (typeHandle.IsMethodTable()) + { + MethodTable methodTable = _methodTables[typeHandle.Address]; + + switch (methodTable.Flags.GetFlag(WFLAGS_HIGH.Category_Mask)) + { + case WFLAGS_HIGH.Category_Array: + TargetPointer clsPtr = GetClassPointer(typeHandle); + rank = _target.ProcessedData.GetOrAdd(clsPtr).Rank; + return true; + + case WFLAGS_HIGH.Category_Array | WFLAGS_HIGH.Category_IfArrayThenSzArray: + rank = 1; + return true; + } + } + + rank = 0; + return false; + } + + public TypeHandle GetTypeParam(TypeHandle typeHandle) + { + if (typeHandle.IsMethodTable()) + { + MethodTable methodTable = _methodTables[typeHandle.Address]; + if (!methodTable.Flags.IsArray) + throw new ArgumentException(nameof(typeHandle)); + + return GetTypeHandle(methodTable.PerInstInfo); + } + else if (typeHandle.IsTypeDesc()) + { + var typeDesc = _target.ProcessedData.GetOrAdd(typeHandle.TypeDescAddress()); + CorElementType elemType = (CorElementType)(typeDesc.TypeAndFlags & 0xFF); + switch (elemType) + { + case CorElementType.ValueType: + case CorElementType.Byref: + case CorElementType.Ptr: + ParamTypeDesc paramTypeDesc = _target.ProcessedData.GetOrAdd(typeHandle.TypeDescAddress()); + return GetTypeHandle(paramTypeDesc.TypeArg); + } + } + throw new ArgumentException(nameof(typeHandle)); + } + + public bool IsGenericVariable(TypeHandle typeHandle, out TargetPointer module, out uint token) + { + module = TargetPointer.Null; + token = 0; + + if (!typeHandle.IsTypeDesc()) + return false; + + var typeDesc = _target.ProcessedData.GetOrAdd(typeHandle.TypeDescAddress()); + CorElementType elemType = (CorElementType)(typeDesc.TypeAndFlags & 0xFF); + switch (elemType) + { + case CorElementType.MVar: + case CorElementType.Var: + TypeVarTypeDesc typeVarTypeDesc = _target.ProcessedData.GetOrAdd(typeHandle.TypeDescAddress()); + module = typeVarTypeDesc.Module; + token = typeVarTypeDesc.Token; + return true; + } + return false; + } + + public bool IsFunctionPointer(TypeHandle typeHandle, out ReadOnlySpan retAndArgTypes, out byte callConv) + { + retAndArgTypes = default; + callConv = default; - public ushort GetNumInterfaces(MethodTableHandle methodTableHandle) => _methodTables[methodTableHandle.Address].NumInterfaces; + if (!typeHandle.IsTypeDesc()) + return false; - public uint GetTypeDefTypeAttributes(MethodTableHandle methodTableHandle) => GetClassData(methodTableHandle).CorTypeAttr; + var typeDesc = _target.ProcessedData.GetOrAdd(typeHandle.TypeDescAddress()); + CorElementType elemType = (CorElementType)(typeDesc.TypeAndFlags & 0xFF); + if (elemType != CorElementType.FnPtr) + return false; - public bool IsDynamicStatics(MethodTableHandle methodTableHandle) => _methodTables[methodTableHandle.Address].Flags.IsDynamicStatics; + FnPtrTypeDesc fnPtrTypeDesc = _target.ProcessedData.GetOrAdd(typeHandle.TypeDescAddress()); + retAndArgTypes = _target.ProcessedData.GetOrAdd(typeHandle.TypeDescAddress()).TypeHandles; + callConv = (byte)fnPtrTypeDesc.CallConv; + return true; + } + + private class FunctionPointerRetAndArgs : IData + { + public static FunctionPointerRetAndArgs Create(Target target, TargetPointer address) => new FunctionPointerRetAndArgs(target, address); + public TypeHandle[] TypeHandles { get; } + private FunctionPointerRetAndArgs(Target target, TargetPointer typePointer) + { + RuntimeTypeSystem_1 rts = (RuntimeTypeSystem_1)target.Contracts.RuntimeTypeSystem; + FnPtrTypeDesc fnPtrTypeDesc = target.ProcessedData.GetOrAdd(typePointer); + + TargetPointer retAndArgs = fnPtrTypeDesc.RetAndArgTypes; + int numberOfRetAndArgTypes = checked((int)fnPtrTypeDesc.NumArgs + 1); + + TypeHandles = new TypeHandle[numberOfRetAndArgTypes]; + for (int i = 0; i < numberOfRetAndArgTypes; i++) + { + TypeHandles[i] = rts.GetTypeHandle(target.ReadPointer(retAndArgs + (ulong)target.PointerSize * (ulong)i)); + } + } + } } diff --git a/src/native/managed/cdacreader/src/Contracts/RuntimeTypeSystem_1_Helpers.cs b/src/native/managed/cdacreader/src/Contracts/RuntimeTypeSystem_1_Helpers.cs new file mode 100644 index 00000000000000..355897000ec69b --- /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/EEClass.cs b/src/native/managed/cdacreader/src/Data/EEClass.cs index e697b3f40756c8..531a30389e41c0 100644 --- a/src/native/managed/cdacreader/src/Data/EEClass.cs +++ b/src/native/managed/cdacreader/src/Data/EEClass.cs @@ -15,9 +15,32 @@ public EEClass(Target target, TargetPointer address) MethodTable = target.ReadPointer(address + (ulong)type.Fields[nameof(MethodTable)].Offset); NumMethods = target.Read(address + (ulong)type.Fields[nameof(NumMethods)].Offset); CorTypeAttr = target.Read(address + (ulong)type.Fields[nameof(CorTypeAttr)].Offset); + InternalCorElementType = target.Read(address + (ulong)type.Fields[nameof(InternalCorElementType)].Offset); } public TargetPointer MethodTable { get; init; } public ushort NumMethods { get; init; } public uint CorTypeAttr { get; init; } + + // An InternalCorElementType uses the enum values of a CorElementType to + // indicate some of the information about the type of the type which uses + // the EEClass + // + // In particular. All reference types are ELEMENT_TYPE_CLASS + // Enums are the element type of their underlying type + // ValueTypes which can exactly be represented as an element type are represented as such + public byte InternalCorElementType { get; init; } +} + +public sealed class ArrayClass : IData +{ + static ArrayClass IData.Create(Target target, TargetPointer address) => new ArrayClass(target, address); + public ArrayClass(Target target, TargetPointer address) + { + Target.TypeInfo type = target.GetTypeInfo(DataType.ArrayClass); + + Rank = target.Read(address + (ulong)type.Fields[nameof(Rank)].Offset); + } + + public byte Rank { get; init; } } diff --git a/src/native/managed/cdacreader/src/Data/GenericsDictInfo.cs b/src/native/managed/cdacreader/src/Data/GenericsDictInfo.cs new file mode 100644 index 00000000000000..a25f4a87c8471b --- /dev/null +++ b/src/native/managed/cdacreader/src/Data/GenericsDictInfo.cs @@ -0,0 +1,23 @@ +// 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); + + NumTypeArgs = target.Read(address + (ulong)type.Fields[nameof(NumTypeArgs)].Offset); + } + + public ushort NumTypeArgs { get; init; } +} diff --git a/src/native/managed/cdacreader/src/Data/MethodTable.cs b/src/native/managed/cdacreader/src/Data/MethodTable.cs index 3319c6547f0568..b1b6723d556541 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/TypeDesc.cs b/src/native/managed/cdacreader/src/Data/TypeDesc.cs new file mode 100644 index 00000000000000..5cef78482999de --- /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/DataType.cs b/src/native/managed/cdacreader/src/DataType.cs index 3f20bdf7b095bc..9f64fef1e28dff 100644 --- a/src/native/managed/cdacreader/src/DataType.cs +++ b/src/native/managed/cdacreader/src/DataType.cs @@ -29,5 +29,11 @@ public enum DataType Module, MethodTable, EEClass, + ArrayClass, MethodTableAuxiliaryData, + GenericsDictInfo, + TypeDesc, + ParamTypeDesc, + TypeVarTypeDesc, + FnPtrTypeDesc, } diff --git a/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs b/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs index 5ebbbd7a7471e9..bcc47d1971954b 100644 --- a/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs +++ b/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs @@ -1,7 +1,11 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using Microsoft.Diagnostics.DataContractReader.Contracts; using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Diagnostics.Contracts; using System.Runtime.InteropServices; using System.Runtime.InteropServices.Marshalling; @@ -89,7 +93,7 @@ public unsafe int GetMethodTableData(ulong mt, DacpMethodTableData* data) try { Contracts.IRuntimeTypeSystem contract = _target.Contracts.RuntimeTypeSystem; - Contracts.MethodTableHandle methodTable = contract.GetMethodTableHandle(mt); + Contracts.TypeHandle methodTable = contract.GetTypeHandle(mt); DacpMethodTableData result = default; result.baseSize = contract.GetBaseSize(methodTable); @@ -123,7 +127,7 @@ public unsafe int GetMethodTableData(ulong mt, DacpMethodTableData* data) *data = result; return HResults.S_OK; } - catch (Exception ex) + catch (System.Exception ex) { return ex.HResult; } @@ -137,11 +141,11 @@ public unsafe int GetMethodTableForEEClass(ulong eeClassReallyCanonMT, ulong* va try { Contracts.IRuntimeTypeSystem contract = _target.Contracts.RuntimeTypeSystem; - Contracts.MethodTableHandle methodTableHandle = contract.GetMethodTableHandle(eeClassReallyCanonMT); + Contracts.TypeHandle methodTableHandle = contract.GetTypeHandle(eeClassReallyCanonMT); *value = methodTableHandle.Address; return HResults.S_OK; } - catch (Exception ex) + catch (global::System.Exception ex) { return ex.HResult; } @@ -191,7 +195,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 +212,7 @@ public unsafe int GetNestedExceptionData(ulong exception, ulong* exceptionObject *exceptionObject = exceptionObjectLocal; *nextNestedException = nextNestedExceptionLocal; } - catch (Exception ex) + catch (global::System.Exception ex) { return ex.HResult; } @@ -234,7 +238,7 @@ public unsafe int GetObjectExceptionData(ulong objectAddress, DacpExceptionObjec data->HResult = exceptionData.HResult; data->XCode = exceptionData.XCode; } - catch (Exception ex) + catch (System.Exception ex) { return ex.HResult; } @@ -284,7 +288,7 @@ public unsafe int GetThreadData(ulong thread, DacpThreadData* data) data->lastThrownObjectHandle = threadData.LastThrownObjectHandle; data->nextThread = threadData.NextThread; } - catch (Exception ex) + catch (global::System.Exception ex) { return ex.HResult; } @@ -314,7 +318,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/Target.cs b/src/native/managed/cdacreader/src/Target.cs index 06b201d3957d55..268f7cd492a5d0 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 /// @@ -246,6 +259,8 @@ private static DataType GetDataType(string type) return DataType.Unknown; } + public int PointerSize => _config.PointerSize; + public T Read(ulong address) where T : unmanaged, IBinaryInteger, IMinMaxValue { if (!TryRead(address, _config.IsLittleEndian, _reader, out T value)) @@ -266,6 +281,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 +306,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 8e2dbca07db9e66326556792356d17a5e18e4f1e Mon Sep 17 00:00:00 2001 From: Aleksey Kliger Date: Tue, 16 Jul 2024 16:10:38 -0400 Subject: [PATCH 13/38] 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 dd451fa075f6ae..1e2c2cf53d9fa9 100644 --- a/src/native/managed/cdacreader/tests/MethodTableTests.cs +++ b/src/native/managed/cdacreader/tests/MethodTableTests.cs @@ -24,6 +24,7 @@ public unsafe class MethodTableTests { nameof(Data.MethodTable.ParentMethodTable), new () { Offset = 40, Type = DataType.pointer}}, { nameof(Data.MethodTable.NumInterfaces), new () { Offset = 48, Type = DataType.uint16}}, { nameof(Data.MethodTable.NumVirtuals), new () { Offset = 50, Type = DataType.uint16}}, + { nameof(Data.MethodTable.PerInstInfo), new () { Offset = 56, Type = DataType.pointer}}, } }; @@ -33,6 +34,7 @@ public unsafe class MethodTableTests { nameof (Data.EEClass.MethodTable), new () { Offset = 8, Type = DataType.pointer}}, { nameof (Data.EEClass.CorTypeAttr), new () { Offset = 16, Type = DataType.uint32}}, { nameof (Data.EEClass.NumMethods), new () { Offset = 20, Type = DataType.uint16}}, + { nameof (Data.EEClass.InternalCorElementType), new () { Offset = 22, Type = DataType.uint8}}, } }; @@ -150,7 +152,7 @@ public void HasRuntimeTypeSystemContract(MockTarget.Architecture arch) { Contracts.IRuntimeTypeSystem metadataContract = target.Contracts.RuntimeTypeSystem; Assert.NotNull(metadataContract); - Contracts.MethodTableHandle handle = metadataContract.GetMethodTableHandle(TestFreeObjectMethodTableAddress); + Contracts.TypeHandle handle = metadataContract.GetTypeHandle(TestFreeObjectMethodTableAddress); Assert.NotEqual(TargetPointer.Null, handle.Address); Assert.True(metadataContract.IsFreeObjectMethodTable(handle)); }); @@ -187,9 +189,9 @@ public void ValidateSystemObjectMethodTable(MockTarget.Architecture arch) { Contracts.IRuntimeTypeSystem metadataContract = target.Contracts.RuntimeTypeSystem; Assert.NotNull(metadataContract); - Contracts.MethodTableHandle systemObjectMethodTableHandle = metadataContract.GetMethodTableHandle(systemObjectMethodTablePtr); - Assert.Equal(systemObjectMethodTablePtr.Value, systemObjectMethodTableHandle.Address.Value); - Assert.False(metadataContract.IsFreeObjectMethodTable(systemObjectMethodTableHandle)); + Contracts.TypeHandle systemObjectTypeHandle = metadataContract.GetTypeHandle(systemObjectMethodTablePtr); + Assert.Equal(systemObjectMethodTablePtr.Value, systemObjectTypeHandle.Address.Value); + Assert.False(metadataContract.IsFreeObjectMethodTable(systemObjectTypeHandle)); }); } @@ -226,10 +228,10 @@ public void ValidateSystemStringMethodTable(MockTarget.Architecture arch) { Contracts.IRuntimeTypeSystem metadataContract = target.Contracts.RuntimeTypeSystem; Assert.NotNull(metadataContract); - Contracts.MethodTableHandle systemStringMethodTableHandle = metadataContract.GetMethodTableHandle(systemStringMethodTablePtr); - Assert.Equal(systemStringMethodTablePtr.Value, systemStringMethodTableHandle.Address.Value); - Assert.False(metadataContract.IsFreeObjectMethodTable(systemStringMethodTableHandle)); - Assert.True(metadataContract.IsString(systemStringMethodTableHandle)); + Contracts.TypeHandle systemStringTypeHandle = metadataContract.GetTypeHandle(systemStringMethodTablePtr); + Assert.Equal(systemStringMethodTablePtr.Value, systemStringTypeHandle.Address.Value); + Assert.False(metadataContract.IsFreeObjectMethodTable(systemStringTypeHandle)); + Assert.True(metadataContract.IsString(systemStringTypeHandle)); }); } @@ -258,7 +260,7 @@ public void MethodTableEEClassInvalidThrows(MockTarget.Architecture arch) { Contracts.IRuntimeTypeSystem metadataContract = target.Contracts.RuntimeTypeSystem; Assert.NotNull(metadataContract); - Assert.Throws(() => metadataContract.GetMethodTableHandle(badMethodTablePtr)); + Assert.Throws(() => metadataContract.GetTypeHandle(badMethodTablePtr)); }); } @@ -308,11 +310,11 @@ public void ValidateGenericInstMethodTable(MockTarget.Architecture arch) { Contracts.IRuntimeTypeSystem metadataContract = target.Contracts.RuntimeTypeSystem; Assert.NotNull(metadataContract); - Contracts.MethodTableHandle genericInstanceMethodTableHandle = metadataContract.GetMethodTableHandle(genericInstanceMethodTablePtr); - Assert.Equal(genericInstanceMethodTablePtr.Value, genericInstanceMethodTableHandle.Address.Value); - Assert.False(metadataContract.IsFreeObjectMethodTable(genericInstanceMethodTableHandle)); - Assert.False(metadataContract.IsString(genericInstanceMethodTableHandle)); - Assert.Equal(numMethods, metadataContract.GetNumMethods(genericInstanceMethodTableHandle)); + Contracts.TypeHandle genericInstanceTypeHandle = metadataContract.GetTypeHandle(genericInstanceMethodTablePtr); + Assert.Equal(genericInstanceMethodTablePtr.Value, genericInstanceTypeHandle.Address.Value); + Assert.False(metadataContract.IsFreeObjectMethodTable(genericInstanceTypeHandle)); + Assert.False(metadataContract.IsString(genericInstanceTypeHandle)); + Assert.Equal(numMethods, metadataContract.GetNumMethods(genericInstanceTypeHandle)); }); } @@ -365,11 +367,11 @@ public void ValidateArrayInstMethodTable(MockTarget.Architecture arch) { Contracts.IRuntimeTypeSystem metadataContract = target.Contracts.RuntimeTypeSystem; Assert.NotNull(metadataContract); - Contracts.MethodTableHandle arrayInstanceMethodTableHandle = metadataContract.GetMethodTableHandle(arrayInstanceMethodTablePtr); - Assert.Equal(arrayInstanceMethodTablePtr.Value, arrayInstanceMethodTableHandle.Address.Value); - Assert.False(metadataContract.IsFreeObjectMethodTable(arrayInstanceMethodTableHandle)); - Assert.False(metadataContract.IsString(arrayInstanceMethodTableHandle)); - Assert.Equal(arrayInstanceComponentSize, metadataContract.GetComponentSize(arrayInstanceMethodTableHandle)); + Contracts.TypeHandle arrayInstanceTypeHandle = metadataContract.GetTypeHandle(arrayInstanceMethodTablePtr); + Assert.Equal(arrayInstanceMethodTablePtr.Value, arrayInstanceTypeHandle.Address.Value); + Assert.False(metadataContract.IsFreeObjectMethodTable(arrayInstanceTypeHandle)); + Assert.False(metadataContract.IsString(arrayInstanceTypeHandle)); + Assert.Equal(arrayInstanceComponentSize, metadataContract.GetComponentSize(arrayInstanceTypeHandle)); }); } From e3964f968a4fe3d20b715114c611ef2ab645454f Mon Sep 17 00:00:00 2001 From: Aleksey Kliger Date: Mon, 8 Jul 2024 15:35:32 -0400 Subject: [PATCH 14/38] start GetMethodDescDataImpl --- src/coreclr/debug/daccess/dacimpl.h | 1 + src/coreclr/debug/daccess/request.cpp | 65 ++++++++++++++++++++++++++- 2 files changed, 65 insertions(+), 1 deletion(-) diff --git a/src/coreclr/debug/daccess/dacimpl.h b/src/coreclr/debug/daccess/dacimpl.h index 3493125334bd07..f6bee37959854c 100644 --- a/src/coreclr/debug/daccess/dacimpl.h +++ b/src/coreclr/debug/daccess/dacimpl.h @@ -1240,6 +1240,7 @@ class ClrDataAccess HRESULT GetMethodTableDataImpl(CLRDATA_ADDRESS mt, struct DacpMethodTableData *data); HRESULT GetMethodTableForEEClassImpl (CLRDATA_ADDRESS eeClassReallyMT, CLRDATA_ADDRESS *value); HRESULT GetObjectExceptionDataImpl(CLRDATA_ADDRESS objAddr, struct DacpExceptionObjectData *data); + HRESULT GetMethodDescDataImpl(CLRDATA_ADDRESS methodDesc, CLRDATA_ADDRESS ip, struct DacpMethodDescData *data, ULONG cRevertedRejitVersions, DacpReJitData * rgRevertedRejitData, ULONG * pcNeededRevertedRejitData); 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 8645398923bdb1..e4b3fca231aec1 100644 --- a/src/coreclr/debug/daccess/request.cpp +++ b/src/coreclr/debug/daccess/request.cpp @@ -1041,6 +1041,70 @@ HRESULT ClrDataAccess::GetMethodDescData( } 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->GetMethodDescData(methodDesc, ip, methodDescData, cRevertedRejitVersions, rgRevertedRejitData, pcNeededRevertedRejitData); + if (FAILED(hr)) + { + hr = GetMethodDescDataImpl(methodDesc, ip, methodDescData, cRevertedRejitVersions, rgRevertedRejitData, pcNeededRevertedRejitData); + } +#ifdef _DEBUG + else + { + // Assert that the data is the same as what we get from the DAC. + DacpMethodDescData mdDataLocal; + DacpReJitData *rgRevertedRejitDataLocal = NULL; + if (rgRevertedRejitData != NULL) + { + rgRevertedRejitDataLocal = new DacpReJitData[cRevertedRejitVersions]; + } + ULONG cNeededRevertedRejitDataLocal = 0; + ULONG *pcNeededRevertedRejitDataLocal = NULL; + if (pcNeededRevertedRejitData != NULL) + { + pcNeededRevertedRejitDataLocal = &cNeededRevertedRejitDataLocal; + } + HRESULT hrLocal = GetMethodDescDataImpl(methodDesc, ip,&mdDataLocal, cRevertedRejitVersions, rgRevertedRejitDataLocal, pcNeededRevertedRejitDataLocal); + _ASSERTE(hr == hrLocal); + _ASSERTE(methodDescData->bHasNativeCode == mdDataLocal.bHasNativeCode); + _ASSERTE(methodDescData->bIsDynamic == mdDataLocal.bIsDynamic); + _ASSERTE(methodDescData->wSlotNumber == mdDataLocal.wSlotNumber); + _ASSERTE(methodDescData->NativeCodeAddr == mdDataLocal.NativeCodeAddr); + _ASSERTE(methodDescData->AddressOfNativeCodeSlot == mdDataLocal.AddressOfNativeCodeSlot); + //TODO[cdac]: assert the rest of mdDataLocal contains the same info as methodDescData + if (rgRevertedRejitData != NULL) + { + _ASSERTE (cNeededRevertedRejitDataLocal == *pcNeededRevertedRejitData); + for (ULONG i = 0; i < cNeededRevertedRejitDataLocal; i++) + { + _ASSERTE(rgRevertedRejitData[i].rejitID == rgRevertedRejitDataLocal[i].rejitID); + _ASSERTE(rgRevertedRejitData[i].NativeCodeAddr == rgRevertedRejitDataLocal[i].NativeCodeAddr); + _ASSERTE(rgRevertedRejitData[i].flags == rgRevertedRejitDataLocal[i].flags); + } + } + } +#endif + } + else + { + hr = GetMethodDescDataImpl(methodDesc, ip, methodDescData, cRevertedRejitVersions, rgRevertedRejitData, pcNeededRevertedRejitData); + } + + SOSDacLeave(); + return hr; +} + +HRESULT ClrDataAccess::GetMethodDescDataImpl( + CLRDATA_ADDRESS methodDesc, + CLRDATA_ADDRESS ip, + struct DacpMethodDescData *methodDescData, + ULONG cRevertedRejitVersions, + DacpReJitData * rgRevertedRejitData, + ULONG * pcNeededRevertedRejitData) +{ + + HRESULT hr = S_OK; PTR_MethodDesc pMD = PTR_MethodDesc(TO_TADDR(methodDesc)); @@ -1236,7 +1300,6 @@ HRESULT ClrDataAccess::GetMethodDescData( } } - SOSDacLeave(); return hr; } From 38d0a511011e4d163a2433dfde6cf1ea2644a97f Mon Sep 17 00:00:00 2001 From: Aleksey Kliger Date: Mon, 8 Jul 2024 16:40:52 -0400 Subject: [PATCH 15/38] WIP: managed GetMethodDescData skeleton --- .../cdacreader/src/Legacy/ISOSDacInterface.cs | 51 ++++++++++++++++++- .../cdacreader/src/Legacy/SOSDacImpl.cs | 13 ++++- 2 files changed, 62 insertions(+), 2 deletions(-) diff --git a/src/native/managed/cdacreader/src/Legacy/ISOSDacInterface.cs b/src/native/managed/cdacreader/src/Legacy/ISOSDacInterface.cs index ef4952f64f61cf..248b3cd5e6eb11 100644 --- a/src/native/managed/cdacreader/src/Legacy/ISOSDacInterface.cs +++ b/src/native/managed/cdacreader/src/Legacy/ISOSDacInterface.cs @@ -93,6 +93,55 @@ internal struct DacpMethodTableData } #pragma warning restore CS0649 // Field is never assigned to, and will always have its default value +internal struct DacpReJitData +{ + // FIXME[cdac]: the C++ definition enum doesn't have an explicit underlying type or constant values for the flags + public enum Flags : uint + { + kUnknown = 0, + kRequested = 1, + kActive = 2, + kReverted = 3, + }; + + public ulong /*CLRDATA_ADDRESS*/ rejitID; + public Flags flags; /* = Flags::kUnknown*/ + public ulong /*CLRDATA_ADDRESS*/ NativeCodeAddr; +}; + +internal struct DacpMethodDescData +{ + public int bHasNativeCode; + public int bIsDynamic; + public ushort wSlotNumber; + public ulong /*CLRDATA_ADDRESS*/ NativeCodeAddr; + // Useful for breaking when a method is jitted. + public ulong /*CLRDATA_ADDRESS*/ AddressOfNativeCodeSlot; + + public ulong /*CLRDATA_ADDRESS*/ MethodDescPtr; + public ulong /*CLRDATA_ADDRESS*/ MethodTablePtr; + public ulong /*CLRDATA_ADDRESS*/ ModulePtr; + + public uint /*mdToken*/ MDToken; + public ulong /*CLRDATA_ADDRESS*/ GCInfo; + public ulong /*CLRDATA_ADDRESS*/ GCStressCodeCopy; + + // This is only valid if bIsDynamic is true + public ulong /*CLRDATA_ADDRESS*/ managedDynamicMethodObject; + + public ulong /*CLRDATA_ADDRESS*/ requestedIP; + + // Gives info for the single currently active version of a method + public DacpReJitData rejitDataCurrent; + + // Gives info corresponding to requestedIP (for !ip2md) + public DacpReJitData rejitDataRequested; + + // Total number of rejit versions that have been jitted + public uint /*ULONG*/ cJittedRejitVersions; + +} + [GeneratedComInterface] [Guid("436f00f2-b42a-4b9f-870c-e73db66ae930")] internal unsafe partial interface ISOSDacInterface @@ -146,7 +195,7 @@ internal unsafe partial interface ISOSDacInterface // MethodDescs [PreserveSig] - int GetMethodDescData(ulong methodDesc, ulong ip, /*struct DacpMethodDescData*/ void* data, uint cRevertedRejitVersions, /*struct DacpReJitData*/ void* rgRevertedRejitData, uint* pcNeededRevertedRejitData); + int GetMethodDescData(ulong methodDesc, ulong ip, DacpMethodDescData* data, uint cRevertedRejitVersions, DacpReJitData* rgRevertedRejitData, uint* pcNeededRevertedRejitData); [PreserveSig] int GetMethodDescPtrFromIP(ulong ip, ulong* ppMD); [PreserveSig] diff --git a/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs b/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs index bcc47d1971954b..e093c512ccf5be 100644 --- a/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs +++ b/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs @@ -79,7 +79,18 @@ public int GetBreakingChangeVersion() public unsafe int GetJitHelperFunctionName(ulong ip, uint count, byte* name, uint* pNeeded) => HResults.E_NOTIMPL; public unsafe int GetJitManagerList(uint count, void* managers, uint* pNeeded) => HResults.E_NOTIMPL; public unsafe int GetJumpThunkTarget(void* ctx, ulong* targetIP, ulong* targetMD) => HResults.E_NOTIMPL; - public unsafe int GetMethodDescData(ulong methodDesc, ulong ip, void* data, uint cRevertedRejitVersions, void* rgRevertedRejitData, uint* pcNeededRevertedRejitData) => HResults.E_NOTIMPL; + public unsafe int GetMethodDescData(ulong methodDesc, ulong ip, DacpMethodDescData* data, uint cRevertedRejitVersions, DacpReJitData* rgRevertedRejitData, uint* pcNeededRevertedRejitData) + { + try + { + return HResults.E_NOTIMPL; + } + catch (Exception ex) + { + return ex.HResult; + } + } + public unsafe int GetMethodDescFromToken(ulong moduleAddr, uint token, ulong* methodDesc) => HResults.E_NOTIMPL; public unsafe int GetMethodDescName(ulong methodDesc, uint count, char* name, uint* pNeeded) => HResults.E_NOTIMPL; public unsafe int GetMethodDescPtrFromFrame(ulong frameAddr, ulong* ppMD) => HResults.E_NOTIMPL; From 9d88f19fc98ed41d10ae3eba41f6cb1425710c66 Mon Sep 17 00:00:00 2001 From: Aleksey Kliger Date: Tue, 9 Jul 2024 17:00:31 -0400 Subject: [PATCH 16/38] wip: MethodDesc --- .../debug/runtimeinfo/datadescriptor.h | 10 ++++ src/coreclr/vm/method.hpp | 7 +++ .../src/Contracts/RuntimeTypeSystem.cs | 14 +++++ .../RuntimeTypeSystem_1.NonValidated.cs | 52 +++++++++++++++++++ .../src/Contracts/RuntimeTypeSystem_1.cs | 40 ++++++++++++++ .../managed/cdacreader/src/Data/MethodDesc.cs | 19 +++++++ src/native/managed/cdacreader/src/DataType.cs | 1 + .../cdacreader/src/Legacy/SOSDacImpl.cs | 17 ++++++ 8 files changed, 160 insertions(+) create mode 100644 src/native/managed/cdacreader/src/Data/MethodDesc.cs diff --git a/src/coreclr/debug/runtimeinfo/datadescriptor.h b/src/coreclr/debug/runtimeinfo/datadescriptor.h index 6bc19b44bddefb..91e25e6bade526 100644 --- a/src/coreclr/debug/runtimeinfo/datadescriptor.h +++ b/src/coreclr/debug/runtimeinfo/datadescriptor.h @@ -246,6 +246,15 @@ CDAC_TYPE_FIELD(FnPtrTypeDesc, /*uint32*/, CallConv, cdac_offsets CDAC_TYPE_FIELD(FnPtrTypeDesc, /*uint32*/, RetAndArgTypes, cdac_offsets::RetAndArgTypes) CDAC_TYPE_END(FnPtrTypeDesc) +CDAC_TYPE_BEGIN(MethodDesc) +CDAC_TYPE_INDETERMINATE(EEClass) +CDAC_TYPE_FIELD(MethodDesc, /*uint8*/, ChunkIndex, cdac_offsets::ChunkIndex) +CDAC_TYPE_END(MethodDesc) + +CDAC_TYPE_BEGIN(MethodDescChunk) +CDAC_TYPE_SIZE(sizeof(MethodDescChunk)) +CDAC_TYPE_END(MethodDescChunk) + CDAC_TYPES_END() CDAC_GLOBALS_BEGIN() @@ -259,6 +268,7 @@ CDAC_GLOBAL(FeatureEHFunclets, uint8, 1) CDAC_GLOBAL(FeatureEHFunclets, uint8, 0) #endif CDAC_GLOBAL(SOSBreakingChangeVersion, uint8, SOS_BREAKING_CHANGE_VERSION) +CDAC_GLOBAL(MethodDescAlignment, uint64, MethodDesc::ALIGNMENT) CDAC_GLOBAL_POINTER(FreeObjectMethodTable, &::g_pFreeObjectMethodTable) CDAC_GLOBALS_END() diff --git a/src/coreclr/vm/method.hpp b/src/coreclr/vm/method.hpp index 168ccaef52d62d..734674cb903620 100644 --- a/src/coreclr/vm/method.hpp +++ b/src/coreclr/vm/method.hpp @@ -1904,6 +1904,13 @@ class MethodDesc public: static void Init(); #endif + + template friend struct ::cdac_offsets; +}; + +template<> struct cdac_offsets +{ + static const size_t ChunkIndex = offsetof(MethodDesc, m_chunkIndex); }; #ifndef DACCESS_COMPILE diff --git a/src/native/managed/cdacreader/src/Contracts/RuntimeTypeSystem.cs b/src/native/managed/cdacreader/src/Contracts/RuntimeTypeSystem.cs index b7267b5f8c23cb..73514e7e8df11a 100644 --- a/src/native/managed/cdacreader/src/Contracts/RuntimeTypeSystem.cs +++ b/src/native/managed/cdacreader/src/Contracts/RuntimeTypeSystem.cs @@ -55,6 +55,16 @@ internal enum CorElementType Sentinel = 0x41, } +internal readonly struct MethodDescHandle +{ + internal MethodDescHandle(TargetPointer address) + { + Address = address; + } + + internal TargetPointer Address { get; } +} + internal interface IRuntimeTypeSystem : IContract { static string IContract.Name => nameof(RuntimeTypeSystem); @@ -113,6 +123,10 @@ static IContract IContract.Create(Target target, int version) public virtual bool IsFunctionPointer(TypeHandle typeHandle, out ReadOnlySpan retAndArgTypes, out byte callConv) => throw new NotImplementedException(); // Returns null if the TypeHandle is not a class/struct/generic variable #endregion TypeHandle inspection APIs + + #region MethodDesc inspection APIs + public virtual MethodDescHandle GetMethodDescHandle(TargetPointer targetPointer) => throw new NotImplementedException(); + #endregion MethodDesc inspection APIs } internal struct RuntimeTypeSystem : IRuntimeTypeSystem diff --git a/src/native/managed/cdacreader/src/Contracts/RuntimeTypeSystem_1.NonValidated.cs b/src/native/managed/cdacreader/src/Contracts/RuntimeTypeSystem_1.NonValidated.cs index ef392130e62316..3f9a0ea6fef58b 100644 --- a/src/native/managed/cdacreader/src/Contracts/RuntimeTypeSystem_1.NonValidated.cs +++ b/src/native/managed/cdacreader/src/Contracts/RuntimeTypeSystem_1.NonValidated.cs @@ -85,6 +85,26 @@ internal EEClass(Target target, TargetPointer eeClassPointer) internal TargetPointer MethodTable => _target.ReadPointer(Address + (ulong)_type.Fields[nameof(MethodTable)].Offset); } + + internal struct MethodDesc + { + public readonly Target _target; + public readonly Target.TypeInfo _type; + internal TargetPointer Address { get; init; } + + internal MethodDesc(Target target, TargetPointer methodDescPointer) + { + _target = target; + _type = target.GetTypeInfo(DataType.pointer /*DataType.MethodDesc*/); // TODO + Address = methodDescPointer; + } + +#pragma warning disable CA1822 // Mark members as static + internal TargetPointer MethodTable => TargetPointer.Null; // TODO + internal ushort Slot => (ushort)0xffffu; // TODO + internal bool HasNonVtableSlot => false; // TODO +#pragma warning restore CA1822 // Mark members as static + } internal static MethodTable GetMethodTableData(Target target, TargetPointer methodTablePointer) { return new MethodTable(target, methodTablePointer); @@ -95,6 +115,10 @@ internal static EEClass GetEEClassData(Target target, TargetPointer eeClassPoint return new EEClass(target, eeClassPointer); } + internal static MethodDesc GetMethodDescData(Target target, TargetPointer methodDescPointer) + { + return new MethodDesc(target, methodDescPointer); + } } /// @@ -194,5 +218,33 @@ private TargetPointer GetClassThrowing(NonValidated.MethodTable methodTable) } } + private bool ValidateMethodDescPointer(NonValidated.MethodDesc umd) + { + try + { + TargetPointer methodTablePointer = umd.MethodTable; + if (methodTablePointer == TargetPointer.Null + || methodTablePointer == new TargetPointer(0xffffffff_fffffffful) + || methodTablePointer == new TargetPointer(0x00000000_fffffffful)) + { + return false; + } + MethodTableHandle methodTableHandle = GetMethodTableHandle(methodTablePointer); + + if (umd.Slot >= GetNumMethods(methodTableHandle) && !umd.HasNonVtableSlot) // FIXME: request.cpp looks at m_usNumVtableSlots + { + return false; + } + } + catch (System.Exception) + { + // TODO(cdac): maybe don't swallow all exceptions? We could consider a richer contract that + // helps to track down what sort of memory corruption caused the validation to fail. + // TODO(cdac): we could also consider a more fine-grained exception type so we don't mask + // programmer mistakes in cdacreader. + return false; + } + return true; + } } diff --git a/src/native/managed/cdacreader/src/Contracts/RuntimeTypeSystem_1.cs b/src/native/managed/cdacreader/src/Contracts/RuntimeTypeSystem_1.cs index 9c3c3bc3f4974d..9496b8ca50dfe9 100644 --- a/src/native/managed/cdacreader/src/Contracts/RuntimeTypeSystem_1.cs +++ b/src/native/managed/cdacreader/src/Contracts/RuntimeTypeSystem_1.cs @@ -19,6 +19,7 @@ internal partial struct RuntimeTypeSystem_1 : IRuntimeTypeSystem // TODO(cdac): we mutate this dictionary - copies of the RuntimeTypeSystem_1 struct share this instance. // If we need to invalidate our view of memory, we should clear this dictionary. private readonly Dictionary _methodTables = new(); + private readonly Dictionary _methodDescs = new(); internal struct MethodTable @@ -67,6 +68,11 @@ internal enum TypeHandleBits ValidMask = 2, } + internal struct MethodDesc + { + + } + internal RuntimeTypeSystem_1(Target target, TargetPointer freeObjectMethodTablePointer) { _target = target; @@ -417,4 +423,38 @@ private FunctionPointerRetAndArgs(Target target, TargetPointer typePointer) } } } + + public MethodDescHandle GetMethodDescHandle(TargetPointer methodDescPointer) + { + // if we already validated this address, return a handle + if (_methodDescs.ContainsKey(methodDescPointer)) + { + return new MethodDescHandle(methodDescPointer); + } +#if false // TODO + // 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); + } +#endif + + // Otherwse, get ready to validate + NonValidated.MethodDesc nonvalidatedMethodDesc = NonValidated.GetMethodDescData(_target, methodDescPointer); + + if (!ValidateMethodDescPointer(nonvalidatedMethodDesc)) + { + throw new InvalidOperationException("Invalid method desc pointer"); + } + // ok, we validated it, cache the data and add the MethodTable_1 struct to the dictionary +#if false // TODO + Data.MethodDesc trustedMethodDescData = _target.ProcessedData.GetOrAdd(methodDescPointer); +#endif + MethodDesc trustedMethodDescF = default; // new MethodDesc(trustedMethodTableData); // TODO + _ = _methodDescs.TryAdd(methodDescPointer, trustedMethodDescF); + return new MethodDescHandle(methodDescPointer); + } } diff --git a/src/native/managed/cdacreader/src/Data/MethodDesc.cs b/src/native/managed/cdacreader/src/Data/MethodDesc.cs new file mode 100644 index 00000000000000..eff33f246a9981 --- /dev/null +++ b/src/native/managed/cdacreader/src/Data/MethodDesc.cs @@ -0,0 +1,19 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; + +namespace Microsoft.Diagnostics.DataContractReader.Data; + +internal sealed class MethodDesc : IData +{ + static MethodDesc IData.Create(Target target, TargetPointer address) => new MethodDesc(target, address); +#pragma warning disable IDE0060 // Remove unused parameter + public MethodDesc(Target target, TargetPointer address) + { + + } +#pragma warning restore IDE0060 + + public byte ChunkIndex { get; init; } +} diff --git a/src/native/managed/cdacreader/src/DataType.cs b/src/native/managed/cdacreader/src/DataType.cs index 9f64fef1e28dff..0c482a9b6f024b 100644 --- a/src/native/managed/cdacreader/src/DataType.cs +++ b/src/native/managed/cdacreader/src/DataType.cs @@ -36,4 +36,5 @@ public enum DataType ParamTypeDesc, TypeVarTypeDesc, FnPtrTypeDesc, + MethodDesc, } diff --git a/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs b/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs index e093c512ccf5be..8db1d55d94631f 100644 --- a/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs +++ b/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs @@ -81,8 +81,25 @@ public int GetBreakingChangeVersion() public unsafe int GetJumpThunkTarget(void* ctx, ulong* targetIP, ulong* targetMD) => HResults.E_NOTIMPL; public unsafe int GetMethodDescData(ulong methodDesc, ulong ip, DacpMethodDescData* data, uint cRevertedRejitVersions, DacpReJitData* rgRevertedRejitData, uint* pcNeededRevertedRejitData) { + if (methodDesc == 0) + { + return HResults.E_INVALIDARG; + } + if (cRevertedRejitVersions != 0 && rgRevertedRejitData == null) + { + return HResults.E_INVALIDARG; + } + if (rgRevertedRejitData != null && pcNeededRevertedRejitData == null) + { + // If you're asking for reverted rejit data, you'd better ask for the number of + // elements we return + return HResults.E_INVALIDARG; + } try { + Contracts.IRuntimeTypeSystem rtsContract = _target.Contracts.RuntimeTypeSystem; + Contracts.MethodDescHandle methodDescHandle = rtsContract.GetMethodDescHandle(methodDesc); + return HResults.E_NOTIMPL; } catch (Exception ex) From 4a4c3a415528c2bed6bf7a529856279c97304923 Mon Sep 17 00:00:00 2001 From: Aleksey Kliger Date: Wed, 10 Jul 2024 10:37:42 -0400 Subject: [PATCH 17/38] add MethodDescChunk --- .../cdacreader/src/Data/MethodDescChunk.cs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 src/native/managed/cdacreader/src/Data/MethodDescChunk.cs diff --git a/src/native/managed/cdacreader/src/Data/MethodDescChunk.cs b/src/native/managed/cdacreader/src/Data/MethodDescChunk.cs new file mode 100644 index 00000000000000..617693efcb1995 --- /dev/null +++ b/src/native/managed/cdacreader/src/Data/MethodDescChunk.cs @@ -0,0 +1,18 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; + +namespace Microsoft.Diagnostics.DataContractReader.Data; + +internal sealed class MethodDescChunk : IData +{ + static MethodDescChunk IData.Create(Target target, TargetPointer address) => new MethodDescChunk(target, address); +#pragma warning disable IDE0060 // Remove unused parameter + public MethodDescChunk(Target target, TargetPointer address) + { + + } +#pragma warning restore IDE0060 + +} From 510a10f7515fb068a9fd5c4fde41616f1f55b7d8 Mon Sep 17 00:00:00 2001 From: Aleksey Kliger Date: Thu, 11 Jul 2024 15:52:16 -0400 Subject: [PATCH 18/38] WIP: validating a MethodDesc --- .../design/datacontracts/RuntimeTypeSystem.md | 16 +++- .../debug/runtimeinfo/datadescriptor.h | 9 +- src/coreclr/vm/class.h | 1 + src/coreclr/vm/method.hpp | 13 +++ src/coreclr/vm/methodtable.h | 2 +- .../managed/cdacreader/src/Constants.cs | 2 + .../src/Contracts/RuntimeTypeSystem.cs | 3 +- .../RuntimeTypeSystem_1.NonValidated.cs | 87 +++++++++++++++---- .../src/Contracts/RuntimeTypeSystem_1.cs | 28 ++++-- .../managed/cdacreader/src/Data/EEClass.cs | 3 + .../managed/cdacreader/src/Data/MethodDesc.cs | 8 +- .../cdacreader/src/Data/MethodDescChunk.cs | 11 ++- src/native/managed/cdacreader/src/DataType.cs | 1 + 13 files changed, 155 insertions(+), 29 deletions(-) diff --git a/docs/design/datacontracts/RuntimeTypeSystem.md b/docs/design/datacontracts/RuntimeTypeSystem.md index 5399e83f5b4a37..3355bbd36cde3d 100644 --- a/docs/design/datacontracts/RuntimeTypeSystem.md +++ b/docs/design/datacontracts/RuntimeTypeSystem.md @@ -4,10 +4,11 @@ This contract is for exploring the properties of the runtime types of values on ## APIs of contract +### TypeHandle + 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 TypeHandle { @@ -80,8 +81,14 @@ struct TypeHandle #endregion TypeHandle inspection APIs ``` +### MethodDesc + +A `MethodDesc` is the runtime representationn of a managed method (either from IL, from reflection emit, or generated by the runtime). + ## Version 1 +### TypeHandle + The `MethodTable` inspection APIs are implemented in terms of the following flags on the runtime `MethodTable` structure: ``` csharp @@ -528,3 +535,10 @@ The contract additionally depends on these data descriptors return true; } ``` + +### MethodDesc + +The version 1 `MethodDesc` APIs depend on the `MethodDescAlignment` global and the `MethodDesc` and `MethodDescChunk` data descriptors. + +In the runtime a `MethodDesc` implicitly belongs to a single `MethodDescChunk` and some common data is shared between method descriptors that belong to the same chunk. A single method table +will typically have multiple chunks. There are subkinds of MethodDescs at runtime of varying sizes (but the sizes must be mutliples of `MethodDescAlignment`) and each chunk contains method descriptors of the same size. diff --git a/src/coreclr/debug/runtimeinfo/datadescriptor.h b/src/coreclr/debug/runtimeinfo/datadescriptor.h index 91e25e6bade526..5e5e492bcfaf61 100644 --- a/src/coreclr/debug/runtimeinfo/datadescriptor.h +++ b/src/coreclr/debug/runtimeinfo/datadescriptor.h @@ -211,6 +211,7 @@ CDAC_TYPE_FIELD(EEClass, /*pointer*/, MethodTable, cdac_offsets::Method 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_FIELD(EEClass, /*uint16*/, NumNonVirtualSlots, cdac_offsets::NumNonVirtualSlots) CDAC_TYPE_END(EEClass) CDAC_TYPE_BEGIN(ArrayClass) @@ -247,12 +248,18 @@ CDAC_TYPE_FIELD(FnPtrTypeDesc, /*uint32*/, RetAndArgTypes, cdac_offsets::ChunkIndex) +CDAC_TYPE_FIELD(MethodDesc, /*uint8*/, Slot, cdac_offsets::Slot) +CDAC_TYPE_FIELD(MethodDesc, /*uint16*/, Flags, cdac_offsets::Flags) CDAC_TYPE_END(MethodDesc) CDAC_TYPE_BEGIN(MethodDescChunk) CDAC_TYPE_SIZE(sizeof(MethodDescChunk)) +CDAC_TYPE_FIELD(MethodDescChunk, /*pointer*/, MethodTable, cdac_offsets::MethodTable) +CDAC_TYPE_FIELD(MethodDescChunk, /*pointer*/, Next, cdac_offsets::Next) +CDAC_TYPE_FIELD(MethodDescChunk, /*uint8*/, Size, cdac_offsets::Size) +CDAC_TYPE_FIELD(MethodDescChunk, /*uint8*/, Count, cdac_offsets::Count) CDAC_TYPE_END(MethodDescChunk) CDAC_TYPES_END() diff --git a/src/coreclr/vm/class.h b/src/coreclr/vm/class.h index e281b8e365e7ed..260abc08fa6fa9 100644 --- a/src/coreclr/vm/class.h +++ b/src/coreclr/vm/class.h @@ -1807,6 +1807,7 @@ template<> struct cdac_offsets 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); + static constexpr size_t NumNonVirtualSlots = offsetof(EEClass, m_NumNonVirtualSlots); }; // -------------------------------------------------------------------------------------------- diff --git a/src/coreclr/vm/method.hpp b/src/coreclr/vm/method.hpp index 734674cb903620..bb43b0f8c5e539 100644 --- a/src/coreclr/vm/method.hpp +++ b/src/coreclr/vm/method.hpp @@ -1911,6 +1911,8 @@ class MethodDesc template<> struct cdac_offsets { static const size_t ChunkIndex = offsetof(MethodDesc, m_chunkIndex); + static const size_t Slot = offsetof(MethodDesc, m_wSlotNumber); + static const size_t Flags = offsetof(MethodDesc, m_wFlags); }; #ifndef DACCESS_COMPILE @@ -2331,6 +2333,17 @@ class MethodDescChunk UINT16 m_flagsAndTokenRange; // Followed by array of method descs... + + template friend struct ::cdac_offsets; +}; + +template<> +struct cdac_offsets +{ + static constexpr size_t MethodTable = offsetof(MethodDescChunk, m_methodTable); + static constexpr size_t Next = offsetof(MethodDescChunk, m_next); + static constexpr size_t Size = offsetof(MethodDescChunk, m_size); + static constexpr size_t Count = offsetof(MethodDescChunk, m_count); }; inline int MethodDesc::GetMethodDescChunkIndex() const diff --git a/src/coreclr/vm/methodtable.h b/src/coreclr/vm/methodtable.h index 47bdd53cfe27f6..0ad2fd3988ac16 100644 --- a/src/coreclr/vm/methodtable.h +++ b/src/coreclr/vm/methodtable.h @@ -621,7 +621,7 @@ struct DynamicStaticsInfo // If it has, then we don't need to do anything return false; } - + if (isClassInitedByUpdatingStaticPointer) { oldValFromInterlockedOp = InterlockedCompareExchangeT(pAddr, newVal, oldVal); diff --git a/src/native/managed/cdacreader/src/Constants.cs b/src/native/managed/cdacreader/src/Constants.cs index 5c95db68d227f9..078c44a0b44a5c 100644 --- a/src/native/managed/cdacreader/src/Constants.cs +++ b/src/native/managed/cdacreader/src/Constants.cs @@ -17,5 +17,7 @@ internal static class Globals internal const string SOSBreakingChangeVersion = nameof(SOSBreakingChangeVersion); internal const string FreeObjectMethodTable = nameof(FreeObjectMethodTable); + + internal const string MethodDescAlignment = nameof(MethodDescAlignment); } } diff --git a/src/native/managed/cdacreader/src/Contracts/RuntimeTypeSystem.cs b/src/native/managed/cdacreader/src/Contracts/RuntimeTypeSystem.cs index 73514e7e8df11a..d5b7c48f8dc199 100644 --- a/src/native/managed/cdacreader/src/Contracts/RuntimeTypeSystem.cs +++ b/src/native/managed/cdacreader/src/Contracts/RuntimeTypeSystem.cs @@ -72,9 +72,10 @@ static IContract IContract.Create(Target target, int version) { TargetPointer targetPointer = target.ReadGlobalPointer(Constants.Globals.FreeObjectMethodTable); TargetPointer freeObjectMethodTable = target.ReadPointer(targetPointer); + ulong methodDescAlignment = target.ReadGlobal(Constants.Globals.MethodDescAlignment); return version switch { - 1 => new RuntimeTypeSystem_1(target, freeObjectMethodTable), + 1 => new RuntimeTypeSystem_1(target, freeObjectMethodTable, methodDescAlignment), _ => default(RuntimeTypeSystem), }; } diff --git a/src/native/managed/cdacreader/src/Contracts/RuntimeTypeSystem_1.NonValidated.cs b/src/native/managed/cdacreader/src/Contracts/RuntimeTypeSystem_1.NonValidated.cs index 3f9a0ea6fef58b..997a0b5a71ef45 100644 --- a/src/native/managed/cdacreader/src/Contracts/RuntimeTypeSystem_1.NonValidated.cs +++ b/src/native/managed/cdacreader/src/Contracts/RuntimeTypeSystem_1.NonValidated.cs @@ -89,22 +89,25 @@ internal EEClass(Target target, TargetPointer eeClassPointer) internal struct MethodDesc { public readonly Target _target; - public readonly Target.TypeInfo _type; - internal TargetPointer Address { get; init; } - - internal MethodDesc(Target target, TargetPointer methodDescPointer) + public TargetPointer Address { get; init; } + public readonly Data.MethodDesc _desc; + public readonly Data.MethodDescChunk _chunk; + internal MethodDesc(Target target, TargetPointer methodDescPointer, Data.MethodDesc desc, Data.MethodDescChunk chunk) { _target = target; - _type = target.GetTypeInfo(DataType.pointer /*DataType.MethodDesc*/); // TODO + _desc = desc; + _chunk = chunk; Address = methodDescPointer; } -#pragma warning disable CA1822 // Mark members as static - internal TargetPointer MethodTable => TargetPointer.Null; // TODO - internal ushort Slot => (ushort)0xffffu; // TODO - internal bool HasNonVtableSlot => false; // TODO -#pragma warning restore CA1822 // Mark members as static + private bool HasFlag(MethodDescFlags flag) => (_desc.Flags & (ushort)flag) != 0; + + internal byte ChunkIndex => _desc.ChunkIndex; + internal TargetPointer MethodTable => _chunk.MethodTable; + internal ushort Slot => _desc.Slot; + internal bool HasNonVtableSlot => HasFlag(MethodDescFlags.HasNonVtableSlot); } + internal static MethodTable GetMethodTableData(Target target, TargetPointer methodTablePointer) { return new MethodTable(target, methodTablePointer); @@ -115,10 +118,6 @@ internal static EEClass GetEEClassData(Target target, TargetPointer eeClassPoint return new EEClass(target, eeClassPointer); } - internal static MethodDesc GetMethodDescData(Target target, TargetPointer methodDescPointer) - { - return new MethodDesc(target, methodDescPointer); - } } /// @@ -218,10 +217,36 @@ private TargetPointer GetClassThrowing(NonValidated.MethodTable methodTable) } } - private bool ValidateMethodDescPointer(NonValidated.MethodDesc umd) + private TargetPointer GetMethodDescChunkPointerMayThrow(TargetPointer methodDescPointer, Data.MethodDesc umd) + { + ulong? methodDescSize = _target.GetTypeInfo(DataType.MethodDesc).Size; + if (!methodDescSize.HasValue) + { + throw new InvalidOperationException("Target has no definite MethodDesc size"); + } + ulong chunkAddress = (ulong)methodDescPointer - methodDescSize.Value - umd.ChunkIndex * MethodDescAlignment; + return new TargetPointer(chunkAddress); + } + + private Data.MethodDescChunk GetMethodDescChunkMayThrow(TargetPointer methodDescPointer, Data.MethodDesc md) + { + return new Data.MethodDescChunk(_target, GetMethodDescChunkPointerMayThrow(methodDescPointer, md)); + } + + private NonValidated.MethodDesc GetMethodDescMayThrow(TargetPointer methodDescPointer) + { + // may throw if the method desc at methodDescPointer is corrupted + // we bypass the target data cache here because we don't want to cache non-validated data + Data.MethodDesc desc = new Data.MethodDesc(_target, methodDescPointer); + Data.MethodDescChunk chunk = GetMethodDescChunkMayThrow(methodDescPointer, desc); + return new NonValidated.MethodDesc(_target, methodDescPointer, desc, chunk); + } + + private bool ValidateMethodDescPointer(TargetPointer methodDescPointer) { try { + NonValidated.MethodDesc umd = GetMethodDescMayThrow(methodDescPointer); TargetPointer methodTablePointer = umd.MethodTable; if (methodTablePointer == TargetPointer.Null || methodTablePointer == new TargetPointer(0xffffffff_fffffffful) @@ -231,10 +256,40 @@ private bool ValidateMethodDescPointer(NonValidated.MethodDesc umd) } MethodTableHandle methodTableHandle = GetMethodTableHandle(methodTablePointer); - if (umd.Slot >= GetNumMethods(methodTableHandle) && !umd.HasNonVtableSlot) // FIXME: request.cpp looks at m_usNumVtableSlots + if (umd.Slot >= GetNumVtableSlots(methodTableHandle) && !umd.HasNonVtableSlot) { return false; } + // TODO: request.cpp +#if false + MethodDesc *pMDCheck = MethodDesc::GetMethodDescFromStubAddr(pMD->GetTemporaryEntryPoint(), TRUE); + + if (PTR_HOST_TO_TADDR(pMD) != PTR_HOST_TO_TADDR(pMDCheck)) + { + retval = FALSE; + } + } + + if (retval && pMD->HasNativeCode() && !pMD->IsFCall()) + { + PCODE jitCodeAddr = pMD->GetNativeCode(); + + MethodDesc *pMDCheck = ExecutionManager::GetCodeMethodDesc(jitCodeAddr); + if (pMDCheck) + { + // Check that the given MethodDesc matches the MethodDesc from + // the CodeHeader + if (PTR_HOST_TO_TADDR(pMD) != PTR_HOST_TO_TADDR(pMDCheck)) + { + retval = FALSE; + } + } + else + { + retval = FALSE; + } + } +#endif } catch (System.Exception) diff --git a/src/native/managed/cdacreader/src/Contracts/RuntimeTypeSystem_1.cs b/src/native/managed/cdacreader/src/Contracts/RuntimeTypeSystem_1.cs index 9496b8ca50dfe9..0367e972fbec13 100644 --- a/src/native/managed/cdacreader/src/Contracts/RuntimeTypeSystem_1.cs +++ b/src/native/managed/cdacreader/src/Contracts/RuntimeTypeSystem_1.cs @@ -15,6 +15,7 @@ internal partial struct RuntimeTypeSystem_1 : IRuntimeTypeSystem { private readonly Target _target; private readonly TargetPointer _freeObjectMethodTablePointer; + private readonly ulong _methodDescAlignment; // TODO(cdac): we mutate this dictionary - copies of the RuntimeTypeSystem_1 struct share this instance. // If we need to invalidate our view of memory, we should clear this dictionary. @@ -46,6 +47,9 @@ internal MethodTable(Data.MethodTable data) ParentMethodTable = data.ParentMethodTable; PerInstInfo = data.PerInstInfo; } + + // this MethodTable is a canonical MethodTable if it's EEClassOrCanonMT is an EEClass + internal bool IsCanonMT => GetEEClassOrCanonMTBits(EEClassOrCanonMT) == EEClassOrCanonMTBits.EEClass; } // Low order bit of EEClassOrCanonMT. @@ -68,19 +72,28 @@ internal enum TypeHandleBits ValidMask = 2, } + [Flags] + internal enum MethodDescFlags : ushort + { + HasNonVtableSlot = 0x0008, + } + internal struct MethodDesc { } - internal RuntimeTypeSystem_1(Target target, TargetPointer freeObjectMethodTablePointer) + internal RuntimeTypeSystem_1(Target target, TargetPointer freeObjectMethodTablePointer, ulong methodDescAlignment) { _target = target; _freeObjectMethodTablePointer = freeObjectMethodTablePointer; + _methodDescAlignment = methodDescAlignment; } internal TargetPointer FreeObjectMethodTablePointer => _freeObjectMethodTablePointer; + internal ulong MethodDescAlignment => _methodDescAlignment; + public TypeHandle GetTypeHandle(TargetPointer typeHandlePointer) { TypeHandleBits addressLowBits = (TypeHandleBits)((ulong)typeHandlePointer & ((ulong)_target.PointerSize - 1)); @@ -422,6 +435,14 @@ private FunctionPointerRetAndArgs(Target target, TargetPointer typePointer) TypeHandles[i] = rts.GetTypeHandle(target.ReadPointer(retAndArgs + (ulong)target.PointerSize * (ulong)i)); } } + + internal ushort GetNumVtableSlots(TypeHandleHandle typeHandle) + { + if (!typeHandle.IsMethodTable()) + return 0; + MethodTable methodTable = _methodTables[typeHandleHandle.Address]; + ushort numNonVirtualSlots = methodTable.IsCanonMT ? GetClassData(typeHandle).NumNonVirtualSlots : (ushort)0; + return checked((ushort)(methodTable.NumVirtuals + numNonVirtualSlots)); } public MethodDescHandle GetMethodDescHandle(TargetPointer methodDescPointer) @@ -442,10 +463,7 @@ public MethodDescHandle GetMethodDescHandle(TargetPointer methodDescPointer) } #endif - // Otherwse, get ready to validate - NonValidated.MethodDesc nonvalidatedMethodDesc = NonValidated.GetMethodDescData(_target, methodDescPointer); - - if (!ValidateMethodDescPointer(nonvalidatedMethodDesc)) + if (!ValidateMethodDescPointer(methodDescPointer)) { throw new InvalidOperationException("Invalid method desc pointer"); } diff --git a/src/native/managed/cdacreader/src/Data/EEClass.cs b/src/native/managed/cdacreader/src/Data/EEClass.cs index 531a30389e41c0..1cdbea9afcc742 100644 --- a/src/native/managed/cdacreader/src/Data/EEClass.cs +++ b/src/native/managed/cdacreader/src/Data/EEClass.cs @@ -16,6 +16,7 @@ public EEClass(Target target, TargetPointer address) 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); + NumNonVirtualSlots = target.Read(address + (ulong)type.Fields[nameof(NumNonVirtualSlots)].Offset); } public TargetPointer MethodTable { get; init; } @@ -30,6 +31,8 @@ public EEClass(Target target, TargetPointer address) // Enums are the element type of their underlying type // ValueTypes which can exactly be represented as an element type are represented as such public byte InternalCorElementType { get; init; } + + public ushort NumNonVirtualSlots { get; init; } } public sealed class ArrayClass : IData diff --git a/src/native/managed/cdacreader/src/Data/MethodDesc.cs b/src/native/managed/cdacreader/src/Data/MethodDesc.cs index eff33f246a9981..edaa5ccaffd7f2 100644 --- a/src/native/managed/cdacreader/src/Data/MethodDesc.cs +++ b/src/native/managed/cdacreader/src/Data/MethodDesc.cs @@ -8,12 +8,16 @@ namespace Microsoft.Diagnostics.DataContractReader.Data; internal sealed class MethodDesc : IData { static MethodDesc IData.Create(Target target, TargetPointer address) => new MethodDesc(target, address); -#pragma warning disable IDE0060 // Remove unused parameter public MethodDesc(Target target, TargetPointer address) { + Target.TypeInfo type = target.GetTypeInfo(DataType.MethodDesc); + ChunkIndex = target.Read(address + (ulong)type.Fields[nameof(ChunkIndex)].Offset); + Slot = target.Read(address + (ulong)type.Fields[nameof(Slot)].Offset); + Flags = target.Read(address + (ulong)type.Fields[nameof(Flags)].Offset); } -#pragma warning restore IDE0060 public byte ChunkIndex { get; init; } + public byte Slot { get; init; } + public ushort Flags { get; init; } } diff --git a/src/native/managed/cdacreader/src/Data/MethodDescChunk.cs b/src/native/managed/cdacreader/src/Data/MethodDescChunk.cs index 617693efcb1995..3d397a124df6d4 100644 --- a/src/native/managed/cdacreader/src/Data/MethodDescChunk.cs +++ b/src/native/managed/cdacreader/src/Data/MethodDescChunk.cs @@ -8,11 +8,18 @@ namespace Microsoft.Diagnostics.DataContractReader.Data; internal sealed class MethodDescChunk : IData { static MethodDescChunk IData.Create(Target target, TargetPointer address) => new MethodDescChunk(target, address); -#pragma warning disable IDE0060 // Remove unused parameter public MethodDescChunk(Target target, TargetPointer address) { + Target.TypeInfo type = target.GetTypeInfo(DataType.MethodDescChunk); + MethodTable = target.ReadPointer(address + (ulong)type.Fields[nameof(MethodTable)].Offset); + Next = target.ReadPointer(address + (ulong)type.Fields[nameof(Next)].Offset); + Size = target.Read(address + (ulong)type.Fields[nameof(Size)].Offset); + Count = target.Read(address + (ulong)type.Fields[nameof(Count)].Offset); } -#pragma warning restore IDE0060 + public TargetPointer MethodTable { get; init; } + public TargetPointer Next { get; init; } + public byte Size { get; init; } + public byte Count { get; init; } } diff --git a/src/native/managed/cdacreader/src/DataType.cs b/src/native/managed/cdacreader/src/DataType.cs index 0c482a9b6f024b..dac4bb8347f34b 100644 --- a/src/native/managed/cdacreader/src/DataType.cs +++ b/src/native/managed/cdacreader/src/DataType.cs @@ -37,4 +37,5 @@ public enum DataType TypeVarTypeDesc, FnPtrTypeDesc, MethodDesc, + MethodDescChunk, } From 419cb11fb03569f55311327de4e936a288bb3cd7 Mon Sep 17 00:00:00 2001 From: Aleksey Kliger Date: Fri, 12 Jul 2024 12:25:47 -0400 Subject: [PATCH 19/38] checkpoint: MethodDesc validation --- .../src/Contracts/RuntimeTypeSystem.cs | 1 + .../RuntimeTypeSystem_1.NonValidated.cs | 30 +++++++----- .../src/Contracts/RuntimeTypeSystem_1.cs | 48 +++++++++++++------ .../cdacreader/src/Legacy/SOSDacImpl.cs | 2 + 4 files changed, 56 insertions(+), 25 deletions(-) diff --git a/src/native/managed/cdacreader/src/Contracts/RuntimeTypeSystem.cs b/src/native/managed/cdacreader/src/Contracts/RuntimeTypeSystem.cs index d5b7c48f8dc199..8cb8a2c8675c57 100644 --- a/src/native/managed/cdacreader/src/Contracts/RuntimeTypeSystem.cs +++ b/src/native/managed/cdacreader/src/Contracts/RuntimeTypeSystem.cs @@ -127,6 +127,7 @@ static IContract IContract.Create(Target target, int version) #region MethodDesc inspection APIs public virtual MethodDescHandle GetMethodDescHandle(TargetPointer targetPointer) => throw new NotImplementedException(); + public virtual TargetPointer GetMethodTable(MethodDescHandle methodDesc) => throw new NotImplementedException(); #endregion MethodDesc inspection APIs } diff --git a/src/native/managed/cdacreader/src/Contracts/RuntimeTypeSystem_1.NonValidated.cs b/src/native/managed/cdacreader/src/Contracts/RuntimeTypeSystem_1.NonValidated.cs index 997a0b5a71ef45..b37b5d002121ce 100644 --- a/src/native/managed/cdacreader/src/Contracts/RuntimeTypeSystem_1.NonValidated.cs +++ b/src/native/managed/cdacreader/src/Contracts/RuntimeTypeSystem_1.NonValidated.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; +using System.Diagnostics.CodeAnalysis; namespace Microsoft.Diagnostics.DataContractReader.Contracts; @@ -88,10 +89,10 @@ internal EEClass(Target target, TargetPointer eeClassPointer) internal struct MethodDesc { - public readonly Target _target; - public TargetPointer Address { get; init; } - public readonly Data.MethodDesc _desc; - public readonly Data.MethodDescChunk _chunk; + private readonly Target _target; + private TargetPointer Address { get; init; } + private readonly Data.MethodDesc _desc; + private readonly Data.MethodDescChunk _chunk; internal MethodDesc(Target target, TargetPointer methodDescPointer, Data.MethodDesc desc, Data.MethodDescChunk chunk) { _target = target; @@ -228,25 +229,27 @@ private TargetPointer GetMethodDescChunkPointerMayThrow(TargetPointer methodDesc return new TargetPointer(chunkAddress); } - private Data.MethodDescChunk GetMethodDescChunkMayThrow(TargetPointer methodDescPointer, Data.MethodDesc md) + private Data.MethodDescChunk GetMethodDescChunkMayThrow(TargetPointer methodDescPointer, Data.MethodDesc md, out TargetPointer methodDescChunkPointer) { - return new Data.MethodDescChunk(_target, GetMethodDescChunkPointerMayThrow(methodDescPointer, md)); + methodDescChunkPointer = GetMethodDescChunkPointerMayThrow(methodDescPointer, md); + return new Data.MethodDescChunk(_target, methodDescChunkPointer); } - private NonValidated.MethodDesc GetMethodDescMayThrow(TargetPointer methodDescPointer) + private NonValidated.MethodDesc GetMethodDescMayThrow(TargetPointer methodDescPointer, out TargetPointer methodDescChunkPointer) { // may throw if the method desc at methodDescPointer is corrupted // we bypass the target data cache here because we don't want to cache non-validated data Data.MethodDesc desc = new Data.MethodDesc(_target, methodDescPointer); - Data.MethodDescChunk chunk = GetMethodDescChunkMayThrow(methodDescPointer, desc); + Data.MethodDescChunk chunk = GetMethodDescChunkMayThrow(methodDescPointer, desc, out methodDescChunkPointer); return new NonValidated.MethodDesc(_target, methodDescPointer, desc, chunk); } - private bool ValidateMethodDescPointer(TargetPointer methodDescPointer) + private bool ValidateMethodDescPointer(TargetPointer methodDescPointer, [NotNullWhen(true)] out TargetPointer methodDescChunkPointer) { + methodDescChunkPointer = TargetPointer.Null; try { - NonValidated.MethodDesc umd = GetMethodDescMayThrow(methodDescPointer); + NonValidated.MethodDesc umd = GetMethodDescMayThrow(methodDescPointer, out methodDescChunkPointer); TargetPointer methodTablePointer = umd.MethodTable; if (methodTablePointer == TargetPointer.Null || methodTablePointer == new TargetPointer(0xffffffff_fffffffful) @@ -261,6 +264,8 @@ private bool ValidateMethodDescPointer(TargetPointer methodDescPointer) return false; } // TODO: request.cpp + // TODO[cdac]: this needs a Precode lookup + // see MethodDescChunk::GetTemporaryEntryPoint #if false MethodDesc *pMDCheck = MethodDesc::GetMethodDescFromStubAddr(pMD->GetTemporaryEntryPoint(), TRUE); @@ -268,8 +273,11 @@ private bool ValidateMethodDescPointer(TargetPointer methodDescPointer) { retval = FALSE; } - } +#endif + // TODO: request.cpp + // TODO[cdac]: needs MethodDesc::GetNativeCode and MethodDesc::GetMethodEntryPoint() +#if false if (retval && pMD->HasNativeCode() && !pMD->IsFCall()) { PCODE jitCodeAddr = pMD->GetNativeCode(); diff --git a/src/native/managed/cdacreader/src/Contracts/RuntimeTypeSystem_1.cs b/src/native/managed/cdacreader/src/Contracts/RuntimeTypeSystem_1.cs index 0367e972fbec13..afbbe91fdfec68 100644 --- a/src/native/managed/cdacreader/src/Contracts/RuntimeTypeSystem_1.cs +++ b/src/native/managed/cdacreader/src/Contracts/RuntimeTypeSystem_1.cs @@ -80,7 +80,18 @@ internal enum MethodDescFlags : ushort internal struct MethodDesc { + private readonly Data.MethodDesc _desc; + private readonly Data.MethodDescChunk _chunk; + internal TargetPointer Address { get; init; } + internal MethodDesc(TargetPointer methodDescPointer, Data.MethodDesc desc, Data.MethodDescChunk chunk) + { + _desc = desc; + _chunk = chunk; + Address = methodDescPointer; + } + public TargetPointer MethodTable => _chunk.MethodTable; + public ushort Slot => _desc.Slot; } internal RuntimeTypeSystem_1(Target target, TargetPointer freeObjectMethodTablePointer, ulong methodDescAlignment) @@ -452,27 +463,36 @@ public MethodDescHandle GetMethodDescHandle(TargetPointer methodDescPointer) { return new MethodDescHandle(methodDescPointer); } -#if false // TODO // Check if we cached the underlying data already - if (_target.ProcessedData.TryGet(methodTablePointer, out Data.MethodTable? methodTableData)) + if (_target.ProcessedData.TryGet(methodDescPointer, out Data.MethodDesc? methodDescData)) { // 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); + TargetPointer mdescChunkPtr = GetMethodDescChunkPointerMayThrow(methodDescPointer, methodDescData); + // FIXME[cdac]: this isn't threadsafe + if (!_target.ProcessedData.TryGet(mdescChunkPtr, out Data.MethodDescChunk? methodDescChunkData)) + { + throw new InvalidOperationException("cached MethodDesc data but not its containing MethodDescChunk"); + } + MethodDesc validatedMethodDesc = new MethodDesc(methodDescPointer, methodDescData, methodDescChunkData); + _ = _methodDescs.TryAdd(methodDescPointer, validatedMethodDesc); + return new MethodDescHandle(methodDescPointer); } -#endif - if (!ValidateMethodDescPointer(methodDescPointer)) + if (!ValidateMethodDescPointer(methodDescPointer, out TargetPointer methodDescChunkPointer)) { throw new InvalidOperationException("Invalid method desc pointer"); } - // ok, we validated it, cache the data and add the MethodTable_1 struct to the dictionary -#if false // TODO - Data.MethodDesc trustedMethodDescData = _target.ProcessedData.GetOrAdd(methodDescPointer); -#endif - MethodDesc trustedMethodDescF = default; // new MethodDesc(trustedMethodTableData); // TODO - _ = _methodDescs.TryAdd(methodDescPointer, trustedMethodDescF); - return new MethodDescHandle(methodDescPointer); + else + { + // ok, we validated it, cache the data and add the MethodTable_1 struct to the dictionary + Data.MethodDescChunk validatedMethodDescChunkData = _target.ProcessedData.GetOrAdd(methodDescChunkPointer); + Data.MethodDesc validatedMethodDescData = _target.ProcessedData.GetOrAdd(methodDescPointer); + + MethodDesc trustedMethodDescF = new MethodDesc(methodDescPointer, validatedMethodDescData, validatedMethodDescChunkData); + _ = _methodDescs.TryAdd(methodDescPointer, trustedMethodDescF); + return new MethodDescHandle(methodDescPointer); + } } + + public TargetPointer GetMethodTable(MethodDescHandle methodDescHandle) => _methodDescs[methodDescHandle.Address].MethodTable; } diff --git a/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs b/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs index 8db1d55d94631f..c15ed2c879a1ed 100644 --- a/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs +++ b/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs @@ -100,6 +100,8 @@ public unsafe int GetMethodDescData(ulong methodDesc, ulong ip, DacpMethodDescDa Contracts.IRuntimeTypeSystem rtsContract = _target.Contracts.RuntimeTypeSystem; Contracts.MethodDescHandle methodDescHandle = rtsContract.GetMethodDescHandle(methodDesc); + data->MethodTablePtr = rtsContract.GetMethodTable(methodDescHandle); + return HResults.E_NOTIMPL; } catch (Exception ex) From 1321aa9bf3951239d35eeecfb1a88dd028a1cb42 Mon Sep 17 00:00:00 2001 From: Aleksey Kliger Date: Fri, 12 Jul 2024 13:16:19 -0400 Subject: [PATCH 20/38] update contract --- .../design/datacontracts/RuntimeTypeSystem.md | 25 +++++++++++++++++++ .../src/Contracts/RuntimeTypeSystem_1.cs | 2 +- 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/docs/design/datacontracts/RuntimeTypeSystem.md b/docs/design/datacontracts/RuntimeTypeSystem.md index 3355bbd36cde3d..d75a89737730d8 100644 --- a/docs/design/datacontracts/RuntimeTypeSystem.md +++ b/docs/design/datacontracts/RuntimeTypeSystem.md @@ -34,6 +34,8 @@ A `TypeHandle` is the runtime representation of the type information about a val struct TypeHandle ``` csharp +partial interface IRuntimeTypeSystem : IContract +{ #region MethodTable inspection APIs public virtual TypeHandle GetTypeHandle(TargetPointer targetPointer); @@ -79,12 +81,33 @@ struct TypeHandle public virtual bool IsFunctionPointer(TypeHandle typeHandle, out ReadOnlySpan retAndArgTypes, out byte callConv); #endregion TypeHandle inspection APIs +} ``` ### MethodDesc A `MethodDesc` is the runtime representationn of a managed method (either from IL, from reflection emit, or generated by the runtime). +```csharp +struct MethodDescHandle +{ + // no public properties or constructors + + internal TargetPointer Address { get; } +} +``` + +```csharp +partial interface IRuntimeTypeSystem : IContract +{ + #region MethodDesc inspection APIs + public virtual MethodDescHandle GetMethodDescHandle (TargetPointer methodDescPointer); + + public virtual TargetPointer GetMethodTable (MethodDescHandle methodDesc); + #endregion MethodDesc inspection APIs +} +``` + ## Version 1 ### TypeHandle @@ -542,3 +565,5 @@ The version 1 `MethodDesc` APIs depend on the `MethodDescAlignment` global and t In the runtime a `MethodDesc` implicitly belongs to a single `MethodDescChunk` and some common data is shared between method descriptors that belong to the same chunk. A single method table will typically have multiple chunks. There are subkinds of MethodDescs at runtime of varying sizes (but the sizes must be mutliples of `MethodDescAlignment`) and each chunk contains method descriptors of the same size. + +**TODO(cdac)** diff --git a/src/native/managed/cdacreader/src/Contracts/RuntimeTypeSystem_1.cs b/src/native/managed/cdacreader/src/Contracts/RuntimeTypeSystem_1.cs index afbbe91fdfec68..3d86923290eee1 100644 --- a/src/native/managed/cdacreader/src/Contracts/RuntimeTypeSystem_1.cs +++ b/src/native/managed/cdacreader/src/Contracts/RuntimeTypeSystem_1.cs @@ -48,7 +48,7 @@ internal MethodTable(Data.MethodTable data) PerInstInfo = data.PerInstInfo; } - // this MethodTable is a canonical MethodTable if it's EEClassOrCanonMT is an EEClass + // this MethodTable is a canonical MethodTable if its EEClassOrCanonMT is an EEClass internal bool IsCanonMT => GetEEClassOrCanonMTBits(EEClassOrCanonMT) == EEClassOrCanonMTBits.EEClass; } From dcc4540ffc00ebd835c354ac6faf548b4ef9f947 Mon Sep 17 00:00:00 2001 From: Aleksey Kliger Date: Fri, 12 Jul 2024 15:57:42 -0400 Subject: [PATCH 21/38] fix RuntimeTypeSystem unit tests mock the additional data and globals --- .../managed/cdacreader/tests/MethodTableTests.cs | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/native/managed/cdacreader/tests/MethodTableTests.cs b/src/native/managed/cdacreader/tests/MethodTableTests.cs index 1e2c2cf53d9fa9..66ff051022885c 100644 --- a/src/native/managed/cdacreader/tests/MethodTableTests.cs +++ b/src/native/managed/cdacreader/tests/MethodTableTests.cs @@ -35,6 +35,7 @@ public unsafe class MethodTableTests { 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}}, + { nameof (Data.EEClass.NumNonVirtualSlots), new () { Offset = 24, Type = DataType.uint16}}, } }; @@ -48,6 +49,7 @@ private static readonly (DataType Type, Target.TypeInfo Info)[] RTSTypes = private static readonly (string Name, ulong Value, string? Type)[] RTSGlobals = [ (nameof(Constants.Globals.FreeObjectMethodTable), TestFreeObjectMethodTableGlobalAddress, null), + (nameof(Constants.Globals.MethodDescAlignment), 8, nameof(DataType.uint64)), ]; private static MockMemorySpace.Builder AddFreeObjectMethodTable(TargetTestHelpers targetTestHelpers, MockMemorySpace.Builder builder) @@ -60,13 +62,14 @@ private static MockMemorySpace.Builder AddFreeObjectMethodTable(TargetTestHelper ]); } - private static MockMemorySpace.Builder AddEEClass(TargetTestHelpers targetTestHelpers, MockMemorySpace.Builder builder, TargetPointer eeClassPtr, string name, TargetPointer canonMTPtr, uint attr, ushort numMethods) + private static MockMemorySpace.Builder AddEEClass(TargetTestHelpers targetTestHelpers, MockMemorySpace.Builder builder, TargetPointer eeClassPtr, string name, TargetPointer canonMTPtr, uint attr, ushort numMethods, ushort numNonVirtualSlots) { MockMemorySpace.HeapFragment eeClassFragment = new() { Name = $"EEClass '{name}'", Address = eeClassPtr, Data = new byte[targetTestHelpers.SizeOfTypeInfo(EEClassTypeInfo)] }; Span dest = eeClassFragment.Data; targetTestHelpers.WritePointer(dest.Slice(EEClassTypeInfo.Fields[nameof(Data.EEClass.MethodTable)].Offset), canonMTPtr); targetTestHelpers.Write(dest.Slice(EEClassTypeInfo.Fields[nameof(Data.EEClass.CorTypeAttr)].Offset), attr); targetTestHelpers.Write(dest.Slice(EEClassTypeInfo.Fields[nameof(Data.EEClass.NumMethods)].Offset), numMethods); + targetTestHelpers.Write(dest.Slice(EEClassTypeInfo.Fields[nameof(Data.EEClass.NumNonVirtualSlots)].Offset), numNonVirtualSlots); return builder.AddHeapFragment(eeClassFragment); } @@ -163,7 +166,7 @@ private static MockMemorySpace.Builder AddSystemObject(TargetTestHelpers targetT System.Reflection.TypeAttributes typeAttributes = System.Reflection.TypeAttributes.Public | System.Reflection.TypeAttributes.Class; const int numMethods = 8; // System.Object has 8 methods const int numVirtuals = 3; // System.Object has 3 virtual methods - builder = AddEEClass(targetTestHelpers, builder, systemObjectEEClassPtr, "System.Object", systemObjectMethodTablePtr, attr: (uint)typeAttributes, numMethods: numMethods); + builder = AddEEClass(targetTestHelpers, builder, systemObjectEEClassPtr, "System.Object", systemObjectMethodTablePtr, attr: (uint)typeAttributes, numMethods: numMethods, numNonVirtualSlots: 0); builder = AddMethodTable(targetTestHelpers, builder, systemObjectMethodTablePtr, "System.Object", systemObjectEEClassPtr, mtflags: default, mtflags2: default, baseSize: targetTestHelpers.ObjectBaseSize, module: TargetPointer.Null, parentMethodTable: TargetPointer.Null, numInterfaces: 0, numVirtuals: numVirtuals); @@ -218,7 +221,7 @@ public void ValidateSystemStringMethodTable(MockTarget.Architecture arch) const int numInterfaces = 8; // Arbitrary const int numVirtuals = 3; // at least as many as System.Object uint mtflags = (uint)RuntimeTypeSystem_1.WFLAGS_HIGH.HasComponentSize | /*componentSize: */2; - builder = AddEEClass(targetTestHelpers, builder, systemStringEEClassPtr, "System.String", systemStringMethodTablePtr, attr: (uint)typeAttributes, numMethods: numMethods); + builder = AddEEClass(targetTestHelpers, builder, systemStringEEClassPtr, "System.String", systemStringMethodTablePtr, attr: (uint)typeAttributes, numMethods: numMethods, numNonVirtualSlots: 0); builder = AddMethodTable(targetTestHelpers, builder, systemStringMethodTablePtr, "System.String", systemStringEEClassPtr, mtflags: mtflags, mtflags2: default, baseSize: targetTestHelpers.StringBaseSize, module: TargetPointer.Null, parentMethodTable: systemObjectMethodTablePtr, numInterfaces: numInterfaces, numVirtuals: numVirtuals); @@ -293,7 +296,7 @@ public void ValidateGenericInstMethodTable(MockTarget.Architecture arch) const int numInterfaces = 0; const int numVirtuals = 3; const uint gtd_mtflags = 0x00000030; // TODO: GenericsMask_TypicalInst - builder = AddEEClass(targetTestHelpers, builder, genericDefinitionEEClassPtr, "EEClass GenericDefinition", genericDefinitionMethodTablePtr, attr: (uint)typeAttributes, numMethods: numMethods); + builder = AddEEClass(targetTestHelpers, builder, genericDefinitionEEClassPtr, "EEClass GenericDefinition", genericDefinitionMethodTablePtr, attr: (uint)typeAttributes, numMethods: numMethods, numNonVirtualSlots: 0); builder = AddMethodTable(targetTestHelpers, builder, genericDefinitionMethodTablePtr, "MethodTable GenericDefinition", genericDefinitionEEClassPtr, mtflags: gtd_mtflags, mtflags2: default, baseSize: targetTestHelpers.ObjectBaseSize, module: TargetPointer.Null, parentMethodTable: systemObjectMethodTablePtr, numInterfaces: numInterfaces, numVirtuals: numVirtuals); @@ -348,7 +351,7 @@ public void ValidateArrayInstMethodTable(MockTarget.Architecture arch) const ushort systemArrayNumMethods = 37; // Arbitrary. Not trying to exactly match the real System.Array const uint systemArrayCorTypeAttr = (uint)(System.Reflection.TypeAttributes.Public | System.Reflection.TypeAttributes.Class); - builder = AddEEClass(targetTestHelpers, builder, systemArrayEEClassPtr, "EEClass System.Array", systemArrayMethodTablePtr, attr: systemArrayCorTypeAttr, numMethods: systemArrayNumMethods); + builder = AddEEClass(targetTestHelpers, builder, systemArrayEEClassPtr, "EEClass System.Array", systemArrayMethodTablePtr, attr: systemArrayCorTypeAttr, numMethods: systemArrayNumMethods, numNonVirtualSlots: 0); builder = AddMethodTable(targetTestHelpers, builder, systemArrayMethodTablePtr, "MethodTable System.Array", systemArrayEEClassPtr, mtflags: default, mtflags2: default, baseSize: targetTestHelpers.ObjectBaseSize, module: TargetPointer.Null, parentMethodTable: systemObjectMethodTablePtr, numInterfaces: systemArrayNumInterfaces, numVirtuals: 3); @@ -356,7 +359,7 @@ public void ValidateArrayInstMethodTable(MockTarget.Architecture arch) const uint arrayInst_mtflags = (uint)(RuntimeTypeSystem_1.WFLAGS_HIGH.HasComponentSize | RuntimeTypeSystem_1.WFLAGS_HIGH.Category_Array) | arrayInstanceComponentSize; const uint arrayInstCorTypeAttr = (uint)(System.Reflection.TypeAttributes.Public | System.Reflection.TypeAttributes.Class | System.Reflection.TypeAttributes.Sealed); - builder = AddEEClass(targetTestHelpers, builder, arrayInstanceEEClassPtr, "EEClass ArrayInstance", arrayInstanceMethodTablePtr, attr: arrayInstCorTypeAttr, numMethods: systemArrayNumMethods); + builder = AddEEClass(targetTestHelpers, builder, arrayInstanceEEClassPtr, "EEClass ArrayInstance", arrayInstanceMethodTablePtr, attr: arrayInstCorTypeAttr, numMethods: systemArrayNumMethods, numNonVirtualSlots: 0); builder = AddMethodTable(targetTestHelpers, builder, arrayInstanceMethodTablePtr, "MethodTable ArrayInstance", arrayInstanceEEClassPtr, mtflags: arrayInst_mtflags, mtflags2: default, baseSize: targetTestHelpers.ObjectBaseSize, module: TargetPointer.Null, parentMethodTable: systemArrayMethodTablePtr, numInterfaces: systemArrayNumInterfaces, numVirtuals: 3); From c31bdcf33577a7d818813c829996d20d04ef400b Mon Sep 17 00:00:00 2001 From: Aleksey Kliger Date: Mon, 15 Jul 2024 13:56:42 -0400 Subject: [PATCH 22/38] update contract --- src/coreclr/debug/runtimeinfo/datadescriptor.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/coreclr/debug/runtimeinfo/datadescriptor.h b/src/coreclr/debug/runtimeinfo/datadescriptor.h index 5e5e492bcfaf61..5a1e12fd306500 100644 --- a/src/coreclr/debug/runtimeinfo/datadescriptor.h +++ b/src/coreclr/debug/runtimeinfo/datadescriptor.h @@ -190,7 +190,7 @@ CDAC_TYPE_FIELD(Module, /*pointer*/, TypeDefToMethodTableMap, cdac_offsets::TypeRefToMethodTableMap) CDAC_TYPE_END(Module) -// Metadata +// RuntimeTypeSystem CDAC_TYPE_BEGIN(MethodTable) CDAC_TYPE_INDETERMINATE(MethodTable) From 7517e3265e93de20ae437fe9e18c2371ab02ff9e Mon Sep 17 00:00:00 2001 From: Aleksey Kliger Date: Mon, 15 Jul 2024 15:57:45 -0400 Subject: [PATCH 23/38] fix GetMethodDescChunkPointerMayThrow --- .../src/Contracts/RuntimeTypeSystem_1.NonValidated.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/native/managed/cdacreader/src/Contracts/RuntimeTypeSystem_1.NonValidated.cs b/src/native/managed/cdacreader/src/Contracts/RuntimeTypeSystem_1.NonValidated.cs index b37b5d002121ce..dcc6eec8e068dc 100644 --- a/src/native/managed/cdacreader/src/Contracts/RuntimeTypeSystem_1.NonValidated.cs +++ b/src/native/managed/cdacreader/src/Contracts/RuntimeTypeSystem_1.NonValidated.cs @@ -220,12 +220,12 @@ private TargetPointer GetClassThrowing(NonValidated.MethodTable methodTable) private TargetPointer GetMethodDescChunkPointerMayThrow(TargetPointer methodDescPointer, Data.MethodDesc umd) { - ulong? methodDescSize = _target.GetTypeInfo(DataType.MethodDesc).Size; - if (!methodDescSize.HasValue) + ulong? methodDescChunkSize = _target.GetTypeInfo(DataType.MethodDescChunk).Size; + if (!methodDescChunkSize.HasValue) { - throw new InvalidOperationException("Target has no definite MethodDesc size"); + throw new InvalidOperationException("Target has no definite MethodDescChunk size"); } - ulong chunkAddress = (ulong)methodDescPointer - methodDescSize.Value - umd.ChunkIndex * MethodDescAlignment; + ulong chunkAddress = (ulong)methodDescPointer - methodDescChunkSize.Value - umd.ChunkIndex * MethodDescAlignment; return new TargetPointer(chunkAddress); } From 54ab23e35697b8d2bf793b703e03128449cecd4b Mon Sep 17 00:00:00 2001 From: Aleksey Kliger Date: Tue, 16 Jul 2024 16:10:38 -0400 Subject: [PATCH 24/38] 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 dd451fa075f6ae..1e2c2cf53d9fa9 100644 --- a/src/native/managed/cdacreader/tests/MethodTableTests.cs +++ b/src/native/managed/cdacreader/tests/MethodTableTests.cs @@ -24,6 +24,7 @@ public unsafe class MethodTableTests { nameof(Data.MethodTable.ParentMethodTable), new () { Offset = 40, Type = DataType.pointer}}, { nameof(Data.MethodTable.NumInterfaces), new () { Offset = 48, Type = DataType.uint16}}, { nameof(Data.MethodTable.NumVirtuals), new () { Offset = 50, Type = DataType.uint16}}, + { nameof(Data.MethodTable.PerInstInfo), new () { Offset = 56, Type = DataType.pointer}}, } }; @@ -33,6 +34,7 @@ public unsafe class MethodTableTests { nameof (Data.EEClass.MethodTable), new () { Offset = 8, Type = DataType.pointer}}, { nameof (Data.EEClass.CorTypeAttr), new () { Offset = 16, Type = DataType.uint32}}, { nameof (Data.EEClass.NumMethods), new () { Offset = 20, Type = DataType.uint16}}, + { nameof (Data.EEClass.InternalCorElementType), new () { Offset = 22, Type = DataType.uint8}}, } }; @@ -150,7 +152,7 @@ public void HasRuntimeTypeSystemContract(MockTarget.Architecture arch) { Contracts.IRuntimeTypeSystem metadataContract = target.Contracts.RuntimeTypeSystem; Assert.NotNull(metadataContract); - Contracts.MethodTableHandle handle = metadataContract.GetMethodTableHandle(TestFreeObjectMethodTableAddress); + Contracts.TypeHandle handle = metadataContract.GetTypeHandle(TestFreeObjectMethodTableAddress); Assert.NotEqual(TargetPointer.Null, handle.Address); Assert.True(metadataContract.IsFreeObjectMethodTable(handle)); }); @@ -187,9 +189,9 @@ public void ValidateSystemObjectMethodTable(MockTarget.Architecture arch) { Contracts.IRuntimeTypeSystem metadataContract = target.Contracts.RuntimeTypeSystem; Assert.NotNull(metadataContract); - Contracts.MethodTableHandle systemObjectMethodTableHandle = metadataContract.GetMethodTableHandle(systemObjectMethodTablePtr); - Assert.Equal(systemObjectMethodTablePtr.Value, systemObjectMethodTableHandle.Address.Value); - Assert.False(metadataContract.IsFreeObjectMethodTable(systemObjectMethodTableHandle)); + Contracts.TypeHandle systemObjectTypeHandle = metadataContract.GetTypeHandle(systemObjectMethodTablePtr); + Assert.Equal(systemObjectMethodTablePtr.Value, systemObjectTypeHandle.Address.Value); + Assert.False(metadataContract.IsFreeObjectMethodTable(systemObjectTypeHandle)); }); } @@ -226,10 +228,10 @@ public void ValidateSystemStringMethodTable(MockTarget.Architecture arch) { Contracts.IRuntimeTypeSystem metadataContract = target.Contracts.RuntimeTypeSystem; Assert.NotNull(metadataContract); - Contracts.MethodTableHandle systemStringMethodTableHandle = metadataContract.GetMethodTableHandle(systemStringMethodTablePtr); - Assert.Equal(systemStringMethodTablePtr.Value, systemStringMethodTableHandle.Address.Value); - Assert.False(metadataContract.IsFreeObjectMethodTable(systemStringMethodTableHandle)); - Assert.True(metadataContract.IsString(systemStringMethodTableHandle)); + Contracts.TypeHandle systemStringTypeHandle = metadataContract.GetTypeHandle(systemStringMethodTablePtr); + Assert.Equal(systemStringMethodTablePtr.Value, systemStringTypeHandle.Address.Value); + Assert.False(metadataContract.IsFreeObjectMethodTable(systemStringTypeHandle)); + Assert.True(metadataContract.IsString(systemStringTypeHandle)); }); } @@ -258,7 +260,7 @@ public void MethodTableEEClassInvalidThrows(MockTarget.Architecture arch) { Contracts.IRuntimeTypeSystem metadataContract = target.Contracts.RuntimeTypeSystem; Assert.NotNull(metadataContract); - Assert.Throws(() => metadataContract.GetMethodTableHandle(badMethodTablePtr)); + Assert.Throws(() => metadataContract.GetTypeHandle(badMethodTablePtr)); }); } @@ -308,11 +310,11 @@ public void ValidateGenericInstMethodTable(MockTarget.Architecture arch) { Contracts.IRuntimeTypeSystem metadataContract = target.Contracts.RuntimeTypeSystem; Assert.NotNull(metadataContract); - Contracts.MethodTableHandle genericInstanceMethodTableHandle = metadataContract.GetMethodTableHandle(genericInstanceMethodTablePtr); - Assert.Equal(genericInstanceMethodTablePtr.Value, genericInstanceMethodTableHandle.Address.Value); - Assert.False(metadataContract.IsFreeObjectMethodTable(genericInstanceMethodTableHandle)); - Assert.False(metadataContract.IsString(genericInstanceMethodTableHandle)); - Assert.Equal(numMethods, metadataContract.GetNumMethods(genericInstanceMethodTableHandle)); + Contracts.TypeHandle genericInstanceTypeHandle = metadataContract.GetTypeHandle(genericInstanceMethodTablePtr); + Assert.Equal(genericInstanceMethodTablePtr.Value, genericInstanceTypeHandle.Address.Value); + Assert.False(metadataContract.IsFreeObjectMethodTable(genericInstanceTypeHandle)); + Assert.False(metadataContract.IsString(genericInstanceTypeHandle)); + Assert.Equal(numMethods, metadataContract.GetNumMethods(genericInstanceTypeHandle)); }); } @@ -365,11 +367,11 @@ public void ValidateArrayInstMethodTable(MockTarget.Architecture arch) { Contracts.IRuntimeTypeSystem metadataContract = target.Contracts.RuntimeTypeSystem; Assert.NotNull(metadataContract); - Contracts.MethodTableHandle arrayInstanceMethodTableHandle = metadataContract.GetMethodTableHandle(arrayInstanceMethodTablePtr); - Assert.Equal(arrayInstanceMethodTablePtr.Value, arrayInstanceMethodTableHandle.Address.Value); - Assert.False(metadataContract.IsFreeObjectMethodTable(arrayInstanceMethodTableHandle)); - Assert.False(metadataContract.IsString(arrayInstanceMethodTableHandle)); - Assert.Equal(arrayInstanceComponentSize, metadataContract.GetComponentSize(arrayInstanceMethodTableHandle)); + Contracts.TypeHandle arrayInstanceTypeHandle = metadataContract.GetTypeHandle(arrayInstanceMethodTablePtr); + Assert.Equal(arrayInstanceMethodTablePtr.Value, arrayInstanceTypeHandle.Address.Value); + Assert.False(metadataContract.IsFreeObjectMethodTable(arrayInstanceTypeHandle)); + Assert.False(metadataContract.IsString(arrayInstanceTypeHandle)); + Assert.Equal(arrayInstanceComponentSize, metadataContract.GetComponentSize(arrayInstanceTypeHandle)); }); } From 27fcddc09567f2b3958e3238ab9f8706a19c227c Mon Sep 17 00:00:00 2001 From: Aleksey Kliger Date: Tue, 16 Jul 2024 16:38:44 -0400 Subject: [PATCH 25/38] fixup rebase --- .../src/Contracts/RuntimeTypeSystem_1.NonValidated.cs | 4 ++-- .../managed/cdacreader/src/Contracts/RuntimeTypeSystem_1.cs | 5 +++-- src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs | 2 +- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/native/managed/cdacreader/src/Contracts/RuntimeTypeSystem_1.NonValidated.cs b/src/native/managed/cdacreader/src/Contracts/RuntimeTypeSystem_1.NonValidated.cs index dcc6eec8e068dc..15eca7b396cd12 100644 --- a/src/native/managed/cdacreader/src/Contracts/RuntimeTypeSystem_1.NonValidated.cs +++ b/src/native/managed/cdacreader/src/Contracts/RuntimeTypeSystem_1.NonValidated.cs @@ -257,9 +257,9 @@ private bool ValidateMethodDescPointer(TargetPointer methodDescPointer, [NotNull { return false; } - MethodTableHandle methodTableHandle = GetMethodTableHandle(methodTablePointer); + TypeHandle typeHandle = GetTypeHandle(methodTablePointer); - if (umd.Slot >= GetNumVtableSlots(methodTableHandle) && !umd.HasNonVtableSlot) + if (umd.Slot >= GetNumVtableSlots(typeHandle) && !umd.HasNonVtableSlot) { return false; } diff --git a/src/native/managed/cdacreader/src/Contracts/RuntimeTypeSystem_1.cs b/src/native/managed/cdacreader/src/Contracts/RuntimeTypeSystem_1.cs index 3d86923290eee1..0ffad344b134d5 100644 --- a/src/native/managed/cdacreader/src/Contracts/RuntimeTypeSystem_1.cs +++ b/src/native/managed/cdacreader/src/Contracts/RuntimeTypeSystem_1.cs @@ -446,12 +446,13 @@ private FunctionPointerRetAndArgs(Target target, TargetPointer typePointer) TypeHandles[i] = rts.GetTypeHandle(target.ReadPointer(retAndArgs + (ulong)target.PointerSize * (ulong)i)); } } + } - internal ushort GetNumVtableSlots(TypeHandleHandle typeHandle) + internal ushort GetNumVtableSlots(TypeHandle typeHandle) { if (!typeHandle.IsMethodTable()) return 0; - MethodTable methodTable = _methodTables[typeHandleHandle.Address]; + MethodTable methodTable = _methodTables[typeHandle.Address]; ushort numNonVirtualSlots = methodTable.IsCanonMT ? GetClassData(typeHandle).NumNonVirtualSlots : (ushort)0; return checked((ushort)(methodTable.NumVirtuals + numNonVirtualSlots)); } diff --git a/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs b/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs index c15ed2c879a1ed..87f1b6313dc1f4 100644 --- a/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs +++ b/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs @@ -104,7 +104,7 @@ public unsafe int GetMethodDescData(ulong methodDesc, ulong ip, DacpMethodDescDa return HResults.E_NOTIMPL; } - catch (Exception ex) + catch (global::System.Exception ex) { return ex.HResult; } From 2a41206b5000340eb71c8af5440453b13eb50b3a Mon Sep 17 00:00:00 2001 From: David Wrighton Date: Tue, 16 Jul 2024 13:46:53 -0700 Subject: [PATCH 26/38] 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 355897000ec69b..af1e23df461b91 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 ce7fa36caa4dd5..fc1ac8c90963f8 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 531a30389e41c0..ea863ce178e518 100644 --- a/src/native/managed/cdacreader/src/Data/EEClass.cs +++ b/src/native/managed/cdacreader/src/Data/EEClass.cs @@ -1,8 +1,6 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System; - namespace Microsoft.Diagnostics.DataContractReader.Data; public sealed class EEClass : IData diff --git a/src/native/managed/cdacreader/src/Data/GenericsDictInfo.cs b/src/native/managed/cdacreader/src/Data/GenericsDictInfo.cs index a25f4a87c8471b..7aad8f95301d76 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 b1b6723d556541..6461a9521f4721 100644 --- a/src/native/managed/cdacreader/src/Data/MethodTable.cs +++ b/src/native/managed/cdacreader/src/Data/MethodTable.cs @@ -1,8 +1,6 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System; - namespace Microsoft.Diagnostics.DataContractReader.Data; internal sealed class MethodTable : IData diff --git a/src/native/managed/cdacreader/src/Data/MethodTableAuxiliaryData.cs b/src/native/managed/cdacreader/src/Data/MethodTableAuxiliaryData.cs index bdc711a5104735..e04e5b56b0c547 100644 --- a/src/native/managed/cdacreader/src/Data/MethodTableAuxiliaryData.cs +++ b/src/native/managed/cdacreader/src/Data/MethodTableAuxiliaryData.cs @@ -1,8 +1,6 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System; - namespace Microsoft.Diagnostics.DataContractReader.Data; internal sealed class MethodTableAuxiliaryData : IData diff --git a/src/native/managed/cdacreader/src/Data/TypeDesc.cs b/src/native/managed/cdacreader/src/Data/TypeDesc.cs index 5cef78482999de..d807f91a365008 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 f861794529346b..5e683d5c72b057 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 30478662048588..244b917a508d8c 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 27/38] - 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 aaceda4699982d..4ddc1eb84ad3ab 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 5399e83f5b4a37..864a225cd6bb03 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 6d78b6161f90f8..688ec1d5b89844 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 00000000000000..9dd20059a55165 --- /dev/null +++ b/src/native/managed/cdacreader/tests/DacStreamsTests.cs @@ -0,0 +1,244 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using System.Text; +using Xunit; + +namespace Microsoft.Diagnostics.DataContractReader.UnitTests; + +public class DacStreamsTests +{ + private delegate MockMemorySpace.Builder ConfigureContextBuilder(MockMemorySpace.Builder builder); + + const ulong TestMiniMetaDataBuffGlobalAddress = 0x00000000_200000a0; + const ulong TestMiniMetaDataBuffGlobalMaxSize = 0x00000000_20000000; + const ulong TestMiniMetaDataBuffAddress = 0x00000000_100000a0; + + const uint MiniMetadataSignature = 0x6d727473; + const uint EENameStreamSignature = 0x614e4545; + + const uint MiniMetaDataStreamsHeaderSize = 12; + + private static readonly (DataType Type, Target.TypeInfo Info)[] DacStreamsTypes = + [ + ]; + + private static readonly (string Name, ulong Value, string? Type)[] DacStreamsGlobals = + [ + (nameof(Constants.Globals.MiniMetaDataBuffAddress), TestMiniMetaDataBuffGlobalAddress, null), + (nameof(Constants.Globals.MiniMetaDataBuffMaxSize), TestMiniMetaDataBuffGlobalMaxSize, null), + ]; + + private static unsafe void DacStreamsContractHelper(MockTarget.Architecture arch, ConfigureContextBuilder configure, Action testCase) + { + TargetTestHelpers targetTestHelpers = new(arch); + string metadataTypesJson = TargetTestHelpers.MakeTypesJson(DacStreamsTypes); + string metadataGlobalsJson = TargetTestHelpers.MakeGlobalsJson(DacStreamsGlobals); + byte[] json = Encoding.UTF8.GetBytes($$""" + { + "version": 0, + "baseline": "empty", + "contracts": { + "{{nameof(Contracts.DacStreams)}}": 1 + }, + "types": { {{metadataTypesJson}} }, + "globals": { {{metadataGlobalsJson}} } + } + """); + Span descriptor = stackalloc byte[targetTestHelpers.ContractDescriptorSize]; + targetTestHelpers.ContractDescriptorFill(descriptor, json.Length, DacStreamsGlobals.Length); + + int pointerSize = targetTestHelpers.PointerSize; + Span pointerData = stackalloc byte[DacStreamsGlobals.Length * pointerSize]; + for (int i = 0; i < DacStreamsGlobals.Length; i++) + { + var (_, value, _) = DacStreamsGlobals[i]; + targetTestHelpers.WritePointer(pointerData.Slice(i * pointerSize), value); + } + + fixed (byte* jsonPtr = json) + { + MockMemorySpace.Builder builder = new(); + + builder = builder.SetDescriptor(descriptor) + .SetJson(json) + .SetPointerData(pointerData); + + if (configure != null) + { + builder = configure(builder); + } + + using MockMemorySpace.ReadContext context = builder.Create(); + + bool success = MockMemorySpace.TryCreateTarget(&context, out Target? target); + Assert.True(success); + + testCase(target); + } + GC.KeepAlive(json); + } + + MockMemorySpace.Builder AddMiniMetaDataBuffMaxSize(TargetTestHelpers targetTestHelpers, MockMemorySpace.Builder builder, uint maxSize) + { + MockMemorySpace.HeapFragment globalAddr = new() { Name = "Address of MiniMetaDataBuffMaxSize", Address = TestMiniMetaDataBuffGlobalMaxSize, Data = new byte[4] }; + targetTestHelpers.Write(globalAddr.Data, maxSize); + return builder.AddHeapFragment(globalAddr); + } + + MockMemorySpace.Builder AddMiniMetaDataBuffAddress(TargetTestHelpers targetTestHelpers, MockMemorySpace.Builder builder, ulong pointer) + { + MockMemorySpace.HeapFragment globalAddr = new() { Name = "Address of MiniMetaDataBuffAddress", Address = TestMiniMetaDataBuffGlobalAddress, Data = new byte[targetTestHelpers.PointerSize] }; + targetTestHelpers.WritePointer(globalAddr.Data, pointer); + return builder.AddHeapFragment(globalAddr); + } + + private class CurrentPointer + { + public ulong Pointer; + } + + MockMemorySpace.Builder AddMiniMetaDataStreamsHeader(TargetTestHelpers targetTestHelpers, MockMemorySpace.Builder builder, uint totalSizeOtherThanStreamsHeader, uint countStreams, CurrentPointer currentPointer) + { + MockMemorySpace.HeapFragment globalAddr = new() { Name = "MiniMetaDataStreamsHeader", Address = currentPointer.Pointer, Data = new byte[MiniMetaDataStreamsHeaderSize] }; + targetTestHelpers.Write(globalAddr.Data.AsSpan().Slice(0, 4), MiniMetadataSignature); + targetTestHelpers.Write(globalAddr.Data.AsSpan().Slice(4, 4), totalSizeOtherThanStreamsHeader + MiniMetaDataStreamsHeaderSize); + targetTestHelpers.Write(globalAddr.Data.AsSpan().Slice(8, 4), countStreams); + currentPointer.Pointer += 12; + return builder.AddHeapFragment(globalAddr); + } + + MockMemorySpace.Builder AddEENameStreamHeader(TargetTestHelpers targetTestHelpers, MockMemorySpace.Builder builder, uint countEntries, CurrentPointer currentPointer) + { + MockMemorySpace.HeapFragment globalAddr = new() { Name = "EEStreamHeader", Address = currentPointer.Pointer, Data = new byte[8] }; + targetTestHelpers.Write(globalAddr.Data.AsSpan().Slice(0, 4), EENameStreamSignature); + targetTestHelpers.Write(globalAddr.Data.AsSpan().Slice(4, 4), countEntries); + currentPointer.Pointer += 8; + return builder.AddHeapFragment(globalAddr); + } + + + MockMemorySpace.Builder AddEENameStream(TargetTestHelpers targetTestHelpers, MockMemorySpace.Builder builder, List<(ulong Pointer, string Name)> names, CurrentPointer currentPointer) + { + builder = AddEENameStreamHeader(targetTestHelpers, builder, checked((uint)names.Count), currentPointer); + + for (int i = 0; i < names.Count; i++) + { + int byteCountWithoutNullTerminator = Encoding.UTF8.GetByteCount(names[i].Name); + int entrySize = byteCountWithoutNullTerminator + 1 + targetTestHelpers.PointerSize; + MockMemorySpace.HeapFragment entryAddr = new() + { + Name = $"EEStreamEntry{i}", Address = currentPointer.Pointer, Data = new byte[byteCountWithoutNullTerminator + 1 + targetTestHelpers.PointerSize] + }; + targetTestHelpers.WritePointer(entryAddr.Data.AsSpan().Slice(0, targetTestHelpers.PointerSize), names[i].Pointer); + Encoding.UTF8.TryGetBytes(names[i].Name.AsSpan(), entryAddr.Data.AsSpan().Slice(targetTestHelpers.PointerSize, byteCountWithoutNullTerminator), out _); + targetTestHelpers.Write(entryAddr.Data.AsSpan().Slice(byteCountWithoutNullTerminator + targetTestHelpers.PointerSize, 1), (byte)0); + currentPointer.Pointer += (ulong)entrySize; + builder = builder.AddHeapFragment(entryAddr); + } + return builder; + } + + [Theory] + [ClassData(typeof(MockTarget.StdArch))] + public void DacStreamValues(MockTarget.Architecture arch) + { + TargetTestHelpers targetTestHelpers = new(arch); + + DacStreamsContractHelper(arch, + (builder) => + { + // Test normal non-error behavior + + List<(ulong Pointer, string Name)> values = [((ulong)0x1234, "Type1"), ((ulong)0x1238, "Type2")]; + builder = AddMiniMetaDataBuffAddress(targetTestHelpers, builder, TestMiniMetaDataBuffAddress); + builder = AddMiniMetaDataBuffMaxSize(targetTestHelpers, builder, 0x10000); + CurrentPointer currentPointer = new(); + ulong eeNameStreamStart = TestMiniMetaDataBuffAddress + MiniMetaDataStreamsHeaderSize; + currentPointer.Pointer = TestMiniMetaDataBuffAddress + MiniMetaDataStreamsHeaderSize; + builder = AddEENameStream(targetTestHelpers, builder, values, currentPointer); + uint eeNameStreamSize = checked((uint)(currentPointer.Pointer - eeNameStreamStart)); + + currentPointer.Pointer = TestMiniMetaDataBuffAddress; + builder = AddMiniMetaDataStreamsHeader(targetTestHelpers, builder, eeNameStreamSize, 1, currentPointer); + return builder; + }, + (target) => + { + Contracts.IDacStreams dacStreamsContract = target.Contracts.DacStreams; + Assert.NotNull(dacStreamsContract); + Assert.Null(dacStreamsContract.StringFromEEAddress(0)); + Assert.Equal("Type1", dacStreamsContract.StringFromEEAddress(0x1234)); + Assert.Equal("Type2", dacStreamsContract.StringFromEEAddress(0x1238)); + }); + } + + [Theory] + [ClassData(typeof(MockTarget.StdArch))] + public void DacStreamValues_TruncatedTotalSize(MockTarget.Architecture arch) + { + // Test behavior if TotalSize isn't big enough to hold the last entry + + TargetTestHelpers targetTestHelpers = new(arch); + + DacStreamsContractHelper(arch, + (builder) => + { + List<(ulong Pointer, string Name)> values = [((ulong)0x1234, "Type1"), ((ulong)0x1238, "Type2")]; + builder = AddMiniMetaDataBuffAddress(targetTestHelpers, builder, TestMiniMetaDataBuffAddress); + builder = AddMiniMetaDataBuffMaxSize(targetTestHelpers, builder, 0x10000); + CurrentPointer currentPointer = new(); + ulong eeNameStreamStart = TestMiniMetaDataBuffAddress + MiniMetaDataStreamsHeaderSize; + currentPointer.Pointer = TestMiniMetaDataBuffAddress + MiniMetaDataStreamsHeaderSize; + builder = AddEENameStream(targetTestHelpers, builder, values, currentPointer); + uint eeNameStreamSize = checked((uint)(currentPointer.Pointer - eeNameStreamStart)); + + currentPointer.Pointer = TestMiniMetaDataBuffAddress; + builder = AddMiniMetaDataStreamsHeader(targetTestHelpers, builder, eeNameStreamSize - 2, 1, currentPointer); + return builder; + }, + (target) => + { + Contracts.IDacStreams dacStreamsContract = target.Contracts.DacStreams; + Assert.NotNull(dacStreamsContract); + Assert.Null(dacStreamsContract.StringFromEEAddress(0)); + Assert.Equal("Type1", dacStreamsContract.StringFromEEAddress(0x1234)); + Assert.Null(dacStreamsContract.StringFromEEAddress(0x1238)); + }); + } + + [Theory] + [ClassData(typeof(MockTarget.StdArch))] + public void DacStreamValues_TruncatedBuffMaxSize(MockTarget.Architecture arch) + { + // Test behavior if MaxSize global is smaller than TotalSize + TargetTestHelpers targetTestHelpers = new(arch); + + DacStreamsContractHelper(arch, + (builder) => + { + List<(ulong Pointer, string Name)> values = [((ulong)0x1234, "Type1"), ((ulong)0x1238, "Type2")]; + builder = AddMiniMetaDataBuffAddress(targetTestHelpers, builder, TestMiniMetaDataBuffAddress); + builder = AddMiniMetaDataBuffMaxSize(targetTestHelpers, builder, (uint)(0x20 + targetTestHelpers.PointerSize * 2 - 1)); + CurrentPointer currentPointer = new(); + ulong eeNameStreamStart = TestMiniMetaDataBuffAddress + MiniMetaDataStreamsHeaderSize; + currentPointer.Pointer = TestMiniMetaDataBuffAddress + MiniMetaDataStreamsHeaderSize; + builder = AddEENameStream(targetTestHelpers, builder, values, currentPointer); + uint eeNameStreamSize = checked((uint)(currentPointer.Pointer - eeNameStreamStart)); + + currentPointer.Pointer = TestMiniMetaDataBuffAddress; + builder = AddMiniMetaDataStreamsHeader(targetTestHelpers, builder, eeNameStreamSize, 1, currentPointer); + return builder; + }, + (target) => + { + Contracts.IDacStreams dacStreamsContract = target.Contracts.DacStreams; + Assert.NotNull(dacStreamsContract); + Assert.Null(dacStreamsContract.StringFromEEAddress(0)); + Assert.Null(dacStreamsContract.StringFromEEAddress(0x1234)); + Assert.Null(dacStreamsContract.StringFromEEAddress(0x1238)); + }); + } +} diff --git a/src/native/managed/cdacreader/tests/MockMemorySpace.cs b/src/native/managed/cdacreader/tests/MockMemorySpace.cs index c74771a7f5b5c3..f2e2419a8ba4b7 100644 --- a/src/native/managed/cdacreader/tests/MockMemorySpace.cs +++ b/src/native/managed/cdacreader/tests/MockMemorySpace.cs @@ -234,23 +234,41 @@ public HeapFragmentReader(IReadOnlyList fragments) public int ReadFragment(ulong address, Span buffer) { - foreach (var fragment in _fragments) + bool partialReadOcurred = false; + HeapFragment lastHeapFragment = default; + int availableLength = 0; + while (true) { - if (address >= fragment.Address && address < fragment.Address + (ulong)fragment.Data.Length) + bool tryAgain = false; + foreach (var fragment in _fragments) { - int offset = (int)(address - fragment.Address); - int availableLength = fragment.Data.Length - offset; - if (availableLength >= buffer.Length) + if (address >= fragment.Address && address < fragment.Address + (ulong)fragment.Data.Length) { - fragment.Data.AsSpan(offset, buffer.Length).CopyTo(buffer); - return 0; - } - else - { - throw new InvalidOperationException($"Not enough data in fragment at {fragment.Address:X} ('{fragment.Name}') to read {buffer.Length} bytes at {address:X} (only {availableLength} bytes available)"); + int offset = (int)(address - fragment.Address); + availableLength = fragment.Data.Length - offset; + if (availableLength >= buffer.Length) + { + fragment.Data.AsSpan(offset, buffer.Length).CopyTo(buffer); + return 0; + } + else + { + lastHeapFragment = fragment; + partialReadOcurred = true; + tryAgain = true; + fragment.Data.AsSpan(offset, availableLength).CopyTo(buffer); + buffer = buffer.Slice(availableLength); + address = fragment.Address + (ulong)fragment.Data.Length; + break; + } } } + if (!tryAgain) + break; } + + if (partialReadOcurred) + throw new InvalidOperationException($"Not enough data in fragment at {lastHeapFragment.Address:X} ('{lastHeapFragment.Name}') to read {buffer.Length} bytes at {address:X} (only {availableLength} bytes available)"); return -1; } } From 3a35f41bde13466028bf7087ade57141f88124c1 Mon Sep 17 00:00:00 2001 From: David Wrighton Date: Tue, 16 Jul 2024 15:51:14 -0700 Subject: [PATCH 28/38] 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 864a225cd6bb03..41d01f6e7db473 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 48ffcae5182507..7d477b00cd858d 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 } From 785ced4f064b48492174f5b4959a4654cb8186a7 Mon Sep 17 00:00:00 2001 From: David Wrighton Date: Fri, 19 Jul 2024 11:47:39 -0700 Subject: [PATCH 29/38] Add non-contract portion of GetMethodDescName --- .../cdacreader/src/Contracts/Loader.cs | 2 + .../src/Contracts/RuntimeTypeSystem.cs | 64 ++- .../cdacreader/src/Legacy/SOSDacImpl.cs | 65 ++- .../cdacreader/src/Legacy/SigFormat.cs | 469 ++++++++++++++++++ .../cdacreader/src/Legacy/TypeNameBuilder.cs | 119 +++++ src/native/managed/cdacreader/src/Target.cs | 29 ++ 6 files changed, 714 insertions(+), 34 deletions(-) create mode 100644 src/native/managed/cdacreader/src/Legacy/SigFormat.cs diff --git a/src/native/managed/cdacreader/src/Contracts/Loader.cs b/src/native/managed/cdacreader/src/Contracts/Loader.cs index 508fd8e56ad836..4b8ac4408041fb 100644 --- a/src/native/managed/cdacreader/src/Contracts/Loader.cs +++ b/src/native/managed/cdacreader/src/Contracts/Loader.cs @@ -130,6 +130,8 @@ static IContract IContract.Create(Target target, int version) public virtual TargetEcmaMetadata GetReadWriteMetadata(ModuleHandle handle) => throw new NotImplementedException(); public virtual ModuleLookupTables GetLookupTables(ModuleHandle handle) => throw new NotImplementedException(); + + public virtual string GetPath(ModuleHandle handle) => throw new NotImplementedException(); } internal readonly struct Loader : ILoader diff --git a/src/native/managed/cdacreader/src/Contracts/RuntimeTypeSystem.cs b/src/native/managed/cdacreader/src/Contracts/RuntimeTypeSystem.cs index 541810e0539fdc..dc60bb191025b4 100644 --- a/src/native/managed/cdacreader/src/Contracts/RuntimeTypeSystem.cs +++ b/src/native/managed/cdacreader/src/Contracts/RuntimeTypeSystem.cs @@ -67,40 +67,12 @@ internal MethodDescHandle(TargetPointer address) internal bool IsNull => Address == 0; } -internal enum CorElementType +public enum ArrayFunctionType { - 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, + Get, + Set, + Address, + Constructor } internal interface IRuntimeTypeSystem : IContract @@ -166,6 +138,32 @@ static IContract IContract.Create(Target target, int version) #region MethodDesc inspection APIs public virtual MethodDescHandle GetMethodDescHandle(TargetPointer targetPointer) => throw new NotImplementedException(); public virtual TargetPointer GetMethodTable(MethodDescHandle methodDesc) => throw new NotImplementedException(); + + // Return true for an uninstantiated generic method + public virtual bool IsGenericMethodDefinition(MethodDescHandle methodDesc) => throw new NotImplementedException(); + public virtual ReadOnlySpan GetGenericMethodInstantiation(MethodDescHandle methodDesc) => throw new NotImplementedException(); + + // Return mdTokenNil (0x06000000) if the method doesn't have a token, otherwise return the token of the method + public virtual uint GetMethodToken(MethodDescHandle methodDesc) => throw new NotImplementedException(); + + // Return true if a MethodDesc represents an array method + // An array method is also a StoredSigMethodDesc + public virtual bool IsArrayMethod(MethodDescHandle methodDesc, out ArrayFunctionType functionType) => throw new NotImplementedException(); + + // Return true if a MethodDesc represents a dynamically generated method, either an IL Stub dynamically + // generated by the runtime, or a MethodDesc that describes a method represented by the System.Reflection.Emit.DynamicMethod class + // A dynamic method is also a StoredSigMethodDesc + public virtual bool IsDynamicMethod(MethodDescHandle methodDesc, out ReadOnlySpan methodName) => throw new NotImplementedException(); + // A StoredSigMethodDesc is a MethodDesc for which the signature isn't found in metadata. + public virtual bool IsStoredSigMethodDesc(MethodDescHandle methodDesc, out ReadOnlySpan signature) => throw new NotImplementedException(); + + // Return true for a MethodDesc that describes a method represented by the System.Reflection.Emit.DynamicMethod class + // A LCG method is also a StoredSigMethodDesc + public virtual bool IsLCGMethod(MethodDescHandle methodDesc) => throw new NotImplementedException(); + + // Return true if a MethodDesc represents an IL Stub dynamically generated by the runtime + public virtual bool IsILStub(MethodDescHandle methodDesc) => throw new NotImplementedException(); + #endregion MethodDesc inspection APIs } diff --git a/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs b/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs index a28f2d062e21d4..b75d7a0f4e82dc 100644 --- a/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs +++ b/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs @@ -8,6 +8,7 @@ using System.Diagnostics.Contracts; using System.Runtime.InteropServices; using System.Runtime.InteropServices.Marshalling; +using System.Text; namespace Microsoft.Diagnostics.DataContractReader.Legacy; @@ -111,7 +112,69 @@ public unsafe int GetMethodDescData(ulong methodDesc, ulong ip, DacpMethodDescDa } public unsafe int GetMethodDescFromToken(ulong moduleAddr, uint token, ulong* methodDesc) => HResults.E_NOTIMPL; - public unsafe int GetMethodDescName(ulong methodDesc, uint count, char* name, uint* pNeeded) => HResults.E_NOTIMPL; + public unsafe int GetMethodDescName(ulong methodDesc, uint count, char* name, uint* pNeeded) + { + if (methodDesc == 0) + return HResults.E_INVALIDARG; + + int hr = HResults.S_OK; + if (pNeeded != null) + *pNeeded = 0; + try + { + StringBuilder stringBuilder = new StringBuilder(); + Contracts.IRuntimeTypeSystem rtsContract = _target.Contracts.RuntimeTypeSystem; + Contracts.MethodDescHandle methodDescHandle = rtsContract.GetMethodDescHandle(methodDesc); + try + { + TypeNameBuilder.AppendMethodInternal(_target, stringBuilder, methodDescHandle, TypeNameFormat.FormatSignature | TypeNameFormat.FormatNamespace | TypeNameFormat.FormatFullInst); + } + catch + { + hr = HResults.E_FAIL; + if (rtsContract.IsDynamicMethod(methodDescHandle, out _)) + { + // In heap dumps, trying to format the signature can fail + // in certain cases. + stringBuilder.Clear(); + TypeNameBuilder.AppendMethodInternal(_target, stringBuilder, methodDescHandle, TypeNameFormat.FormatNamespace | TypeNameFormat.FormatFullInst); + hr = HResults.S_OK; + } + else + { + string? fallbackNameString = _target.Contracts.DacStreams.StringFromEEAddress(methodDesc); + if (!string.IsNullOrEmpty(fallbackNameString)) + { + stringBuilder.Clear(); + stringBuilder.Append(fallbackNameString); + hr = HResults.S_OK; + } + else + { + TargetPointer modulePtr = rtsContract.GetModule(rtsContract.GetTypeHandle(rtsContract.GetMethodTable(methodDescHandle))); + Contracts.ModuleHandle module = _target.Contracts.Loader.GetModuleHandle(modulePtr); + string modulePath = _target.Contracts.Loader.GetPath(module); + stringBuilder.Clear(); + stringBuilder.Append(modulePath); + stringBuilder.Append("!Unknown"); + hr = HResults.S_OK; + } + } + } + + if (hr == HResults.S_OK) + { + CopyStringToTargetBuffer(name, count, pNeeded, stringBuilder.ToString()); + } + } + catch (System.Exception ex) + { + return ex.HResult; + } + + return hr; + } + public unsafe int GetMethodDescPtrFromFrame(ulong frameAddr, ulong* ppMD) => HResults.E_NOTIMPL; public unsafe int GetMethodDescPtrFromIP(ulong ip, ulong* ppMD) => HResults.E_NOTIMPL; public unsafe int GetMethodDescTransparencyData(ulong methodDesc, void* data) => HResults.E_NOTIMPL; diff --git a/src/native/managed/cdacreader/src/Legacy/SigFormat.cs b/src/native/managed/cdacreader/src/Legacy/SigFormat.cs new file mode 100644 index 00000000000000..965bb7edf99e8a --- /dev/null +++ b/src/native/managed/cdacreader/src/Legacy/SigFormat.cs @@ -0,0 +1,469 @@ +// 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 System.Xml.Linq; +using Microsoft.Diagnostics.DataContractReader.Contracts; +using Microsoft.Diagnostics.DataContractReader.Helpers; + +namespace Microsoft.Diagnostics.DataContractReader.Legacy +{ + internal static class SigFormat + { + private const uint IMAGE_CEE_CS_CALLCONV_MASK = 0xF; + private const uint IMAGE_CEE_CS_CALLCONV_VARARG = 0x5; + private const uint IMAGE_CEE_CS_CALLCONV_GENERIC = 0x10; + public static void AppendSigFormat(Target target, + StringBuilder stringBuilder, + ReadOnlySpan signature, + EcmaMetadataReader? metadata, + string? memberName, + string? className, + string? namespaceName, + ReadOnlySpan typeInstantiation, + ReadOnlySpan methodInstantiation, + bool CStringParmsOnly) + { + byte callConv = checked((byte)GetData(ref signature)); + + if ((callConv & IMAGE_CEE_CS_CALLCONV_GENERIC) != 0) + { + GetData(ref signature); // Ignore generic parameter count + } + int cArgs = (int)GetData(ref signature); + bool isVarArg = (callConv & IMAGE_CEE_CS_CALLCONV_MASK) == IMAGE_CEE_CS_CALLCONV_VARARG; + + if (!CStringParmsOnly) + { + AddTypeString(target, stringBuilder, ref signature, typeInstantiation, methodInstantiation, metadata); + stringBuilder.Append(' '); + if (!string.IsNullOrEmpty(namespaceName)) + { + stringBuilder.Append(namespaceName); + stringBuilder.Append('.'); + } + if (!string.IsNullOrEmpty(className)) + { + stringBuilder.Append(className); + stringBuilder.Append('.'); + } + if (!string.IsNullOrEmpty(memberName)) + { + stringBuilder.Append(memberName); + stringBuilder.Append('.'); + } + } + else + { + StringBuilder sbDummy = new StringBuilder(); + AddTypeString(target, sbDummy, ref signature, typeInstantiation, methodInstantiation, metadata); + } + + stringBuilder.Append('('); + for (int i = 0; i < cArgs; i++) + { + AddTypeString(target, stringBuilder, ref signature, typeInstantiation, methodInstantiation, metadata); + if (i != cArgs - 1) + stringBuilder.Append(", "); + } + + if (isVarArg) + { + if (cArgs > 0) + stringBuilder.Append(", "); + stringBuilder.Append("..."); + } + stringBuilder.Append(')'); + } + + private static Contracts.CorElementType GetElemType(ref ReadOnlySpan signature) + { + CorElementType result = (Contracts.CorElementType)signature[0]; + signature = signature.Slice(1); + return result; + } + + private static uint GetData(ref ReadOnlySpan signature) + { + byte headerByte = signature[0]; + if ((headerByte & 0x80) == 0) + { + signature = signature.Slice(1); + return headerByte; + } + else if ((headerByte & 0x40) == 0) + { + int result = ((headerByte & 0x3f) << 8) | signature[1]; + signature = signature.Slice(2); + return (uint)result; + } + else if ((headerByte & 0x20) == 0) + { + int result = ((headerByte & 0x1f) << 24) | (signature[1] << 16) | (signature[2] << 8) | signature[3]; + signature = signature.Slice(4); + return (uint)result; + } + throw new InvalidOperationException("Invalid signature format"); + } + + private static uint GetToken(ref ReadOnlySpan signature) + { + uint data = GetData(ref signature); + MetadataTable table; + switch (data & 3) + { + case 0: table = MetadataTable.TypeDef; break; + case 1: table = MetadataTable.TypeRef; break; + case 2: table = MetadataTable.TypeSpec; break; + default: throw new InvalidOperationException("Invalid signature format"); + } + + return EcmaMetadataReader.CreateToken(table, data >> 2); + } + + private static void AddTypeString(Target target, + StringBuilder stringBuilder, + ref ReadOnlySpan signature, + ReadOnlySpan typeInstantiation, + ReadOnlySpan methodInstantiation, + EcmaMetadataReader? metadata) + { + string _namespace; + string name; + EcmaMetadataCursor cursor; + + while (true) + { + switch (GetElemType(ref signature)) + { + case CorElementType.Void: stringBuilder.Append("Void"); return; + case CorElementType.Boolean: stringBuilder.Append("Boolean"); return; + case CorElementType.I: stringBuilder.Append("IntPtr"); return; + case CorElementType.U: stringBuilder.Append("UIntPtr"); return; + case CorElementType.I1: stringBuilder.Append("SByte"); return; + case CorElementType.U1: stringBuilder.Append("Byte"); return; + case CorElementType.I2: stringBuilder.Append("Int16"); return; + case CorElementType.U2: stringBuilder.Append("UInt16"); return; + case CorElementType.I4: stringBuilder.Append("Int32"); return; + case CorElementType.U4: stringBuilder.Append("UInt32"); return; + case CorElementType.I8: stringBuilder.Append("Int64"); return; + case CorElementType.U8: stringBuilder.Append("UInt64"); return; + case CorElementType.R4: stringBuilder.Append("Single"); return; + case CorElementType.R8: stringBuilder.Append("Double"); return; + case CorElementType.Char: stringBuilder.Append("Char"); return; + + case CorElementType.Object: stringBuilder.Append("System.Object"); return; + case CorElementType.String: stringBuilder.Append("System.String"); return; + + case CorElementType.ValueType: + case CorElementType.Class: + if (metadata == null) + throw new InvalidOperationException("Invalid signature without metadata reader"); + uint token = GetToken(ref signature); + cursor = metadata.GetCursor(token); + switch (EcmaMetadataReader.TokenToTable(token)) + { + case MetadataTable.TypeDef: + _namespace = metadata.GetColumnAsUtf8String(cursor, MetadataColumnIndex.TypeDef_TypeNamespace); + name = metadata.GetColumnAsUtf8String(cursor, MetadataColumnIndex.TypeDef_TypeNamespace); + break; + case MetadataTable.TypeRef: + _namespace = metadata.GetColumnAsUtf8String(cursor, MetadataColumnIndex.TypeRef_TypeNamespace); + name = metadata.GetColumnAsUtf8String(cursor, MetadataColumnIndex.TypeRef_TypeNamespace); + break; + default: + return; + } + + if (!string.IsNullOrEmpty(_namespace)) + { + stringBuilder.Append(_namespace); + stringBuilder.Append('.'); + } + stringBuilder.Append(name); + return; + + case CorElementType.Internal: + TargetPointer typeHandlePointer = target.ReadPointerFromSpan(signature); + IRuntimeTypeSystem runtimeTypeSystem = target.Contracts.RuntimeTypeSystem; + signature.Slice(target.PointerSize); + TypeHandle th = runtimeTypeSystem.GetTypeHandle(typeHandlePointer); + switch (runtimeTypeSystem.GetSignatureCorElementType(th)) + { + case CorElementType.FnPtr: + case CorElementType.Ptr: + stringBuilder.Append("System.UIntPtr"); + return; + case CorElementType.ValueType: + if (runtimeTypeSystem.HasTypeParam(th)) + { + th = runtimeTypeSystem.GetTypeParam(th); + } + break; + + case CorElementType.Byref: + case CorElementType.Array: + AddType(target, stringBuilder, th); + return; + } + + uint typeDefToken = runtimeTypeSystem.GetTypeDefToken(th); + TargetPointer modulePointer = target.Contracts.RuntimeTypeSystem.GetModule(th); + Contracts.ModuleHandle module = target.Contracts.Loader.GetModuleHandle(modulePointer); + EcmaMetadataReader internalTypeMetadata = target.Metadata.GetMetadata(module).EcmaMetadataReader; + cursor = internalTypeMetadata.GetCursor(typeDefToken); + _namespace = internalTypeMetadata.GetColumnAsUtf8String(cursor, MetadataColumnIndex.TypeDef_TypeNamespace); + name = internalTypeMetadata.GetColumnAsUtf8String(cursor, MetadataColumnIndex.TypeDef_TypeNamespace); + + if (!string.IsNullOrEmpty(_namespace)) + { + stringBuilder.Append(_namespace); + stringBuilder.Append('.'); + } + stringBuilder.Append(name); + return; + + case CorElementType.TypedByRef: + stringBuilder.Append("TypedReference"); + return; + + case CorElementType.Byref: + AddTypeString(target, stringBuilder, ref signature, typeInstantiation, methodInstantiation, metadata); + stringBuilder.Append(" ByRef"); + return; + + case CorElementType.Ptr: + AddTypeString(target, stringBuilder, ref signature, typeInstantiation, methodInstantiation, metadata); + stringBuilder.Append('*'); + return; + + case CorElementType.MVar: + uint mvarIndex = GetData(ref signature); + if (methodInstantiation.Length > (int)mvarIndex) + { + AddType(target, stringBuilder, methodInstantiation[(int)mvarIndex]); + } + else + { + stringBuilder.Append($"!!{mvarIndex}"); + } + return; + + case CorElementType.Var: + uint varIndex = GetData(ref signature); + if (methodInstantiation.Length > (int)varIndex) + { + AddType(target, stringBuilder, methodInstantiation[(int)varIndex]); + } + else + { + stringBuilder.Append($"!!{varIndex}"); + } + return; + + case CorElementType.GenericInst: + AddTypeString(target, stringBuilder, ref signature, typeInstantiation, methodInstantiation, metadata); + uint genericArgCount = GetData(ref signature); + stringBuilder.Append('<'); + for (uint i = 0; i < genericArgCount; i++) + { + if (i != 0) + stringBuilder.Append(','); + AddTypeString(target, stringBuilder, ref signature, typeInstantiation, methodInstantiation, metadata); + } + stringBuilder.Append('>'); + return; + + case CorElementType.SzArray: + AddTypeString(target, stringBuilder, ref signature, typeInstantiation, methodInstantiation, metadata); + stringBuilder.Append("[]"); + return; + + case CorElementType.Array: + AddTypeString(target, stringBuilder, ref signature, typeInstantiation, methodInstantiation, metadata); + stringBuilder.Append('['); + uint rank = GetData(ref signature); + for (uint i = 1; i < rank; i++) + { + stringBuilder.Append(','); + } + stringBuilder.Append(']'); + uint numSizes = GetData(ref signature); + for (uint i = 0; i < numSizes; i++) + { + GetData(ref signature); + } + uint numLoBounds = GetData(ref signature); + for (uint i = 0; i < numLoBounds; i++) + { + GetData(ref signature); + } + return; + + case CorElementType.FnPtr: + uint callConv = GetData(ref signature); + uint cArgs = GetData(ref signature); + AddTypeString(target, stringBuilder, ref signature, typeInstantiation, methodInstantiation, metadata); + stringBuilder.Append(" ("); + for (uint i = 0; i < cArgs; i++) + { + AddTypeString(target, stringBuilder, ref signature, typeInstantiation, methodInstantiation, metadata); + if (i != cArgs - 1) + stringBuilder.Append(", "); + } + if ((callConv & IMAGE_CEE_CS_CALLCONV_MASK) == IMAGE_CEE_CS_CALLCONV_VARARG) + { + if (cArgs > 0) + stringBuilder.Append(", "); + stringBuilder.Append("..."); + } + stringBuilder.Append(')'); + return; + + case CorElementType.CModOpt: + case CorElementType.CModReqd: + GetToken(ref signature); + break; + + default: + stringBuilder.Append("**UNKNOWN TYPE**"); + return; + } + } + } + + private static void AddType(Target target, StringBuilder stringBuilder, TypeHandle typeHandle) + { + IRuntimeTypeSystem runtimeTypeSystem = target.Contracts.RuntimeTypeSystem; + + if (typeHandle.IsNull) + stringBuilder.Append("**UNKNOWN TYPE**"); + CorElementType corElementType = runtimeTypeSystem.GetSignatureCorElementType(typeHandle); + if (corElementType == CorElementType.ValueType && runtimeTypeSystem.HasTypeParam(typeHandle)) + { + typeHandle = runtimeTypeSystem.GetTypeParam(typeHandle); + } + + switch (corElementType) + { + case CorElementType.Void: stringBuilder.Append("Void"); return; + case CorElementType.Boolean: stringBuilder.Append("Boolean"); return; + case CorElementType.I: stringBuilder.Append("IntPtr"); return; + case CorElementType.U: stringBuilder.Append("UIntPtr"); return; + case CorElementType.I1: stringBuilder.Append("SByte"); return; + case CorElementType.U1: stringBuilder.Append("Byte"); return; + case CorElementType.I2: stringBuilder.Append("Int16"); return; + case CorElementType.U2: stringBuilder.Append("UInt16"); return; + case CorElementType.I4: stringBuilder.Append("Int32"); return; + case CorElementType.U4: stringBuilder.Append("UInt32"); return; + case CorElementType.I8: stringBuilder.Append("Int64"); return; + case CorElementType.U8: stringBuilder.Append("UInt64"); return; + case CorElementType.R4: stringBuilder.Append("Single"); return; + case CorElementType.R8: stringBuilder.Append("Double"); return; + case CorElementType.Char: stringBuilder.Append("Char"); return; + + case CorElementType.Object: stringBuilder.Append("System.Object"); return; + case CorElementType.String: stringBuilder.Append("System.String"); return; + + case CorElementType.ValueType: + case CorElementType.Class: + uint typeDefToken = runtimeTypeSystem.GetTypeDefToken(typeHandle); + TargetPointer modulePointer = target.Contracts.RuntimeTypeSystem.GetModule(typeHandle); + Contracts.ModuleHandle module = target.Contracts.Loader.GetModuleHandle(modulePointer); + EcmaMetadataReader metadata = target.Metadata.GetMetadata(module).EcmaMetadataReader; + EcmaMetadataCursor cursor = metadata.GetCursor(typeDefToken); + string _namespace = metadata.GetColumnAsUtf8String(cursor, MetadataColumnIndex.TypeDef_TypeNamespace); + string name = metadata.GetColumnAsUtf8String(cursor, MetadataColumnIndex.TypeDef_TypeNamespace); + + if (!string.IsNullOrEmpty(_namespace)) + { + stringBuilder.Append(_namespace); + stringBuilder.Append('.'); + } + stringBuilder.Append(name); + + ReadOnlySpan instantiation = runtimeTypeSystem.GetInstantiation(typeHandle); + if (instantiation.Length > 0) + { + stringBuilder.Append('<'); + for (int i = 0; i < instantiation.Length; i++) + { + if (i != 0) + stringBuilder.Append(','); + AddType(target, stringBuilder, instantiation[i]); + } + stringBuilder.Append('>'); + } + + return; + + case CorElementType.TypedByRef: + stringBuilder.Append("TypedReference"); + return; + + case CorElementType.Byref: + AddType(target, stringBuilder, runtimeTypeSystem.GetTypeParam(typeHandle)); + stringBuilder.Append(" ByRef"); + return; + + case CorElementType.Ptr: + AddType(target, stringBuilder, runtimeTypeSystem.GetTypeParam(typeHandle)); + stringBuilder.Append('*'); + return; + + case CorElementType.MVar: + case CorElementType.Var: + runtimeTypeSystem.IsGenericVariable(typeHandle, out TargetPointer genericVariableModulePointer, out uint typeVarToken); + Contracts.ModuleHandle genericVariableModule = target.Contracts.Loader.GetModuleHandle(genericVariableModulePointer); + EcmaMetadataReader genericVariableMetadata = target.Metadata.GetMetadata(genericVariableModule).EcmaMetadataReader; + EcmaMetadataCursor genericVariableCursor = genericVariableMetadata.GetCursor(typeVarToken); + stringBuilder.Append(genericVariableMetadata.GetColumnAsUtf8String(genericVariableCursor, MetadataColumnIndex.GenericParam_Name)); + return; + + case CorElementType.SzArray: + AddType(target, stringBuilder, runtimeTypeSystem.GetTypeParam(typeHandle)); + stringBuilder.Append("[]"); + return; + + case CorElementType.Array: + AddType(target, stringBuilder, runtimeTypeSystem.GetTypeParam(typeHandle)); + stringBuilder.Append('['); + + runtimeTypeSystem.IsArray(typeHandle, out uint rank); + for (uint i = 1; i < rank; i++) + { + stringBuilder.Append(','); + } + stringBuilder.Append(']'); + return; + + case CorElementType.FnPtr: + runtimeTypeSystem.IsFunctionPointer(typeHandle, out ReadOnlySpan retAndArgTypes, out byte callConv); + AddType(target, stringBuilder, retAndArgTypes[0]); + stringBuilder.Append(" ("); + for (int i = 1; i < retAndArgTypes.Length; i++) + { + AddType(target, stringBuilder, retAndArgTypes[i]); + if (i != retAndArgTypes.Length - 1) + stringBuilder.Append(", "); + } + if ((callConv & IMAGE_CEE_CS_CALLCONV_MASK) == IMAGE_CEE_CS_CALLCONV_VARARG) + { + if (retAndArgTypes.Length > 1) + stringBuilder.Append(", "); + stringBuilder.Append("..."); + } + stringBuilder.Append(')'); + return; + + default: + stringBuilder.Append("**UNKNOWN TYPE**"); + return; + } + } + } +} diff --git a/src/native/managed/cdacreader/src/Legacy/TypeNameBuilder.cs b/src/native/managed/cdacreader/src/Legacy/TypeNameBuilder.cs index 510367bd12480f..f282ee6858ecf8 100644 --- a/src/native/managed/cdacreader/src/Legacy/TypeNameBuilder.cs +++ b/src/native/managed/cdacreader/src/Legacy/TypeNameBuilder.cs @@ -17,6 +17,7 @@ internal enum TypeNameFormat FormatAngleBrackets = 4, FormatAssembly = 8, FormatGenericParam = 16, + FormatSignature = 32, } internal struct TypeNameBuilder @@ -51,15 +52,133 @@ private TypeNameBuilder(StringBuilder typeString, Target target, TypeNameFormat State = ParseState.Start; } + public static void AppendMethodInternal(Target target, StringBuilder stringBuilder, Contracts.MethodDescHandle method, TypeNameFormat format) + { + AppendMethodImpl(target, stringBuilder, method, default, format); + } + + public static void AppendMethodImpl(Target target, StringBuilder stringBuilder, Contracts.MethodDescHandle method, ReadOnlySpan typeInstantiation, TypeNameFormat format) + { + IRuntimeTypeSystem runtimeTypeSystem = target.Contracts.RuntimeTypeSystem; + ILoader loader = target.Contracts.Loader; + ReadOnlySpan methodNameSpan; + TypeHandle th = default; + Contracts.ModuleHandle module = default; + + bool isDynamicMethod = runtimeTypeSystem.IsDynamicMethod(method, out methodNameSpan); + if (isDynamicMethod) + { + if (runtimeTypeSystem.IsLCGMethod(method)) + { + stringBuilder.Append("DynamicClass"); + } + else if (runtimeTypeSystem.IsILStub(method)) + { + stringBuilder.Append("ILStubClass"); + } + } + else + { + th = runtimeTypeSystem.GetTypeHandle(runtimeTypeSystem.GetMethodTable(method)); + AppendType(target, stringBuilder, th, typeInstantiation, format); + } + + stringBuilder.Append('.'); + + if (isDynamicMethod) + { + stringBuilder.Append(Encoding.UTF8.GetString(methodNameSpan)); + } + else if (runtimeTypeSystem.IsArrayMethod(method, out ArrayFunctionType functionType)) + { + string arrayName; + + switch (functionType) + { + case ArrayFunctionType.Set: + arrayName = "Set"; + break; + case ArrayFunctionType.Get: + arrayName = "Get"; + break; + case ArrayFunctionType.Address: + arrayName = "Address"; + break; + case ArrayFunctionType.Constructor: + arrayName = ".ctor"; + break; + default: + throw new ArgumentException(nameof(method)); + } + + stringBuilder.Append(arrayName); + } + else + { + module = loader.GetModuleHandle(runtimeTypeSystem.GetModule(th)); + EcmaMetadataReader reader = target.Metadata.GetMetadata(module).EcmaMetadataReader; + var cursor = reader.GetCursor(runtimeTypeSystem.GetMethodToken(method)); + stringBuilder.Append(reader.GetColumnAsUtf8String(cursor, MetadataColumnIndex.MethodDef_Name)); + } + + if (format.HasFlag(TypeNameFormat.FormatSignature)) + { + ReadOnlySpan signature; + EcmaMetadataReader? reader = default; + if (!runtimeTypeSystem.IsStoredSigMethodDesc(method, out signature)) + { + reader = target.Metadata.GetMetadata(module).EcmaMetadataReader; + var cursor = reader.GetCursor(runtimeTypeSystem.GetMethodToken(method)); + signature = reader.GetColumnAsBlob(cursor, MetadataColumnIndex.MethodDef_Signature); + } + + ReadOnlySpan typeInstantiationSigFormat = default; + if (!th.IsNull) + { + typeInstantiationSigFormat = runtimeTypeSystem.GetInstantiation(th); + } + + SigFormat.AppendSigFormat(target, stringBuilder, signature, reader, null, null, null, typeInstantiationSigFormat, runtimeTypeSystem.GetGenericMethodInstantiation(method), true); + } + } + + public static TypeHandle GetExactOwningType(IRuntimeTypeSystem runtimeTypeSystem, TypeHandle possiblyDerivedType, MethodDescHandle method) + { + TypeHandle approxOwner = runtimeTypeSystem.GetTypeHandle(runtimeTypeSystem.GetMethodTable(method)); + + uint typeDefTokenOfOwner = runtimeTypeSystem.GetTypeDefToken(approxOwner); + TargetPointer moduleOfOwner = runtimeTypeSystem.GetModule(approxOwner); + + do + { + uint typeDefTokenOfPossiblyDerivedType = runtimeTypeSystem.GetTypeDefToken(possiblyDerivedType); + TargetPointer moduleOfPossiblyDerivedType = runtimeTypeSystem.GetModule(possiblyDerivedType); + + if ((typeDefTokenOfOwner == typeDefTokenOfPossiblyDerivedType) && (moduleOfOwner == moduleOfPossiblyDerivedType)) + { + return possiblyDerivedType; + } + + TargetPointer parentTypePointer = runtimeTypeSystem.GetParentMethodTable(possiblyDerivedType); + if (parentTypePointer.Value == 0) + throw new InvalidOperationException("Invalid parent type"); + + // TODO(cdac) - Consider adding infinite loop detection here + possiblyDerivedType = runtimeTypeSystem.GetTypeHandle(parentTypePointer); + } while (true); + } + 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); diff --git a/src/native/managed/cdacreader/src/Target.cs b/src/native/managed/cdacreader/src/Target.cs index 30478662048588..e6721478653ade 100644 --- a/src/native/managed/cdacreader/src/Target.cs +++ b/src/native/managed/cdacreader/src/Target.cs @@ -283,6 +283,23 @@ private static bool TryRead(ulong address, bool isLittleEndian, Reader reader : T.TryReadBigEndian(buffer, !IsSigned(), out value); } + private static T Read(ReadOnlySpan bytes, bool isLittleEndian) where T : unmanaged, IBinaryInteger, IMinMaxValue + { + if (sizeof(T) != bytes.Length) + throw new ArgumentException(nameof(bytes)); + + T value; + if (isLittleEndian) + { + T.TryReadLittleEndian(bytes, !IsSigned(), out value); + } + else + { + T.TryReadBigEndian(bytes, !IsSigned(), out value); + } + return value; + } + public void ReadBuffer(ulong address, Span buffer) { if (!TryReadBuffer(address, buffer)) @@ -308,6 +325,18 @@ public TargetPointer ReadPointer(ulong address) return pointer; } + public TargetPointer ReadPointerFromSpan(ReadOnlySpan bytes) + { + if (_config.PointerSize == sizeof(uint)) + { + return new TargetPointer(Read(bytes.Slice(0, sizeof(uint)), _config.IsLittleEndian)); + } + else + { + return new TargetPointer(Read(bytes.Slice(0, sizeof(ulong)), _config.IsLittleEndian)); + } + } + 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 From 01d3d244f3fa45e1e2ac1b13e300f2fd536203e0 Mon Sep 17 00:00:00 2001 From: David Wrighton Date: Wed, 7 Aug 2024 10:19:51 -0700 Subject: [PATCH 30/38] Remove default parameter from AppendMethodInternal --- src/coreclr/vm/typestring.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/coreclr/vm/typestring.h b/src/coreclr/vm/typestring.h index 869039ffe5d703..f3bd23bfc6707b 100644 --- a/src/coreclr/vm/typestring.h +++ b/src/coreclr/vm/typestring.h @@ -184,7 +184,7 @@ class TypeString // Append a representation of the method m to the string s // The following flags in the FormatFlags argument are significant: FormatNamespace FormatFullInst FormatAssembly FormatSignature FormatNoVersion - static void AppendMethodInternal(SString& s, MethodDesc *pMD, const DWORD format = FormatNamespace|FormatSignature|FormatStubInfo); + static void AppendMethodInternal(SString& s, MethodDesc *pMD, const DWORD format); // Append the field name and generic instantiation info. static void AppendField(SString& s, FieldDesc *pFD, Instantiation typeInstantiation, const DWORD format = FormatNamespace); From 0c7a254aec8f14e64580fb3db0623e9de9d6daf9 Mon Sep 17 00:00:00 2001 From: David Wrighton Date: Wed, 7 Aug 2024 19:50:56 -0700 Subject: [PATCH 31/38] First stab at the actual contract implementations for the MethodDesc reading needs --- .../design/datacontracts/RuntimeTypeSystem.md | 14 +- .../debug/runtimeinfo/datadescriptor.h | 22 ++ src/coreclr/vm/method.hpp | 24 ++ .../managed/cdacreader/src/Constants.cs | 2 + .../cdacreader/src/Contracts/Loader_1.cs | 2 + .../src/Contracts/RuntimeTypeSystem.cs | 58 +---- .../src/Contracts/RuntimeTypeSystem_1.cs | 243 +++++++++++++++++- .../managed/cdacreader/src/Data/MethodDesc.cs | 49 ++++ .../cdacreader/src/Data/MethodDescChunk.cs | 2 + src/native/managed/cdacreader/src/DataType.cs | 3 + 10 files changed, 349 insertions(+), 70 deletions(-) diff --git a/docs/design/datacontracts/RuntimeTypeSystem.md b/docs/design/datacontracts/RuntimeTypeSystem.md index ac4a1cd6a7eaae..5693ab205ce592 100644 --- a/docs/design/datacontracts/RuntimeTypeSystem.md +++ b/docs/design/datacontracts/RuntimeTypeSystem.md @@ -88,19 +88,7 @@ struct MethodDescHandle { // no public properties or 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 - // + - 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 TargetPointer Address { get; } } ``` diff --git a/src/coreclr/debug/runtimeinfo/datadescriptor.h b/src/coreclr/debug/runtimeinfo/datadescriptor.h index e301dc57ee2533..bc9f6ffb00946d 100644 --- a/src/coreclr/debug/runtimeinfo/datadescriptor.h +++ b/src/coreclr/debug/runtimeinfo/datadescriptor.h @@ -296,6 +296,7 @@ CDAC_TYPE_INDETERMINATE(MethodDesc) CDAC_TYPE_FIELD(MethodDesc, /*uint8*/, ChunkIndex, cdac_data::ChunkIndex) CDAC_TYPE_FIELD(MethodDesc, /*uint16*/, Slot, cdac_data::Slot) CDAC_TYPE_FIELD(MethodDesc, /*uint16*/, Flags, cdac_data::Flags) +CDAC_TYPE_FIELD(MethodDesc, /*uint16*/, Flags3AndTokenRemainder, cdac_data::Flags3AndTokenRemainder) CDAC_TYPE_END(MethodDesc) CDAC_TYPE_BEGIN(MethodDescChunk) @@ -304,8 +305,28 @@ CDAC_TYPE_FIELD(MethodDescChunk, /*pointer*/, MethodTable, cdac_data::Next) CDAC_TYPE_FIELD(MethodDescChunk, /*uint8*/, Size, cdac_data::Size) CDAC_TYPE_FIELD(MethodDescChunk, /*uint8*/, Count, cdac_data::Count) +CDAC_TYPE_FIELD(MethodDescChunk, /*uint16*/, FlagsAndTokenRange, cdac_data::FlagsAndTokenRange) CDAC_TYPE_END(MethodDescChunk) +CDAC_TYPE_BEGIN(InstantiatedMethodDesc) +CDAC_TYPE_INDETERMINATE(InstantiatedMethodDesc) +CDAC_TYPE_FIELD(InstantiatedMethodDesc, /*pointer*/, PerInstInfo, cdac_data::PerInstInfo) +CDAC_TYPE_FIELD(InstantiatedMethodDesc, /*uint16*/, Flags2, cdac_data::Flags2) +CDAC_TYPE_FIELD(InstantiatedMethodDesc, /*uint16*/, NumGenericArgs, cdac_data::NumGenericArgs) +CDAC_TYPE_END(InstantiatedMethodDesc) + +CDAC_TYPE_BEGIN(StoredSigMethodDesc) +CDAC_TYPE_INDETERMINATE(StoredSigMethodDesc) +CDAC_TYPE_FIELD(StoredSigMethodDesc, /*pointer*/, Sig, cdac_data::Sig) +CDAC_TYPE_FIELD(StoredSigMethodDesc, /*uint32*/, cSig, cdac_data::cSig) +CDAC_TYPE_FIELD(StoredSigMethodDesc, /*uint32*/, ExtendedFlags, cdac_data::ExtendedFlags) +CDAC_TYPE_END(StoredSigMethodDesc) + +CDAC_TYPE_BEGIN(DynamicMethodDesc) +CDAC_TYPE_INDETERMINATE(DynamicMethodDesc) +CDAC_TYPE_FIELD(DynamicMethodDesc, /*pointer*/, MethodName, cdac_data::MethodName) +CDAC_TYPE_END(DynamicMethodDesc) + CDAC_TYPES_END() CDAC_GLOBALS_BEGIN() @@ -313,6 +334,7 @@ CDAC_GLOBAL_POINTER(AppDomain, &AppDomain::m_pTheAppDomain) CDAC_GLOBAL_POINTER(ThreadStore, &ThreadStore::s_pThreadStore) CDAC_GLOBAL_POINTER(FinalizerThread, &::g_pFinalizerThread) CDAC_GLOBAL_POINTER(GCThread, &::g_pSuspensionThread) +CDAC_GLOBAL(MethodDescTokenRemainderBitCount, uint8, METHOD_TOKEN_REMAINDER_BIT_COUNT) #if FEATURE_EH_FUNCLETS CDAC_GLOBAL(FeatureEHFunclets, uint8, 1) #else diff --git a/src/coreclr/vm/method.hpp b/src/coreclr/vm/method.hpp index 628eb39864b7e7..a38902b38e6fab 100644 --- a/src/coreclr/vm/method.hpp +++ b/src/coreclr/vm/method.hpp @@ -1917,6 +1917,7 @@ template<> struct cdac_data static constexpr size_t ChunkIndex = offsetof(MethodDesc, m_chunkIndex); static constexpr size_t Slot = offsetof(MethodDesc, m_wSlotNumber); static constexpr size_t Flags = offsetof(MethodDesc, m_wFlags); + static constexpr size_t Flags3AndTokenRemainder = offsetof(MethodDesc, m_wFlags3AndTokenRemainder); }; #ifndef DACCESS_COMPILE @@ -2348,6 +2349,7 @@ struct cdac_data static constexpr size_t Next = offsetof(MethodDescChunk, m_next); static constexpr size_t Size = offsetof(MethodDescChunk, m_size); static constexpr size_t Count = offsetof(MethodDescChunk, m_count); + static constexpr size_t FlagsAndTokenRange = offsetof(MethodDescChunk, m_flagsAndTokenRange); }; inline int MethodDesc::GetMethodDescChunkIndex() const @@ -2424,6 +2426,15 @@ class StoredSigMethodDesc : public MethodDesc #ifdef DACCESS_COMPILE void EnumMemoryRegions(CLRDataEnumMemoryFlags flags); #endif + template friend struct ::cdac_data; +}; + +template<> +struct cdac_data +{ + static constexpr size_t Sig = offsetof(StoredSigMethodDesc, m_pSig); + static constexpr size_t cSig = offsetof(StoredSigMethodDesc, m_cSig); + static constexpr size_t ExtendedFlags = offsetof(StoredSigMethodDesc, m_dwExtendedFlags); }; //----------------------------------------------------------------------- @@ -2665,8 +2676,14 @@ class DynamicMethodDesc : public StoredSigMethodDesc // following implementations defined in DynamicMethod.cpp // void Destroy(); + template friend struct ::cdac_data; }; +template<> +struct cdac_data +{ + static constexpr size_t MethodName = offsetof(DynamicMethodDesc, m_pszMethodName); +}; class ArrayMethodDesc : public StoredSigMethodDesc { @@ -3418,7 +3435,14 @@ class InstantiatedMethodDesc final : public MethodDesc MethodDesc* pSharedMDescForStub, Instantiation methodInst, BOOL getSharedNotStub); + template friend struct ::cdac_data; +}; +template<> struct cdac_data +{ + static constexpr size_t PerInstInfo = offsetof(InstantiatedMethodDesc, m_pPerInstInfo); + static constexpr size_t Flags2 = offsetof(InstantiatedMethodDesc, m_wFlags2); + static constexpr size_t NumGenericArgs = offsetof(InstantiatedMethodDesc, m_wNumGenericArgs); }; inline PTR_MethodTable MethodDesc::GetMethodTable() const diff --git a/src/native/managed/cdacreader/src/Constants.cs b/src/native/managed/cdacreader/src/Constants.cs index bd29ab0aeb8d92..a30c1b02a35607 100644 --- a/src/native/managed/cdacreader/src/Constants.cs +++ b/src/native/managed/cdacreader/src/Constants.cs @@ -35,5 +35,7 @@ internal static class Globals internal const string SyncTableEntries = nameof(SyncTableEntries); internal const string ArrayBoundsZero = nameof(ArrayBoundsZero); + + internal const string MethodDescTokenRemainderBitCount = nameof(MethodDescTokenRemainderBitCount); } } diff --git a/src/native/managed/cdacreader/src/Contracts/Loader_1.cs b/src/native/managed/cdacreader/src/Contracts/Loader_1.cs index 2c4aa6677f85c0..f137484af72e17 100644 --- a/src/native/managed/cdacreader/src/Contracts/Loader_1.cs +++ b/src/native/managed/cdacreader/src/Contracts/Loader_1.cs @@ -97,4 +97,6 @@ ModuleLookupTables ILoader.GetLookupTables(ModuleHandle handle) module.TypeDefToMethodTableMap, module.TypeRefToMethodTableMap); } + + } diff --git a/src/native/managed/cdacreader/src/Contracts/RuntimeTypeSystem.cs b/src/native/managed/cdacreader/src/Contracts/RuntimeTypeSystem.cs index 49edc036e05224..9b69dac7500a9d 100644 --- a/src/native/managed/cdacreader/src/Contracts/RuntimeTypeSystem.cs +++ b/src/native/managed/cdacreader/src/Contracts/RuntimeTypeSystem.cs @@ -63,64 +63,14 @@ internal MethodDescHandle(TargetPointer address) } internal TargetPointer Address { get; } - - internal bool IsNull => Address == 0; -} - -internal enum CorElementType -{ - Void = 1, - Boolean = 2, - Char = 3, - I1 = 4, - U1 = 5, - I2 = 6, - U2 = 7, - I4 = 8, - U4 = 9, - I8 = 0xa, - U8 = 0xb, - R4 = 0xc, - R8 = 0xd, - String = 0xe, - Ptr = 0xf, - Byref = 0x10, - ValueType = 0x11, - Class = 0x12, - Var = 0x13, - Array = 0x14, - GenericInst = 0x15, - TypedByRef = 0x16, - I = 0x18, - U = 0x19, - FnPtr = 0x1b, - Object = 0x1c, - SzArray = 0x1d, - MVar = 0x1e, - CModReqd = 0x1f, - CModOpt = 0x20, - Internal = 0x21, - Sentinel = 0x41, -} - -internal readonly struct MethodDescHandle -{ - internal MethodDescHandle(TargetPointer address) - { - Address = address; - } - - internal TargetPointer Address { get; } - - internal bool IsNull => Address == 0; } public enum ArrayFunctionType { - Get, - Set, - Address, - Constructor + Get = 0, + Set = 1, + Address = 2, + Constructor = 3 } internal interface IRuntimeTypeSystem : IContract diff --git a/src/native/managed/cdacreader/src/Contracts/RuntimeTypeSystem_1.cs b/src/native/managed/cdacreader/src/Contracts/RuntimeTypeSystem_1.cs index 8a690f6a79cf36..cd58f2f5960e14 100644 --- a/src/native/managed/cdacreader/src/Contracts/RuntimeTypeSystem_1.cs +++ b/src/native/managed/cdacreader/src/Contracts/RuntimeTypeSystem_1.cs @@ -7,6 +7,8 @@ using Microsoft.Diagnostics.DataContractReader.Data; using Microsoft.Diagnostics.DataContractReader.Contracts.RuntimeTypeSystem_1_NS; using System.Diagnostics; +using System.Text; +using System.Reflection; namespace Microsoft.Diagnostics.DataContractReader.Contracts; @@ -71,19 +73,52 @@ internal enum TypeHandleBits ValidMask = 2, } + internal enum MethodDescClassification + { + mcIL = 0, // IL + mcFCall = 1, // FCall (also includes tlbimped ctor, Delegate ctor) + mcNDirect = 2, // N/Direct + mcEEImpl = 3, // special method; implementation provided by EE (like Delegate Invoke) + mcArray = 4, // Array ECall + mcInstantiated = 5, // Instantiated generic methods, including descriptors + // for both shared and unshared code (see InstantiatedMethodDesc) + mcComInterop = 6, + mcDynamic = 7, // for method desc with no metadata behind + } + [Flags] internal enum MethodDescFlags : ushort { + ClassificationMask = 0x7, HasNonVtableSlot = 0x0008, } + internal enum InstantiatedMethodDescFlags2 : ushort + { + KindMask = 0x07, + GenericMethodDefinition = 0x01, + UnsharedMethodInstantiation = 0x02, + SharedMethodInstantiation = 0x03, + WrapperStubWithInstantiations = 0x04, + } + + [Flags] + internal enum DynamicMethodDescExtendedFlags : uint + { + IsLCGMethod = 0x00004000, + IsILStub = 0x00008000, + } + internal struct MethodDesc { private readonly Data.MethodDesc _desc; private readonly Data.MethodDescChunk _chunk; + private readonly Target _target; + internal TargetPointer Address { get; init; } - internal MethodDesc(TargetPointer methodDescPointer, Data.MethodDesc desc, Data.MethodDescChunk chunk) + internal MethodDesc(Target target, TargetPointer methodDescPointer, Data.MethodDesc desc, Data.MethodDescChunk chunk) { + _target = target; _desc = desc; _chunk = chunk; Address = methodDescPointer; @@ -91,6 +126,24 @@ internal MethodDesc(TargetPointer methodDescPointer, Data.MethodDesc desc, Data. public TargetPointer MethodTable => _chunk.MethodTable; public ushort Slot => _desc.Slot; + public uint Token + { + get + { + int tokenRemainderBitCount = _target.ReadGlobal(Constants.Globals.MethodDescTokenRemainderBitCount); + int tokenRangeBitCount = 24 - tokenRemainderBitCount; + uint allRidBitsSet = 0xFFFFFF; + uint tokenRemainderMask = allRidBitsSet >> tokenRangeBitCount; + uint tokenRangeMask = allRidBitsSet >> tokenRemainderBitCount; + + uint tokenRemainder = (uint)(_desc.Flags3AndTokenRemainder & tokenRemainderMask); + uint tokenRange = ((uint)(_chunk.FlagsAndTokenRange & tokenRangeMask)) << tokenRemainderBitCount; + + return 0x06000000 | tokenRange | tokenRemainder; + } + } + + public MethodDescClassification Classification => (MethodDescClassification)((int)_desc.Flags & (int)MethodDescFlags.ClassificationMask); } internal RuntimeTypeSystem_1(Target target, TargetPointer freeObjectMethodTablePointer, ulong methodDescAlignment) @@ -473,7 +526,7 @@ public MethodDescHandle GetMethodDescHandle(TargetPointer methodDescPointer) { throw new InvalidOperationException("cached MethodDesc data but not its containing MethodDescChunk"); } - MethodDesc validatedMethodDesc = new MethodDesc(methodDescPointer, methodDescData, methodDescChunkData); + MethodDesc validatedMethodDesc = new MethodDesc(_target, methodDescPointer, methodDescData, methodDescChunkData); _ = _methodDescs.TryAdd(methodDescPointer, validatedMethodDesc); return new MethodDescHandle(methodDescPointer); } @@ -487,10 +540,194 @@ public MethodDescHandle GetMethodDescHandle(TargetPointer methodDescPointer) Data.MethodDescChunk validatedMethodDescChunkData = _target.ProcessedData.GetOrAdd(methodDescChunkPointer); Data.MethodDesc validatedMethodDescData = _target.ProcessedData.GetOrAdd(methodDescPointer); - MethodDesc trustedMethodDescF = new MethodDesc(methodDescPointer, validatedMethodDescData, validatedMethodDescChunkData); + MethodDesc trustedMethodDescF = new MethodDesc(_target, methodDescPointer, validatedMethodDescData, validatedMethodDescChunkData); _ = _methodDescs.TryAdd(methodDescPointer, trustedMethodDescF); return new MethodDescHandle(methodDescPointer); } public TargetPointer GetMethodTable(MethodDescHandle methodDescHandle) => _methodDescs[methodDescHandle.Address].MethodTable; + + public bool IsGenericMethodDefinition(MethodDescHandle methodDescHandle) + { + MethodDesc methodDesc = _methodDescs[methodDescHandle.Address]; + + if (methodDesc.Classification != MethodDescClassification.mcInstantiated) + return false; + + Data.InstantiatedMethodDesc instantiatedMethodDesc = _target.ProcessedData.GetOrAdd(methodDescHandle.Address); + return ((int)instantiatedMethodDesc.Flags2 & (int)InstantiatedMethodDescFlags2.KindMask) == (int)InstantiatedMethodDescFlags2.GenericMethodDefinition; + } + + public ReadOnlySpan GetGenericMethodInstantiation(MethodDescHandle methodDescHandle) + { + MethodDesc methodDesc = _methodDescs[methodDescHandle.Address]; + + if (methodDesc.Classification != MethodDescClassification.mcInstantiated) + return default; + + return _target.ProcessedData.GetOrAdd (methodDescHandle.Address).TypeHandles; + } + + private class MethodInstantiation : IData + { + public static MethodInstantiation Create(Target target, TargetPointer address) => new MethodInstantiation(target, address); + + public TypeHandle[] TypeHandles { get; } + private MethodInstantiation(Target target, TargetPointer methodDescPointer) + { + RuntimeTypeSystem_1 rts = (RuntimeTypeSystem_1)target.Contracts.RuntimeTypeSystem; + Data.InstantiatedMethodDesc instantiatedMethodDesc = target.ProcessedData.GetOrAdd(methodDescPointer); + + int numGenericArgs = instantiatedMethodDesc.NumGenericArgs; + TargetPointer perInstInfo = instantiatedMethodDesc.PerInstInfo; + if (perInstInfo == TargetPointer.Null) + { + numGenericArgs = 0; + } + + TypeHandles = new TypeHandle[numGenericArgs]; + for (int i = 0; i < numGenericArgs; i++) + { + TypeHandles[i] = rts.GetTypeHandle(target.ReadPointer(perInstInfo + (ulong)target.PointerSize * (ulong)i)); + } + } + } + + public uint GetMethodToken(MethodDescHandle methodDescHandle) + { + MethodDesc methodDesc = _methodDescs[methodDescHandle.Address]; + return methodDesc.Token; + } + + public bool IsArrayMethod(MethodDescHandle methodDescHandle, out ArrayFunctionType functionType) + { + MethodDesc methodDesc = _methodDescs[methodDescHandle.Address]; + + if (methodDesc.Classification != MethodDescClassification.mcArray) + { + functionType = default; + return false; + } + + int arrayMethodIndex = methodDesc.Slot - GetNumVtableSlots(GetTypeHandle(methodDesc.MethodTable)); + + functionType = arrayMethodIndex switch + { + 0 => ArrayFunctionType.Get, + 1 => ArrayFunctionType.Set, + 2 => ArrayFunctionType.Address, + > 3 => ArrayFunctionType.Constructor, + _ => throw new InvalidOperationException() + }; + + return true; + } + + public bool IsDynamicMethod(MethodDescHandle methodDescHandle, out ReadOnlySpan methodName) + { + MethodDesc methodDesc = _methodDescs[methodDescHandle.Address]; + + if (methodDesc.Classification != MethodDescClassification.mcDynamic) + { + methodName = default; + return false; + } + + methodName = _target.ProcessedData.GetOrAdd(methodDescHandle.Address).MethodName; + return true; + } + + private class DynamicMethodDesc : IData + { + public static DynamicMethodDesc Create(Target target, TargetPointer address) => new DynamicMethodDesc(target, address); + + public byte[] MethodName { get; } + public DynamicMethodDescExtendedFlags ExtendedFlags { get; } + private DynamicMethodDesc(Target target, TargetPointer methodDescPointer) + { + List nameBytes = new(); + Data.DynamicMethodDesc dynamicMethodDesc = target.ProcessedData.GetOrAdd(methodDescPointer); + + if (dynamicMethodDesc.MethodName != TargetPointer.Null) + { + TargetPointer currentNameAddress = dynamicMethodDesc.MethodName; + do + { + byte nameByte = target.Read(currentNameAddress); + + if (nameByte == 0) + break; + + nameBytes.Add(nameByte); + currentNameAddress++; + } while (true); + + MethodName = nameBytes.ToArray(); + } + else + { + MethodName = System.Array.Empty(); + } + + Data.StoredSigMethodDesc storedSigMethodDesc = target.ProcessedData.GetOrAdd(methodDescPointer); + ExtendedFlags = (DynamicMethodDescExtendedFlags)storedSigMethodDesc.ExtendedFlags; + } + } + + public bool IsStoredSigMethodDesc(MethodDescHandle methodDescHandle, out ReadOnlySpan signature) + { + MethodDesc methodDesc = _methodDescs[methodDescHandle.Address]; + + switch (methodDesc.Classification) + { + case MethodDescClassification.mcDynamic: + case MethodDescClassification.mcEEImpl: + case MethodDescClassification.mcArray: + break; // These have stored sigs + + default: + signature = default; + return false; + } + + signature = _target.ProcessedData.GetOrAdd(methodDescHandle.Address).Signature; + return true; + } + + private class StoredSigMethodDesc: IData + { + public static StoredSigMethodDesc Create(Target target, TargetPointer address) => new StoredSigMethodDesc(target, address); + + public byte[] Signature { get; } + private StoredSigMethodDesc(Target target, TargetPointer methodDescPointer) + { + Data.StoredSigMethodDesc storedSigMethodDesc = target.ProcessedData.GetOrAdd(methodDescPointer); + Signature = new byte[storedSigMethodDesc.cSig]; + target.ReadBuffer(storedSigMethodDesc.Sig, Signature.AsSpan()); + } + } + + public bool IsLCGMethod(MethodDescHandle methodDescHandle) + { + MethodDesc methodDesc = _methodDescs[methodDescHandle.Address]; + + if (methodDesc.Classification != MethodDescClassification.mcDynamic) + { + return false; + } + + return _target.ProcessedData.GetOrAdd(methodDescHandle.Address).ExtendedFlags.HasFlag(DynamicMethodDescExtendedFlags.IsLCGMethod); + } + + public bool IsILStub(MethodDescHandle methodDescHandle) + { + MethodDesc methodDesc = _methodDescs[methodDescHandle.Address]; + + if (methodDesc.Classification != MethodDescClassification.mcDynamic) + { + return false; + } + + return _target.ProcessedData.GetOrAdd(methodDescHandle.Address).ExtendedFlags.HasFlag(DynamicMethodDescExtendedFlags.IsILStub); + } } diff --git a/src/native/managed/cdacreader/src/Data/MethodDesc.cs b/src/native/managed/cdacreader/src/Data/MethodDesc.cs index 7c4d6b9c8ebb37..d51153ab53e59d 100644 --- a/src/native/managed/cdacreader/src/Data/MethodDesc.cs +++ b/src/native/managed/cdacreader/src/Data/MethodDesc.cs @@ -15,9 +15,58 @@ public MethodDesc(Target target, TargetPointer address) ChunkIndex = target.Read(address + (ulong)type.Fields[nameof(ChunkIndex)].Offset); Slot = target.Read(address + (ulong)type.Fields[nameof(Slot)].Offset); Flags = target.Read(address + (ulong)type.Fields[nameof(Flags)].Offset); + Flags3AndTokenRemainder = target.Read(address + (ulong)type.Fields[nameof(Flags3AndTokenRemainder)].Offset); } public byte ChunkIndex { get; init; } public ushort Slot { get; init; } public ushort Flags { get; init; } + public ushort Flags3AndTokenRemainder { get; init; } +} + +internal sealed class InstantiatedMethodDesc : IData +{ + static InstantiatedMethodDesc IData.Create(Target target, TargetPointer address) => new InstantiatedMethodDesc(target, address); + public InstantiatedMethodDesc(Target target, TargetPointer address) + { + Target.TypeInfo type = target.GetTypeInfo(DataType.InstantiatedMethodDesc); + + PerInstInfo = target.ReadPointer(address + (ulong)type.Fields[nameof(PerInstInfo)].Offset); + NumGenericArgs = target.Read(address + (ulong)type.Fields[nameof(NumGenericArgs)].Offset); + Flags2 = target.Read(address + (ulong)type.Fields[nameof(Flags2)].Offset); + } + + public TargetPointer PerInstInfo { get; init; } + public ushort NumGenericArgs { get; init; } + public ushort Flags2 { get; init; } +} + +internal sealed class DynamicMethodDesc : IData +{ + static DynamicMethodDesc IData.Create(Target target, TargetPointer address) => new DynamicMethodDesc(target, address); + public DynamicMethodDesc(Target target, TargetPointer address) + { + Target.TypeInfo type = target.GetTypeInfo(DataType.DynamicMethodDesc); + + MethodName = target.ReadPointer(address + (ulong)type.Fields[nameof(MethodName)].Offset); + } + + public TargetPointer MethodName { get; init; } +} + +internal sealed class StoredSigMethodDesc : IData +{ + static StoredSigMethodDesc IData.Create(Target target, TargetPointer address) => new StoredSigMethodDesc(target, address); + public StoredSigMethodDesc(Target target, TargetPointer address) + { + Target.TypeInfo type = target.GetTypeInfo(DataType.StoredSigMethodDesc); + + Sig = target.ReadPointer(address + (ulong)type.Fields[nameof(Sig)].Offset); + cSig = target.Read(address + (ulong)type.Fields[nameof(cSig)].Offset); + ExtendedFlags = target.Read(address + (ulong)type.Fields[nameof(ExtendedFlags)].Offset); + } + + public TargetPointer Sig { get; init; } + public uint cSig { get; init; } + public uint ExtendedFlags { get; init; } } diff --git a/src/native/managed/cdacreader/src/Data/MethodDescChunk.cs b/src/native/managed/cdacreader/src/Data/MethodDescChunk.cs index 3d397a124df6d4..028050c40d9d42 100644 --- a/src/native/managed/cdacreader/src/Data/MethodDescChunk.cs +++ b/src/native/managed/cdacreader/src/Data/MethodDescChunk.cs @@ -16,10 +16,12 @@ public MethodDescChunk(Target target, TargetPointer address) Next = target.ReadPointer(address + (ulong)type.Fields[nameof(Next)].Offset); Size = target.Read(address + (ulong)type.Fields[nameof(Size)].Offset); Count = target.Read(address + (ulong)type.Fields[nameof(Count)].Offset); + FlagsAndTokenRange = target.Read(address + (ulong)type.Fields[nameof(FlagsAndTokenRange)].Offset); } public TargetPointer MethodTable { get; init; } public TargetPointer Next { get; init; } public byte Size { get; init; } public byte Count { get; init; } + public ushort FlagsAndTokenRange { get; init; } } diff --git a/src/native/managed/cdacreader/src/DataType.cs b/src/native/managed/cdacreader/src/DataType.cs index 25f60a2fc8af58..e91f2e2cba6961 100644 --- a/src/native/managed/cdacreader/src/DataType.cs +++ b/src/native/managed/cdacreader/src/DataType.cs @@ -45,4 +45,7 @@ public enum DataType SyncBlock, SyncTableEntry, InteropSyncBlockInfo, + InstantiatedMethodDesc, + DynamicMethodDesc, + StoredSigMethodDesc, } From ec9eb540786359c74d986dc06cfbb78d10fb0866 Mon Sep 17 00:00:00 2001 From: David Wrighton Date: Thu, 8 Aug 2024 11:00:16 -0700 Subject: [PATCH 32/38] Commit current state --- src/coreclr/debug/daccess/dacfn.cpp | 13 +++++ src/coreclr/debug/daccess/dacimpl.h | 1 + src/coreclr/debug/daccess/request.cpp | 57 +++++++++++++++++++ .../debug/runtimeinfo/datadescriptor.h | 6 +- src/coreclr/inc/daccess.h | 21 +++++++ 5 files changed, 95 insertions(+), 3 deletions(-) diff --git a/src/coreclr/debug/daccess/dacfn.cpp b/src/coreclr/debug/daccess/dacfn.cpp index 2c9f28481d218e..07cb785ee4ae10 100644 --- a/src/coreclr/debug/daccess/dacfn.cpp +++ b/src/coreclr/debug/daccess/dacfn.cpp @@ -1386,6 +1386,15 @@ DacAllocHostOnlyInstance(ULONG32 size, bool throwEx) return inst + 1; } +thread_local bool t_DacAssertsUnconditionally = false; + +bool DacSetEnableDacAssertsUnconditionally(bool enable) +{ + bool oldValue = t_DacAssertsUnconditionally; + t_DacAssertsUnconditionally = enable; + return oldValue; +} + // // Queries whether ASSERTs should be raised when inconsistencies in the target are detected // @@ -1404,6 +1413,10 @@ bool DacTargetConsistencyAssertsEnabled() return true; } + // If asserts are unconditionally enabled via the thread local, simply return true. + if (t_DacAssertsUnconditionally) + return true; + return g_dacImpl->TargetConsistencyAssertsEnabled(); } diff --git a/src/coreclr/debug/daccess/dacimpl.h b/src/coreclr/debug/daccess/dacimpl.h index 70e8845f6b9023..d09dab39dccc65 100644 --- a/src/coreclr/debug/daccess/dacimpl.h +++ b/src/coreclr/debug/daccess/dacimpl.h @@ -1245,6 +1245,7 @@ class ClrDataAccess HRESULT GetObjectStringDataImpl(CLRDATA_ADDRESS obj, unsigned int count, _Inout_updates_z_(count) WCHAR *stringData, unsigned int *pNeeded); HRESULT GetUsefulGlobalsImpl(struct DacpUsefulGlobalsData *globalsData); HRESULT GetMethodDescDataImpl(CLRDATA_ADDRESS methodDesc, CLRDATA_ADDRESS ip, struct DacpMethodDescData *data, ULONG cRevertedRejitVersions, DacpReJitData * rgRevertedRejitData, ULONG * pcNeededRevertedRejitData); + HRESULT GetMethodDescNameImpl(CLRDATA_ADDRESS methodDesc, unsigned int count, _Inout_updates_z_(count) WCHAR *name, 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 50a45522968c66..58fa58148f99ef 100644 --- a/src/coreclr/debug/daccess/request.cpp +++ b/src/coreclr/debug/daccess/request.cpp @@ -317,6 +317,7 @@ HRESULT ClrDataAccess::GetThreadStoreData(struct DacpThreadStoreData *threadStor // Assert that the data is the same as what we get from the DAC. DacpThreadStoreData threadStoreDataLocal; HRESULT hrLocal = GetThreadStoreDataImpl(&threadStoreDataLocal); + DacAssertsEnabledHolder assertsEnabled; _ASSERTE(hr == hrLocal); _ASSERTE(threadStoreData->threadCount == threadStoreDataLocal.threadCount); _ASSERTE(threadStoreData->unstartedThreadCount == threadStoreDataLocal.unstartedThreadCount); @@ -895,6 +896,7 @@ HRESULT ClrDataAccess::GetThreadData(CLRDATA_ADDRESS threadAddr, struct DacpThre { DacpThreadData threadDataLocal; HRESULT hrLocal = GetThreadDataImpl(threadAddr, &threadDataLocal); + DacAssertsEnabledHolder assertsEnabled; _ASSERTE(hr == hrLocal); _ASSERTE(threadData->corThreadId == threadDataLocal.corThreadId); _ASSERTE(threadData->osThreadId == threadDataLocal.osThreadId); @@ -1080,6 +1082,7 @@ HRESULT ClrDataAccess::GetMethodDescData( pcNeededRevertedRejitDataLocal = &cNeededRevertedRejitDataLocal; } HRESULT hrLocal = GetMethodDescDataImpl(methodDesc, ip,&mdDataLocal, cRevertedRejitVersions, rgRevertedRejitDataLocal, pcNeededRevertedRejitDataLocal); + DacAssertsEnabledHolder assertsEnabled; _ASSERTE(hr == hrLocal); _ASSERTE(methodDescData->bHasNativeCode == mdDataLocal.bHasNativeCode); _ASSERTE(methodDescData->bIsDynamic == mdDataLocal.bIsDynamic); @@ -1581,6 +1584,51 @@ ClrDataAccess::GetMethodDescName(CLRDATA_ADDRESS methodDesc, unsigned int count, SOSDacEnter(); + if (m_cdacSos != NULL) + { + hr = m_cdacSos->GetMethodDescName(methodDesc, count, name, pNeeded); + if (FAILED(hr)) + { + hr = GetMethodDescNameImpl(methodDesc, count, name, pNeeded); + } +#ifdef _DEBUG + else + { + NewArrayHolder nameLocal = new WCHAR[count]; + unsigned int neededLocal = 0; + HRESULT hrLocal = GetMethodDescNameImpl(methodDesc, count, nameLocal, &neededLocal); + + DacAssertsEnabledHolder assertsEnabled; + _ASSERTE(hr == hrLocal); + + if (name != NULL) + { + _ASSERTE(0 == u16_strncmp(name, (WCHAR *)nameLocal, count)); + } + if (pNeeded != NULL) + { + _ASSERTE(*pNeeded == neededLocal); + } + } +#endif + } + else + { + hr = GetMethodDescNameImpl(methodDesc, count, name, pNeeded); + } + + SOSDacLeave(); + return hr; +} + +HRESULT +ClrDataAccess::GetMethodDescNameImpl(CLRDATA_ADDRESS methodDesc, unsigned int count, _Inout_updates_z_(count) WCHAR *name, unsigned int *pNeeded) +{ + if (methodDesc == 0) + return E_INVALIDARG; + + SOSDacEnter(); + MethodDesc* pMD = PTR_MethodDesc(TO_TADDR(methodDesc)); StackSString str; @@ -1701,6 +1749,7 @@ ClrDataAccess::GetObjectStringData(CLRDATA_ADDRESS obj, unsigned int count, _Ino unsigned int neededLocal; SString stringDataLocal; HRESULT hrLocal = GetObjectStringDataImpl(obj, count, stringDataLocal.OpenUnicodeBuffer(count), &neededLocal); + DacAssertsEnabledHolder assertsEnabled; _ASSERTE(hr == hrLocal); _ASSERTE(pNeeded == NULL || *pNeeded == neededLocal); _ASSERTE(u16_strncmp(stringData, stringDataLocal, count) == 0); @@ -1930,6 +1979,7 @@ ClrDataAccess::GetModuleData(CLRDATA_ADDRESS addr, struct DacpModuleData* module { DacpModuleData moduleDataLocal; HRESULT hrLocal = GetModuleDataImpl(addr, &moduleDataLocal); + DacAssertsEnabledHolder assertsEnabled; _ASSERTE(hr == hrLocal); _ASSERTE(moduleData->Address == moduleDataLocal.Address); _ASSERTE(moduleData->PEAssembly == moduleDataLocal.PEAssembly); @@ -2047,6 +2097,7 @@ ClrDataAccess::GetMethodTableData(CLRDATA_ADDRESS mt, struct DacpMethodTableData // Assert that the data is the same as what we get from the DAC. DacpMethodTableData mtDataLocal; HRESULT hrLocal = GetMethodTableDataImpl(mt, &mtDataLocal); + DacAssertsEnabledHolder assertsEnabled; _ASSERTE(hr == hrLocal); _ASSERTE(MTData->BaseSize == mtDataLocal.BaseSize); _ASSERTE(MTData->ComponentSize == mtDataLocal.ComponentSize); @@ -2136,6 +2187,7 @@ ClrDataAccess::GetMethodTableName(CLRDATA_ADDRESS mt, unsigned int count, _Inout NewArrayHolder pwszNameLocal(new WCHAR[count]); unsigned int neededLocal = 0; HRESULT hrLocal = GetMethodTableNameImpl(mt, count, mtName != NULL ? (WCHAR *)pwszNameLocal : NULL, pNeeded != NULL ? &neededLocal : NULL); + DacAssertsEnabledHolder assertsEnabled; _ASSERTE(hr == hrLocal); if (mtName != NULL) @@ -2415,6 +2467,7 @@ ClrDataAccess::GetMethodTableForEEClass(CLRDATA_ADDRESS eeClassReallyCanonMT, CL // Assert that the data is the same as what we get from the DAC. CLRDATA_ADDRESS valueLocal; HRESULT hrLocal = GetMethodTableForEEClassImpl(eeClassReallyCanonMT, &valueLocal); + DacAssertsEnabledHolder assertsEnabled; _ASSERTE(hr == hrLocal); _ASSERTE(*value == valueLocal); } @@ -2578,6 +2631,7 @@ ClrDataAccess::GetObjectData(CLRDATA_ADDRESS addr, struct DacpObjectData* object { DacpObjectData objectDataLocal; HRESULT hrLocal = GetObjectDataImpl(addr, &objectDataLocal); + DacAssertsEnabledHolder assertsEnabled; _ASSERTE(hr == hrLocal); _ASSERTE(objectData->MethodTable == objectDataLocal.MethodTable); _ASSERTE(objectData->ObjectType == objectDataLocal.ObjectType); @@ -3586,6 +3640,7 @@ ClrDataAccess::GetUsefulGlobals(struct DacpUsefulGlobalsData* globalsData) // Assert that the data is the same as what we get from the DAC. DacpUsefulGlobalsData globalsDataLocal; HRESULT hrLocal = GetUsefulGlobalsImpl(&globalsDataLocal); + DacAssertsEnabledHolder assertsEnabled; _ASSERTE(hr == hrLocal); _ASSERTE(globalsData->ArrayMethodTable == globalsDataLocal.ArrayMethodTable); _ASSERTE(globalsData->StringMethodTable == globalsDataLocal.StringMethodTable); @@ -3644,6 +3699,7 @@ ClrDataAccess::GetNestedExceptionData(CLRDATA_ADDRESS exception, CLRDATA_ADDRESS CLRDATA_ADDRESS exceptionObjectLocal; CLRDATA_ADDRESS nextNestedExceptionLocal; HRESULT hrLocal = GetNestedExceptionDataImpl(exception, &exceptionObjectLocal, &nextNestedExceptionLocal); + DacAssertsEnabledHolder assertsEnabled; _ASSERTE(hr == hrLocal); _ASSERTE(*exceptionObject == exceptionObjectLocal); _ASSERTE(*nextNestedException == nextNestedExceptionLocal); @@ -4965,6 +5021,7 @@ HRESULT ClrDataAccess::GetObjectExceptionData(CLRDATA_ADDRESS objAddr, struct Da { DacpExceptionObjectData dataLocal; HRESULT hrLocal = GetObjectExceptionDataImpl(objAddr, &dataLocal); + DacAssertsEnabledHolder assertsEnabled; _ASSERTE(hr == hrLocal); _ASSERTE(data->Message == dataLocal.Message); _ASSERTE(data->InnerException == dataLocal.InnerException); diff --git a/src/coreclr/debug/runtimeinfo/datadescriptor.h b/src/coreclr/debug/runtimeinfo/datadescriptor.h index bc9f6ffb00946d..c6b56004b54006 100644 --- a/src/coreclr/debug/runtimeinfo/datadescriptor.h +++ b/src/coreclr/debug/runtimeinfo/datadescriptor.h @@ -317,9 +317,9 @@ CDAC_TYPE_END(InstantiatedMethodDesc) CDAC_TYPE_BEGIN(StoredSigMethodDesc) CDAC_TYPE_INDETERMINATE(StoredSigMethodDesc) -CDAC_TYPE_FIELD(StoredSigMethodDesc, /*pointer*/, Sig, cdac_data::Sig) -CDAC_TYPE_FIELD(StoredSigMethodDesc, /*uint32*/, cSig, cdac_data::cSig) -CDAC_TYPE_FIELD(StoredSigMethodDesc, /*uint32*/, ExtendedFlags, cdac_data::ExtendedFlags) +CDAC_TYPE_FIELD(StoredSigMethodDesc, /*pointer*/, Sig, cdac_data::Sig) +CDAC_TYPE_FIELD(StoredSigMethodDesc, /*uint32*/, cSig, cdac_data::cSig) +CDAC_TYPE_FIELD(StoredSigMethodDesc, /*uint32*/, ExtendedFlags, cdac_data::ExtendedFlags) CDAC_TYPE_END(StoredSigMethodDesc) CDAC_TYPE_BEGIN(DynamicMethodDesc) diff --git a/src/coreclr/inc/daccess.h b/src/coreclr/inc/daccess.h index dc9b02daa1cda8..74aeed1c9bb067 100644 --- a/src/coreclr/inc/daccess.h +++ b/src/coreclr/inc/daccess.h @@ -733,6 +733,27 @@ PVOID DacAllocHostOnlyInstance(ULONG32 size, bool throwEx); // Determines whether ASSERTs should be raised when inconsistencies in the target are detected bool DacTargetConsistencyAssertsEnabled(); +// Sets whether ASSERTs should be raised when then fail. +// Returns the previous value +bool DacSetEnableDacAssertsUnconditionally(bool enable); + +class DacAssertsEnabledHolder +{ +#ifdef _DEBUG + bool m_fOldValue; +public: + DacAssertsEnabledHolder() + { + m_fOldValue = DacSetEnableDacAssertsUnconditionally(true); + } + + ~DacAssertsEnabledHolder() + { + DacSetEnableDacAssertsUnconditionally(m_fOldValue); + } +#endif // _DEBUG +}; + // Host instances can be marked as they are enumerated in // order to break cycles. This function returns true if // the instance is already marked, otherwise it marks the From 54ded9a67765d98be4df17ddce60c77804bdd53f Mon Sep 17 00:00:00 2001 From: David Wrighton Date: Thu, 8 Aug 2024 16:05:13 -0700 Subject: [PATCH 33/38] It Works! --- .../src/Helpers/EcmaMetadataReader.cs | 37 ++++++++++++++++++- .../cdacreader/src/Legacy/TypeNameBuilder.cs | 22 ++++++++++- 2 files changed, 56 insertions(+), 3 deletions(-) diff --git a/src/native/managed/cdacreader/src/Helpers/EcmaMetadataReader.cs b/src/native/managed/cdacreader/src/Helpers/EcmaMetadataReader.cs index d358ccc15d5240..47a2a5d92ce639 100644 --- a/src/native/managed/cdacreader/src/Helpers/EcmaMetadataReader.cs +++ b/src/native/managed/cdacreader/src/Helpers/EcmaMetadataReader.cs @@ -278,7 +278,42 @@ public uint GetColumnAsConstant(EcmaMetadataCursor c, MetadataColumnIndex col_id public System.ReadOnlySpan GetColumnAsBlob(EcmaMetadataCursor c, MetadataColumnIndex col_idx) { - throw new NotImplementedException(); + if (columnTypes[(int)col_idx] != ColumnType.Blob) + throw new NotImplementedException(); + + uint rawResult = GetColumnRaw(c, col_idx); + if (rawResult == 0) + return default; + + checked + { + ReadOnlySpan blobHeap = _ecmaMetadata.BlobHeap.Span; + int curOffset = (int)rawResult; + + byte headerByte1 = blobHeap[curOffset]; + int size; + if ((headerByte1 & 0x80) == 0) + { + size = headerByte1; + } + else if ((headerByte1 & 0xC) == 0x80) + { + byte headerByte2 = blobHeap[++curOffset]; + size = headerByte1 & 0x3F << 8 + headerByte2; + } + else + { + byte headerByte2 = blobHeap[++curOffset]; + byte headerByte3 = blobHeap[++curOffset]; + byte headerByte4 = blobHeap[++curOffset]; + size = (headerByte1 & 0x3F << 24) + + (headerByte2 << 16) + + (headerByte3 << 8) + + headerByte4; + } + curOffset++; + return blobHeap.Slice(curOffset, size); + } } public uint GetColumnAsToken(EcmaMetadataCursor c, MetadataColumnIndex col_idx) diff --git a/src/native/managed/cdacreader/src/Legacy/TypeNameBuilder.cs b/src/native/managed/cdacreader/src/Legacy/TypeNameBuilder.cs index f282ee6858ecf8..b5f89821d7c228 100644 --- a/src/native/managed/cdacreader/src/Legacy/TypeNameBuilder.cs +++ b/src/native/managed/cdacreader/src/Legacy/TypeNameBuilder.cs @@ -44,12 +44,15 @@ private enum ParseState private int InstNesting; private Stack? GenericStartsStack; - private TypeNameBuilder(StringBuilder typeString, Target target, TypeNameFormat format) + private TypeNameBuilder(StringBuilder typeString, Target target, TypeNameFormat format, bool initialStateIsName = false) { TypeString = typeString; Target = target; UseAngleBracketsForGenerics = format.HasFlag(TypeNameFormat.FormatAngleBrackets); - State = ParseState.Start; + if (initialStateIsName) + State = ParseState.Name; + else + State = ParseState.Start; } public static void AppendMethodInternal(Target target, StringBuilder stringBuilder, Contracts.MethodDescHandle method, TypeNameFormat format) @@ -121,6 +124,12 @@ public static void AppendMethodImpl(Target target, StringBuilder stringBuilder, stringBuilder.Append(reader.GetColumnAsUtf8String(cursor, MetadataColumnIndex.MethodDef_Name)); } + ReadOnlySpan genericMethodInstantiation = runtimeTypeSystem.GetGenericMethodInstantiation(method); + if (genericMethodInstantiation.Length > 0 && !runtimeTypeSystem.IsGenericMethodDefinition(method)) + { + AppendInst(target, stringBuilder, genericMethodInstantiation, format); + } + if (format.HasFlag(TypeNameFormat.FormatSignature)) { ReadOnlySpan signature; @@ -303,6 +312,15 @@ private static void AppendTypeCore(ref TypeNameBuilder tnb, Contracts.TypeHandle } } + // Append a square-bracket-enclosed, comma-separated list of n type parameters in inst to the string s + // and enclose each parameter in square brackets to disambiguate the commas + // The following flags in the FormatFlags argument are significant: FormatNamespace FormatFullInst FormatAssembly FormatNoVersion + private static void AppendInst(Target target, StringBuilder stringBuilder, ReadOnlySpan inst, TypeNameFormat format) + { + TypeNameBuilder tnb = new (stringBuilder, target, format, initialStateIsName: true); + AppendInst(ref tnb, inst, format); + } + private static void AppendInst(ref TypeNameBuilder tnb, ReadOnlySpan inst, TypeNameFormat format) { tnb.OpenGenericArguments(); From 3ffb03bff4e4f27db7cc719d0fa471171d52c99e Mon Sep 17 00:00:00 2001 From: David Wrighton Date: Thu, 8 Aug 2024 16:34:28 -0700 Subject: [PATCH 34/38] Document the new contracts needed for generating MethodDesc names --- .../design/datacontracts/RuntimeTypeSystem.md | 253 +++++++++++++++++- 1 file changed, 245 insertions(+), 8 deletions(-) diff --git a/docs/design/datacontracts/RuntimeTypeSystem.md b/docs/design/datacontracts/RuntimeTypeSystem.md index 5693ab205ce592..c1bd9fae885a25 100644 --- a/docs/design/datacontracts/RuntimeTypeSystem.md +++ b/docs/design/datacontracts/RuntimeTypeSystem.md @@ -92,12 +92,49 @@ struct MethodDescHandle } ``` +```csharp +public enum ArrayFunctionType +{ + Get = 0, + Set = 1, + Address = 2, + Constructor = 3 +} +``` + ```csharp partial interface IRuntimeTypeSystem : IContract { public virtual MethodDescHandle GetMethodDescHandle(TargetPointer methodDescPointer); public virtual TargetPointer GetMethodTable(MethodDescHandle methodDesc); + + // Return true for an uninstantiated generic method + public virtual bool IsGenericMethodDefinition(MethodDescHandle methodDesc); + + public virtual ReadOnlySpan GetGenericMethodInstantiation(MethodDescHandle methodDesc); + + // Return mdTokenNil (0x06000000) if the method doesn't have a token, otherwise return the token of the method + public virtual uint GetMethodToken(MethodDescHandle methodDesc); + + // Return true if a MethodDesc represents an array method + // An array method is also a StoredSigMethodDesc + public virtual bool IsArrayMethod(MethodDescHandle methodDesc, out ArrayFunctionType functionType); + + // Return true if a MethodDesc represents a dynamically generated method, either an IL Stub dynamically + // generated by the runtime, or a MethodDesc that describes a method represented by the System.Reflection.Emit.DynamicMethod class + // A dynamic method is also a StoredSigMethodDesc + public virtual bool IsDynamicMethod(MethodDescHandle methodDesc, out ReadOnlySpan methodName); + + // A StoredSigMethodDesc is a MethodDesc for which the signature isn't found in metadata. + public virtual bool IsStoredSigMethodDesc(MethodDescHandle methodDesc, out ReadOnlySpan signature); + + // Return true for a MethodDesc that describes a method represented by the System.Reflection.Emit.DynamicMethod class + // A LCG method is also a StoredSigMethodDesc + public virtual bool IsLCGMethod(MethodDescHandle methodDesc); + + // Return true if a MethodDesc represents an IL Stub dynamically generated by the runtime + public virtual bool IsILStub(MethodDescHandle methodDesc); } ``` @@ -563,7 +600,8 @@ The version 1 `MethodDesc` APIs depend on the `MethodDescAlignment` global and t | Global name | Meaning | | --- | --- | -| `MethodDescAlignment` | `MethodDescChunk` trailing data is allocated in multiples of this constant. The size (in bytes) of each `MethodDesc` (or subclass) instance is a multiple of this constant. +| `MethodDescAlignment` | `MethodDescChunk` trailing data is allocated in multiples of this constant. The size (in bytes) of each `MethodDesc` (or subclass) instance is a multiple of this constant. | +| `MethodDescTokenRemainderBitCount` | Number of bits in the token remainder in `MethodDesc` | In the runtime a `MethodDesc` implicitly belongs to a single `MethodDescChunk` and some common data is shared between method descriptors that belong to the same chunk. A single method table @@ -572,12 +610,211 @@ will typically have multiple chunks. There are subkinds of MethodDescs at runti We depend on the following data descriptors: | Data Descriptor Name | Field | Meaning | | --- | --- | --- | -| `MethodDesc` | `ChunkIndex` | Offset of this `MethodDesc` relative to the end of its containing `MethodDescChunk` - in multiples of `MethodDescAlignment` -| `MethodDesc` | `Slot` | The method's slot -| `MethodDesc` | `Flags` | The method's flags -| `MethodDescChunk` | `MethodTable` | The method table set of methods belongs to -| `MethodDescChunk` | `Next` | The next chunk of methods -| `MethodDescChunk` | `Size` | The size of this `MethodDescChunk` following this `MethodDescChunk` header, minus 1. In multiples of `MethodDescAlignment` -| `MethodDescChunk` | `Count` | The number of `MethodDesc` entries in this chunk, minus 1. +| `MethodDesc` | `ChunkIndex` | Offset of this `MethodDesc` relative to the end of its containing `MethodDescChunk` - in multiples of `MethodDescAlignment` | +| `MethodDesc` | `Slot` | The method's slot | +| `MethodDesc` | `Flags` | The method's flags | +| `MethodDesc` | `Flags3AndTokenRemainder` | More flags for the method, and the low bits of the method's token's RID | +| `MethodDescChunk` | `MethodTable` | The method table set of methods belongs to | +| `MethodDescChunk` | `Next` | The next chunk of methods | +| `MethodDescChunk` | `Size` | The size of this `MethodDescChunk` following this `MethodDescChunk` header, minus 1. In multiples of `MethodDescAlignment` | +| `MethodDescChunk` | `Count` | The number of `MethodDesc` entries in this chunk, minus 1. | +| `MethodDescChunk` | `FlagsAndTokenRange` | `MethodDescChunk` flags, and the upper bits of the method token's RID | +| `InstantiatedMethodDesc` | `PerInstInfo` | The pointer to the method's type arguments | +| `InstantiatedMethodDesc` | `Flags2` | Flags for the `InstantiatedMethodDesc` | +| `InstantiatedMethodDesc` | `NumGenericArgs` | How many generic args the method has | +| `StoredSigMethodDesc` | `Sig` | Pointer to a metadata signature | +| `StoredSigMethodDesc` | `cSig` | Count of bytes in the metadata signature | +| `StoredSigMethodDesc` | `ExtendedFlags` | Flags field for the `StoredSigMethodDesc` | +| `DynamicMethodDesc` | `MethodName` | Pointer to Null-terminated UTF8 string describing the Method desc | + + +And the following enumeration definitions + +```csharp + internal enum MethodDescClassification + { + mcIL = 0, // IL + mcFCall = 1, // FCall (also includes tlbimped ctor, Delegate ctor) + mcNDirect = 2, // N/Direct + mcEEImpl = 3, // special method; implementation provided by EE (like Delegate Invoke) + mcArray = 4, // Array ECall + mcInstantiated = 5, // Instantiated generic methods, including descriptors + // for both shared and unshared code (see InstantiatedMethodDesc) + mcComInterop = 6, + mcDynamic = 7, // for method desc with no metadata behind + } + + [Flags] + internal enum MethodDescFlags : ushort + { + ClassificationMask = 0x7, + HasNonVtableSlot = 0x0008, + } + + internal enum InstantiatedMethodDescFlags2 : ushort + { + KindMask = 0x07, + GenericMethodDefinition = 0x01, + UnsharedMethodInstantiation = 0x02, + SharedMethodInstantiation = 0x03, + WrapperStubWithInstantiations = 0x04, + } + + [Flags] + internal enum DynamicMethodDescExtendedFlags : uint + { + IsLCGMethod = 0x00004000, + IsILStub = 0x00008000, + } +``` + + +And the various apis are implemented with the following algorithms + +```csharp + public bool IsGenericMethodDefinition(MethodDescHandle methodDescHandle) + { + MethodDesc methodDesc = _methodDescs[methodDescHandle.Address]; + + if (methodDesc.Classification != MethodDescClassification.mcInstantiated) + return false; + + ushort Flags2 = // Read Flags2 field from InstantiatedMethodDesc contract using address methodDescHandle.Address + + return ((int)Flags2 & (int)InstantiatedMethodDescFlags2.KindMask) == (int)InstantiatedMethodDescFlags2.GenericMethodDefinition; + } + + public ReadOnlySpan GetGenericMethodInstantiation(MethodDescHandle methodDescHandle) + { + MethodDesc methodDesc = _methodDescs[methodDescHandle.Address]; + + if (methodDesc.Classification != MethodDescClassification.mcInstantiated) + return default; + + TargetPointer dictionaryPointer = // Read PerInstInfo field from InstantiatedMethodDesc contract using address methodDescHandle.Address + if (dictionaryPointer == 0) + return default; + + int NumTypeArgs = // Read NumGenericArgs from methodDescHandle.Address using InstantiatedMethodDesc contract + TypeHandle[] instantiation = new TypeHandle[NumTypeArgs]; + for (int i = 0; i < NumTypeArgs; i++) + instantiation[i] = GetTypeHandle(_target.ReadPointer(dictionaryPointer + _target.PointerSize * i)); + + return instantiation; + } + + public uint GetMethodToken(MethodDescHandle methodDescHandle) + { + MethodDesc methodDesc = _methodDescs[methodDescHandle.Address]; + + TargetPointer methodDescChunk = // Using ChunkIndex from methodDesc, compute the wrapping MethodDescChunk + + ushort Flags3AndTokenRemainder = // Read Flags3AndTokenRemainder field from MethodDesc contract using address methodDescHandle.Address + + ushort FlagsAndTokenRange = // Read FlagsAndTokenRange field from MethodDescChunk contract using address methodDescChunk + int tokenRemainderBitCount = _target.ReadGlobal(Constants.Globals.MethodDescTokenRemainderBitCount); + int tokenRangeBitCount = 24 - tokenRemainderBitCount; + uint allRidBitsSet = 0xFFFFFF; + uint tokenRemainderMask = allRidBitsSet >> tokenRangeBitCount; + uint tokenRangeMask = allRidBitsSet >> tokenRemainderBitCount; + + uint tokenRemainder = (uint)(_desc.Flags3AndTokenRemainder & tokenRemainderMask); + uint tokenRange = ((uint)(_chunk.FlagsAndTokenRange & tokenRangeMask)) << tokenRemainderBitCount; + + return 0x06000000 | tokenRange | tokenRemainder; + } + + public bool IsArrayMethod(MethodDescHandle methodDescHandle, out ArrayFunctionType functionType) + { + MethodDesc methodDesc = _methodDescs[methodDescHandle.Address]; + + if (methodDesc.Classification != MethodDescClassification.mcArray) + { + functionType = default; + return false; + } + + int arrayMethodIndex = methodDesc.Slot - GetNumVtableSlots(GetTypeHandle(methodDesc.MethodTable)); + + functionType = arrayMethodIndex switch + { + 0 => ArrayFunctionType.Get, + 1 => ArrayFunctionType.Set, + 2 => ArrayFunctionType.Address, + > 3 => ArrayFunctionType.Constructor, + _ => throw new InvalidOperationException() + }; + + return true; + } + + public bool IsDynamicMethod(MethodDescHandle methodDescHandle, out ReadOnlySpan methodName) + { + MethodDesc methodDesc = _methodDescs[methodDescHandle.Address]; + + if (methodDesc.Classification != MethodDescClassification.mcDynamic) + { + methodName = default; + return false; + } + + TargetPointer methodNamePointer = // Read MethodName field from DynamicMethodDesc contract using address methodDescHandle.Address + + methodName = // ReadBuffer from target of a utf8 null terminated string, starting at address methodNamePointer + return true; + } + + public bool IsStoredSigMethodDesc(MethodDescHandle methodDescHandle, out ReadOnlySpan signature) + { + MethodDesc methodDesc = _methodDescs[methodDescHandle.Address]; + + switch (methodDesc.Classification) + { + case MethodDescClassification.mcDynamic: + case MethodDescClassification.mcEEImpl: + case MethodDescClassification.mcArray: + break; // These have stored sigs + + default: + signature = default; + return false; + } + + TargetPointer Sig = // Read Sig field from StoredSigMethodDesc contract using address methodDescHandle.Address + uint cSig = // Read cSig field from StoredSigMethodDesc contract using address methodDescHandle.Address + + TargetPointer methodNamePointer = // Read S field from DynamicMethodDesc contract using address methodDescHandle.Address + signature = // Read buffer from target memory starting at address Sig, with cSig bytes in it. + return true; + } + + public bool IsLCGMethod(MethodDescHandle methodDescHandle) + { + MethodDesc methodDesc = _methodDescs[methodDescHandle.Address]; + + if (methodDesc.Classification != MethodDescClassification.mcDynamic) + { + return false; + } + + uint ExtendedFlags = // Read ExtendedFlags field from StoredSigMethodDesc contract using address methodDescHandle.Address + + return ((DynamicMethodDescExtendedFlags)ExtendedFlags).HasFlag(DynamicMethodDescExtendedFlags.IsLCGMethod); + } + + public bool IsILStub(MethodDescHandle methodDescHandle) + { + MethodDesc methodDesc = _methodDescs[methodDescHandle.Address]; + + if (methodDesc.Classification != MethodDescClassification.mcDynamic) + { + return false; + } + + uint ExtendedFlags = // Read ExtendedFlags field from StoredSigMethodDesc contract using address methodDescHandle.Address + + return ((DynamicMethodDescExtendedFlags)ExtendedFlags).HasFlag(DynamicMethodDescExtendedFlags.IsILStub); + } +``` **TODO(cdac)** From e72cea6ccf4dc7b58c9d60e55a9969e2c7da8b2d Mon Sep 17 00:00:00 2001 From: David Wrighton Date: Thu, 8 Aug 2024 17:21:28 -0700 Subject: [PATCH 35/38] Handle Path fallback scenario. Leave actual implementation/documentation of contract Loader.GetPath for a future date --- src/coreclr/debug/runtimeinfo/datadescriptor.h | 1 + src/native/managed/cdacreader/src/Constants.cs | 1 + src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs | 8 +++++++- src/native/managed/cdacreader/src/Target.cs | 2 ++ 4 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/coreclr/debug/runtimeinfo/datadescriptor.h b/src/coreclr/debug/runtimeinfo/datadescriptor.h index c6b56004b54006..44e1769cc15190 100644 --- a/src/coreclr/debug/runtimeinfo/datadescriptor.h +++ b/src/coreclr/debug/runtimeinfo/datadescriptor.h @@ -352,6 +352,7 @@ CDAC_GLOBAL(ObjectToMethodTableUnmask, uint8, 1 | 1 << 1 | 1 << 2) CDAC_GLOBAL(ObjectToMethodTableUnmask, uint8, 1 | 1 << 1) #endif //TARGET_64BIT CDAC_GLOBAL(SOSBreakingChangeVersion, uint8, SOS_BREAKING_CHANGE_VERSION) +CDAC_GLOBAL(DirectorySeparator, uint8, (uint8_t)DIRECTORY_SEPARATOR_CHAR_A) CDAC_GLOBAL(MethodDescAlignment, uint64, MethodDesc::ALIGNMENT) CDAC_GLOBAL(ObjectHeaderSize, uint64, OBJHEADER_SIZE) CDAC_GLOBAL(SyncBlockValueToObjectOffset, uint16, OBJHEADER_SIZE - cdac_data::SyncBlockValue) diff --git a/src/native/managed/cdacreader/src/Constants.cs b/src/native/managed/cdacreader/src/Constants.cs index a30c1b02a35607..5353fed7503444 100644 --- a/src/native/managed/cdacreader/src/Constants.cs +++ b/src/native/managed/cdacreader/src/Constants.cs @@ -37,5 +37,6 @@ internal static class Globals internal const string ArrayBoundsZero = nameof(ArrayBoundsZero); internal const string MethodDescTokenRemainderBitCount = nameof(MethodDescTokenRemainderBitCount); + internal const string DirectorySeparator = nameof(DirectorySeparator); } } diff --git a/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs b/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs index 1e3b50ed6254ef..73649f9802b7d0 100644 --- a/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs +++ b/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs @@ -158,8 +158,14 @@ public unsafe int GetMethodDescName(ulong methodDesc, uint count, char* name, ui TargetPointer modulePtr = rtsContract.GetModule(rtsContract.GetTypeHandle(rtsContract.GetMethodTable(methodDescHandle))); Contracts.ModuleHandle module = _target.Contracts.Loader.GetModuleHandle(modulePtr); string modulePath = _target.Contracts.Loader.GetPath(module); + ReadOnlySpan moduleSpan = modulePath.AsSpan(); + int pathNameSpanIndex = moduleSpan.LastIndexOf(_target.DirectorySeparator); + if (pathNameSpanIndex != -1) + { + moduleSpan = moduleSpan.Slice(pathNameSpanIndex + 1); + } stringBuilder.Clear(); - stringBuilder.Append(modulePath); + stringBuilder.Append(moduleSpan); stringBuilder.Append("!Unknown"); hr = HResults.S_OK; } diff --git a/src/native/managed/cdacreader/src/Target.cs b/src/native/managed/cdacreader/src/Target.cs index 56357c915c1caf..a091e2e4f960cd 100644 --- a/src/native/managed/cdacreader/src/Target.cs +++ b/src/native/managed/cdacreader/src/Target.cs @@ -401,6 +401,8 @@ public bool IsAlignedToPointerSize(ulong value) public bool IsAlignedToPointerSize(TargetPointer pointer) => IsAligned(pointer.Value, _config.PointerSize); + public char DirectorySeparator => (char)ReadGlobal(Constants.Globals.DirectorySeparator); + public T ReadGlobal(string name) where T : struct, INumber => ReadGlobal(name, out _); From c250bb8917221df8403e88571dcd1cd3489bc59b Mon Sep 17 00:00:00 2001 From: David Wrighton Date: Thu, 8 Aug 2024 17:45:34 -0700 Subject: [PATCH 36/38] Remove extra lines found in Loader_1.cs --- src/native/managed/cdacreader/src/Contracts/Loader_1.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/native/managed/cdacreader/src/Contracts/Loader_1.cs b/src/native/managed/cdacreader/src/Contracts/Loader_1.cs index f137484af72e17..2c4aa6677f85c0 100644 --- a/src/native/managed/cdacreader/src/Contracts/Loader_1.cs +++ b/src/native/managed/cdacreader/src/Contracts/Loader_1.cs @@ -97,6 +97,4 @@ ModuleLookupTables ILoader.GetLookupTables(ModuleHandle handle) module.TypeDefToMethodTableMap, module.TypeRefToMethodTableMap); } - - } From c3ebfdb0b09f3b91ca2e6bbb105f65591b14eea4 Mon Sep 17 00:00:00 2001 From: David Wrighton Date: Fri, 9 Aug 2024 15:48:58 -0700 Subject: [PATCH 37/38] Address code review comments 1. Rename mcNDirect to mcPInvoke 2. Rename MethodDescClassification to MethodClassification 3. Remove mc prefix from all MethodClassification enum values 4. Move handling of various forms of MethodDescs to using an AsBlah method, and handle the flags in those methods, to make the actual contract methods somewhat simpler. 5. Compute the token eagerly on MethodDesc creation to avoid having to hold a pointer to the Target --- .../design/datacontracts/RuntimeTypeSystem.md | 34 +-- src/coreclr/vm/classcompat.cpp | 6 +- src/coreclr/vm/method.cpp | 4 +- src/coreclr/vm/method.hpp | 4 +- src/coreclr/vm/methodtablebuilder.cpp | 8 +- .../src/Contracts/RuntimeTypeSystem_1.cs | 265 ++++++++++-------- 6 files changed, 180 insertions(+), 141 deletions(-) diff --git a/docs/design/datacontracts/RuntimeTypeSystem.md b/docs/design/datacontracts/RuntimeTypeSystem.md index c1bd9fae885a25..ca4cd8986d317f 100644 --- a/docs/design/datacontracts/RuntimeTypeSystem.md +++ b/docs/design/datacontracts/RuntimeTypeSystem.md @@ -633,15 +633,15 @@ And the following enumeration definitions ```csharp internal enum MethodDescClassification { - mcIL = 0, // IL - mcFCall = 1, // FCall (also includes tlbimped ctor, Delegate ctor) - mcNDirect = 2, // N/Direct - mcEEImpl = 3, // special method; implementation provided by EE (like Delegate Invoke) - mcArray = 4, // Array ECall - mcInstantiated = 5, // Instantiated generic methods, including descriptors + IL = 0, // IL + FCall = 1, // FCall (also includes tlbimped ctor, Delegate ctor) + PInvoke = 2, // PInvoke method + EEImpl = 3, // special method; implementation provided by EE (like Delegate Invoke) + Array = 4, // Array ECall + Instantiated = 5, // Instantiated generic methods, including descriptors // for both shared and unshared code (see InstantiatedMethodDesc) - mcComInterop = 6, - mcDynamic = 7, // for method desc with no metadata behind + ComInterop = 6, + Dynamic = 7, // for method desc with no metadata behind } [Flags] @@ -676,7 +676,7 @@ And the various apis are implemented with the following algorithms { MethodDesc methodDesc = _methodDescs[methodDescHandle.Address]; - if (methodDesc.Classification != MethodDescClassification.mcInstantiated) + if (methodDesc.Classification != MethodDescClassification.Instantiated) return false; ushort Flags2 = // Read Flags2 field from InstantiatedMethodDesc contract using address methodDescHandle.Address @@ -688,7 +688,7 @@ And the various apis are implemented with the following algorithms { MethodDesc methodDesc = _methodDescs[methodDescHandle.Address]; - if (methodDesc.Classification != MethodDescClassification.mcInstantiated) + if (methodDesc.Classification != MethodDescClassification.Instantiated) return default; TargetPointer dictionaryPointer = // Read PerInstInfo field from InstantiatedMethodDesc contract using address methodDescHandle.Address @@ -729,7 +729,7 @@ And the various apis are implemented with the following algorithms { MethodDesc methodDesc = _methodDescs[methodDescHandle.Address]; - if (methodDesc.Classification != MethodDescClassification.mcArray) + if (methodDesc.Classification != MethodDescClassification.Array) { functionType = default; return false; @@ -753,7 +753,7 @@ And the various apis are implemented with the following algorithms { MethodDesc methodDesc = _methodDescs[methodDescHandle.Address]; - if (methodDesc.Classification != MethodDescClassification.mcDynamic) + if (methodDesc.Classification != MethodDescClassification.Dynamic) { methodName = default; return false; @@ -771,9 +771,9 @@ And the various apis are implemented with the following algorithms switch (methodDesc.Classification) { - case MethodDescClassification.mcDynamic: - case MethodDescClassification.mcEEImpl: - case MethodDescClassification.mcArray: + case MethodDescClassification.Dynamic: + case MethodDescClassification.EEImpl: + case MethodDescClassification.Array: break; // These have stored sigs default: @@ -793,7 +793,7 @@ And the various apis are implemented with the following algorithms { MethodDesc methodDesc = _methodDescs[methodDescHandle.Address]; - if (methodDesc.Classification != MethodDescClassification.mcDynamic) + if (methodDesc.Classification != MethodDescClassification.Dynamic) { return false; } @@ -807,7 +807,7 @@ And the various apis are implemented with the following algorithms { MethodDesc methodDesc = _methodDescs[methodDescHandle.Address]; - if (methodDesc.Classification != MethodDescClassification.mcDynamic) + if (methodDesc.Classification != MethodDescClassification.Dynamic) { return false; } diff --git a/src/coreclr/vm/classcompat.cpp b/src/coreclr/vm/classcompat.cpp index ece9384703b2b8..f2aa54a9e10ca1 100644 --- a/src/coreclr/vm/classcompat.cpp +++ b/src/coreclr/vm/classcompat.cpp @@ -2705,13 +2705,13 @@ VOID MethodTableBuilder::EnumerateClassMethods() if (dwMethodRVA == 0) Classification = mcFCall; else - Classification = mcNDirect; + Classification = mcPInvoke; } // The NAT_L attribute is present, marking this method as NDirect else { CONSISTENCY_CHECK(hr == S_OK); - Classification = mcNDirect; + Classification = mcPInvoke; } } else if (IsMiRuntime(dwImplFlags)) @@ -2840,7 +2840,7 @@ VOID MethodTableBuilder::EnumerateClassMethods() } BYTE type; - if ((Classification & mdfClassification) == mcNDirect) + if ((Classification & mdfClassification) == mcPInvoke) { type = METHOD_TYPE_NDIRECT; } diff --git a/src/coreclr/vm/method.cpp b/src/coreclr/vm/method.cpp index d8313fdc965162..35c06ac2a79004 100644 --- a/src/coreclr/vm/method.cpp +++ b/src/coreclr/vm/method.cpp @@ -68,7 +68,7 @@ static_assert_no_msg((sizeof(DynamicMethodDesc) & MethodDesc::ALIGNMENT_MASK #define METHOD_DESC_SIZES(adjustment) \ adjustment + sizeof(MethodDesc), /* mcIL */ \ adjustment + sizeof(FCallMethodDesc), /* mcFCall */ \ - adjustment + sizeof(NDirectMethodDesc), /* mcNDirect */ \ + adjustment + sizeof(NDirectMethodDesc), /* mcPInvoke */ \ adjustment + sizeof(EEImplMethodDesc), /* mcEEImpl */ \ adjustment + sizeof(ArrayMethodDesc), /* mcArray */ \ adjustment + sizeof(InstantiatedMethodDesc), /* mcInstantiated */ \ @@ -2539,7 +2539,7 @@ BOOL MethodDesc::MayHaveNativeCode() break; case mcFCall: // FCalls do not have real native code. return FALSE; - case mcNDirect: // NDirect never have native code (note that the NDirect method + case mcPInvoke: // NDirect never have native code (note that the NDirect method return FALSE; // does not appear as having a native code even for stubs as IL) case mcEEImpl: // Runtime provided implementation. No native code. return FALSE; diff --git a/src/coreclr/vm/method.hpp b/src/coreclr/vm/method.hpp index a38902b38e6fab..2e275c2656dbd2 100644 --- a/src/coreclr/vm/method.hpp +++ b/src/coreclr/vm/method.hpp @@ -93,7 +93,7 @@ enum MethodClassification { mcIL = 0, // IL mcFCall = 1, // FCall (also includes tlbimped ctor, Delegate ctor) - mcNDirect = 2, // N/Direct + mcPInvoke = 2, // PInvoke method also known as N/Direct in this codebase mcEEImpl = 3, // special method; implementation provided by EE (like Delegate Invoke) mcArray = 4, // Array ECall mcInstantiated = 5, // Instantiated generic methods, including descriptors @@ -637,7 +637,7 @@ class MethodDesc inline DWORD IsNDirect() { LIMITED_METHOD_DAC_CONTRACT; - return mcNDirect == GetClassification(); + return mcPInvoke == GetClassification(); } inline DWORD IsInterface() diff --git a/src/coreclr/vm/methodtablebuilder.cpp b/src/coreclr/vm/methodtablebuilder.cpp index 8ed085986a915d..e29fc2d6655228 100644 --- a/src/coreclr/vm/methodtablebuilder.cpp +++ b/src/coreclr/vm/methodtablebuilder.cpp @@ -3170,14 +3170,14 @@ MethodTableBuilder::EnumerateClassMethods() } else { - type = mcNDirect; + type = mcPInvoke; } } // The NAT_L attribute is present, marking this method as NDirect else { CONSISTENCY_CHECK(hr == S_OK); - type = mcNDirect; + type = mcPInvoke; } } else if (IsMiRuntime(dwImplFlags)) @@ -5080,7 +5080,7 @@ MethodTableBuilder::ValidateMethods() { BuildMethodTableThrowException(IDS_CLASSLOAD_BAD_UNMANAGED_RVA, it.Token()); } - if (it.MethodType() != mcNDirect) + if (it.MethodType() != mcPInvoke) { BuildMethodTableThrowException(BFA_BAD_UNMANAGED_ENTRY_POINT); } @@ -6086,7 +6086,7 @@ MethodTableBuilder::InitMethodDesc( switch (Classification) { - case mcNDirect: + case mcPInvoke: { // NDirect specific initialization. NDirectMethodDesc *pNewNMD = (NDirectMethodDesc*)pNewMD; diff --git a/src/native/managed/cdacreader/src/Contracts/RuntimeTypeSystem_1.cs b/src/native/managed/cdacreader/src/Contracts/RuntimeTypeSystem_1.cs index cd58f2f5960e14..a4f0186be88c90 100644 --- a/src/native/managed/cdacreader/src/Contracts/RuntimeTypeSystem_1.cs +++ b/src/native/managed/cdacreader/src/Contracts/RuntimeTypeSystem_1.cs @@ -73,17 +73,17 @@ internal enum TypeHandleBits ValidMask = 2, } - internal enum MethodDescClassification + internal enum MethodClassification { - mcIL = 0, // IL - mcFCall = 1, // FCall (also includes tlbimped ctor, Delegate ctor) - mcNDirect = 2, // N/Direct - mcEEImpl = 3, // special method; implementation provided by EE (like Delegate Invoke) - mcArray = 4, // Array ECall - mcInstantiated = 5, // Instantiated generic methods, including descriptors - // for both shared and unshared code (see InstantiatedMethodDesc) - mcComInterop = 6, - mcDynamic = 7, // for method desc with no metadata behind + IL = 0, // IL + FCall = 1, // FCall (also includes tlbimped ctor, Delegate ctor) + PInvoke = 2, // PInvoke Method + EEImpl = 3, // special method; implementation provided by EE (like Delegate Invoke) + Array = 4, // Array ECall + Instantiated = 5, // Instantiated generic methods, including descriptors + // for both shared and unshared code (see InstantiatedMethodDesc) + ComInterop = 6, + Dynamic = 7, // for method desc with no metadata behind } [Flags] @@ -122,28 +122,124 @@ internal MethodDesc(Target target, TargetPointer methodDescPointer, Data.MethodD _desc = desc; _chunk = chunk; Address = methodDescPointer; + + Token = ComputeToken(target, desc, chunk); } public TargetPointer MethodTable => _chunk.MethodTable; public ushort Slot => _desc.Slot; - public uint Token + public uint Token { get; } + + private static uint ComputeToken(Target target, Data.MethodDesc desc, Data.MethodDescChunk chunk) { - get + int tokenRemainderBitCount = target.ReadGlobal(Constants.Globals.MethodDescTokenRemainderBitCount); + int tokenRangeBitCount = 24 - tokenRemainderBitCount; + uint allRidBitsSet = 0xFFFFFF; + uint tokenRemainderMask = allRidBitsSet >> tokenRangeBitCount; + uint tokenRangeMask = allRidBitsSet >> tokenRemainderBitCount; + + uint tokenRemainder = (uint)(desc.Flags3AndTokenRemainder & tokenRemainderMask); + uint tokenRange = ((uint)(chunk.FlagsAndTokenRange & tokenRangeMask)) << tokenRemainderBitCount; + + return 0x06000000 | tokenRange | tokenRemainder; + } + + public MethodClassification Classification => (MethodClassification)((int)_desc.Flags & (int)MethodDescFlags.ClassificationMask); + } + + private class InstantiatedMethodDesc : IData + { + public static InstantiatedMethodDesc Create(Target target, TargetPointer address) => new InstantiatedMethodDesc(target, address); + + private readonly TargetPointer _address; + private readonly Data.InstantiatedMethodDesc _desc; + + private InstantiatedMethodDesc(Target target, TargetPointer methodDescPointer) + { + _address = methodDescPointer; + RuntimeTypeSystem_1 rts = (RuntimeTypeSystem_1)target.Contracts.RuntimeTypeSystem; + _desc = target.ProcessedData.GetOrAdd(methodDescPointer); + + int numGenericArgs = _desc.NumGenericArgs; + TargetPointer perInstInfo = _desc.PerInstInfo; + if ((perInstInfo == TargetPointer.Null) || (numGenericArgs == 0)) + { + Instantiation = System.Array.Empty(); + } + else { - int tokenRemainderBitCount = _target.ReadGlobal(Constants.Globals.MethodDescTokenRemainderBitCount); - int tokenRangeBitCount = 24 - tokenRemainderBitCount; - uint allRidBitsSet = 0xFFFFFF; - uint tokenRemainderMask = allRidBitsSet >> tokenRangeBitCount; - uint tokenRangeMask = allRidBitsSet >> tokenRemainderBitCount; + Instantiation = new TypeHandle[numGenericArgs]; + for (int i = 0; i < numGenericArgs; i++) + { + Instantiation[i] = rts.GetTypeHandle(target.ReadPointer(perInstInfo + (ulong)target.PointerSize * (ulong)i)); + } + } + } + + private bool HasFlags(InstantiatedMethodDescFlags2 mask, InstantiatedMethodDescFlags2 flags) => (_desc.Flags2 & (ushort)mask) == (ushort)flags; + internal bool IsWrapperStubWithInstantiations => HasFlags(InstantiatedMethodDescFlags2.KindMask, InstantiatedMethodDescFlags2.WrapperStubWithInstantiations); + internal bool IsGenericMethodDefinition => HasFlags(InstantiatedMethodDescFlags2.KindMask, InstantiatedMethodDescFlags2.GenericMethodDefinition); + internal bool HasPerInstInfo => _desc.PerInstInfo != TargetPointer.Null; + internal bool HasMethodInstantiation => IsGenericMethodDefinition || HasPerInstInfo; + public TypeHandle[] Instantiation { get; } + } + + private class DynamicMethodDesc : IData + { + public static DynamicMethodDesc Create(Target target, TargetPointer address) => new DynamicMethodDesc(target, address); + + private readonly TargetPointer _address; + private readonly Data.DynamicMethodDesc _desc; + private readonly Data.StoredSigMethodDesc _storedSigDesc; - uint tokenRemainder = (uint)(_desc.Flags3AndTokenRemainder & tokenRemainderMask); - uint tokenRange = ((uint)(_chunk.FlagsAndTokenRange & tokenRangeMask)) << tokenRemainderBitCount; + private DynamicMethodDesc(Target target, TargetPointer methodDescPointer) + { + _address = methodDescPointer; + List nameBytes = new(); + _desc = target.ProcessedData.GetOrAdd(methodDescPointer); - return 0x06000000 | tokenRange | tokenRemainder; + if (_desc.MethodName != TargetPointer.Null) + { + TargetPointer currentNameAddress = _desc.MethodName; + do + { + byte nameByte = target.Read(currentNameAddress); + + if (nameByte == 0) + break; + + nameBytes.Add(nameByte); + currentNameAddress++; + } while (true); + + MethodName = nameBytes.ToArray(); + } + else + { + MethodName = System.Array.Empty(); } + + _storedSigDesc = target.ProcessedData.GetOrAdd(methodDescPointer); } - public MethodDescClassification Classification => (MethodDescClassification)((int)_desc.Flags & (int)MethodDescFlags.ClassificationMask); + public byte[] MethodName { get; } + public DynamicMethodDescExtendedFlags ExtendedFlags => (DynamicMethodDescExtendedFlags)_storedSigDesc.ExtendedFlags; + + public bool IsLCGMethod => ExtendedFlags.HasFlag(DynamicMethodDescExtendedFlags.IsLCGMethod); + public bool IsILStub => ExtendedFlags.HasFlag(DynamicMethodDescExtendedFlags.IsILStub); + } + + private class StoredSigMethodDesc : IData + { + public static StoredSigMethodDesc Create(Target target, TargetPointer address) => new StoredSigMethodDesc(target, address); + + public byte[] Signature { get; } + private StoredSigMethodDesc(Target target, TargetPointer methodDescPointer) + { + Data.StoredSigMethodDesc storedSigMethodDesc = target.ProcessedData.GetOrAdd(methodDescPointer); + Signature = new byte[storedSigMethodDesc.cSig]; + target.ReadBuffer(storedSigMethodDesc.Sig, Signature.AsSpan()); + } } internal RuntimeTypeSystem_1(Target target, TargetPointer freeObjectMethodTablePointer, ulong methodDescAlignment) @@ -547,50 +643,43 @@ public MethodDescHandle GetMethodDescHandle(TargetPointer methodDescPointer) public TargetPointer GetMethodTable(MethodDescHandle methodDescHandle) => _methodDescs[methodDescHandle.Address].MethodTable; - public bool IsGenericMethodDefinition(MethodDescHandle methodDescHandle) + private InstantiatedMethodDesc AsInstantiatedMethodDesc(MethodDesc methodDesc) { - MethodDesc methodDesc = _methodDescs[methodDescHandle.Address]; + Debug.Assert(methodDesc.Classification == MethodClassification.Instantiated); + return _target.ProcessedData.GetOrAdd(methodDesc.Address); + } - if (methodDesc.Classification != MethodDescClassification.mcInstantiated) - return false; + private DynamicMethodDesc AsDynamicMethodDesc(MethodDesc methodDesc) + { + Debug.Assert(methodDesc.Classification == MethodClassification.Dynamic); + return _target.ProcessedData.GetOrAdd(methodDesc.Address); + } - Data.InstantiatedMethodDesc instantiatedMethodDesc = _target.ProcessedData.GetOrAdd(methodDescHandle.Address); - return ((int)instantiatedMethodDesc.Flags2 & (int)InstantiatedMethodDescFlags2.KindMask) == (int)InstantiatedMethodDescFlags2.GenericMethodDefinition; + private StoredSigMethodDesc AsStoredSigMethodDesc(MethodDesc methodDesc) + { + Debug.Assert(methodDesc.Classification == MethodClassification.Dynamic || + methodDesc.Classification == MethodClassification.EEImpl || + methodDesc.Classification == MethodClassification.Array); + return _target.ProcessedData.GetOrAdd(methodDesc.Address); } - public ReadOnlySpan GetGenericMethodInstantiation(MethodDescHandle methodDescHandle) + public bool IsGenericMethodDefinition(MethodDescHandle methodDescHandle) { MethodDesc methodDesc = _methodDescs[methodDescHandle.Address]; - if (methodDesc.Classification != MethodDescClassification.mcInstantiated) - return default; - - return _target.ProcessedData.GetOrAdd (methodDescHandle.Address).TypeHandles; + if (methodDesc.Classification != MethodClassification.Instantiated) + return false; + return AsInstantiatedMethodDesc(methodDesc).IsGenericMethodDefinition; } - private class MethodInstantiation : IData + public ReadOnlySpan GetGenericMethodInstantiation(MethodDescHandle methodDescHandle) { - public static MethodInstantiation Create(Target target, TargetPointer address) => new MethodInstantiation(target, address); - - public TypeHandle[] TypeHandles { get; } - private MethodInstantiation(Target target, TargetPointer methodDescPointer) - { - RuntimeTypeSystem_1 rts = (RuntimeTypeSystem_1)target.Contracts.RuntimeTypeSystem; - Data.InstantiatedMethodDesc instantiatedMethodDesc = target.ProcessedData.GetOrAdd(methodDescPointer); + MethodDesc methodDesc = _methodDescs[methodDescHandle.Address]; - int numGenericArgs = instantiatedMethodDesc.NumGenericArgs; - TargetPointer perInstInfo = instantiatedMethodDesc.PerInstInfo; - if (perInstInfo == TargetPointer.Null) - { - numGenericArgs = 0; - } + if (methodDesc.Classification != MethodClassification.Instantiated) + return default; - TypeHandles = new TypeHandle[numGenericArgs]; - for (int i = 0; i < numGenericArgs; i++) - { - TypeHandles[i] = rts.GetTypeHandle(target.ReadPointer(perInstInfo + (ulong)target.PointerSize * (ulong)i)); - } - } + return AsInstantiatedMethodDesc(methodDesc).Instantiation; } public uint GetMethodToken(MethodDescHandle methodDescHandle) @@ -603,7 +692,7 @@ public bool IsArrayMethod(MethodDescHandle methodDescHandle, out ArrayFunctionTy { MethodDesc methodDesc = _methodDescs[methodDescHandle.Address]; - if (methodDesc.Classification != MethodDescClassification.mcArray) + if (methodDesc.Classification != MethodClassification.Array) { functionType = default; return false; @@ -627,62 +716,25 @@ public bool IsDynamicMethod(MethodDescHandle methodDescHandle, out ReadOnlySpan< { MethodDesc methodDesc = _methodDescs[methodDescHandle.Address]; - if (methodDesc.Classification != MethodDescClassification.mcDynamic) + if (methodDesc.Classification != MethodClassification.Dynamic) { methodName = default; return false; } - methodName = _target.ProcessedData.GetOrAdd(methodDescHandle.Address).MethodName; + methodName = AsDynamicMethodDesc(methodDesc).MethodName; return true; } - private class DynamicMethodDesc : IData - { - public static DynamicMethodDesc Create(Target target, TargetPointer address) => new DynamicMethodDesc(target, address); - - public byte[] MethodName { get; } - public DynamicMethodDescExtendedFlags ExtendedFlags { get; } - private DynamicMethodDesc(Target target, TargetPointer methodDescPointer) - { - List nameBytes = new(); - Data.DynamicMethodDesc dynamicMethodDesc = target.ProcessedData.GetOrAdd(methodDescPointer); - - if (dynamicMethodDesc.MethodName != TargetPointer.Null) - { - TargetPointer currentNameAddress = dynamicMethodDesc.MethodName; - do - { - byte nameByte = target.Read(currentNameAddress); - - if (nameByte == 0) - break; - - nameBytes.Add(nameByte); - currentNameAddress++; - } while (true); - - MethodName = nameBytes.ToArray(); - } - else - { - MethodName = System.Array.Empty(); - } - - Data.StoredSigMethodDesc storedSigMethodDesc = target.ProcessedData.GetOrAdd(methodDescPointer); - ExtendedFlags = (DynamicMethodDescExtendedFlags)storedSigMethodDesc.ExtendedFlags; - } - } - public bool IsStoredSigMethodDesc(MethodDescHandle methodDescHandle, out ReadOnlySpan signature) { MethodDesc methodDesc = _methodDescs[methodDescHandle.Address]; switch (methodDesc.Classification) { - case MethodDescClassification.mcDynamic: - case MethodDescClassification.mcEEImpl: - case MethodDescClassification.mcArray: + case MethodClassification.Dynamic: + case MethodClassification.EEImpl: + case MethodClassification.Array: break; // These have stored sigs default: @@ -690,44 +742,31 @@ public bool IsStoredSigMethodDesc(MethodDescHandle methodDescHandle, out ReadOnl return false; } - signature = _target.ProcessedData.GetOrAdd(methodDescHandle.Address).Signature; + signature = AsStoredSigMethodDesc(methodDesc).Signature; return true; } - private class StoredSigMethodDesc: IData - { - public static StoredSigMethodDesc Create(Target target, TargetPointer address) => new StoredSigMethodDesc(target, address); - - public byte[] Signature { get; } - private StoredSigMethodDesc(Target target, TargetPointer methodDescPointer) - { - Data.StoredSigMethodDesc storedSigMethodDesc = target.ProcessedData.GetOrAdd(methodDescPointer); - Signature = new byte[storedSigMethodDesc.cSig]; - target.ReadBuffer(storedSigMethodDesc.Sig, Signature.AsSpan()); - } - } - public bool IsLCGMethod(MethodDescHandle methodDescHandle) { MethodDesc methodDesc = _methodDescs[methodDescHandle.Address]; - if (methodDesc.Classification != MethodDescClassification.mcDynamic) + if (methodDesc.Classification != MethodClassification.Dynamic) { return false; } - return _target.ProcessedData.GetOrAdd(methodDescHandle.Address).ExtendedFlags.HasFlag(DynamicMethodDescExtendedFlags.IsLCGMethod); + return AsDynamicMethodDesc(methodDesc).IsLCGMethod; } public bool IsILStub(MethodDescHandle methodDescHandle) { MethodDesc methodDesc = _methodDescs[methodDescHandle.Address]; - if (methodDesc.Classification != MethodDescClassification.mcDynamic) + if (methodDesc.Classification != MethodClassification.Dynamic) { return false; } - return _target.ProcessedData.GetOrAdd(methodDescHandle.Address).ExtendedFlags.HasFlag(DynamicMethodDescExtendedFlags.IsILStub); + return AsDynamicMethodDesc(methodDesc).IsILStub; } } From bcf9fba627c9a63482ae2ba4e79140280c9825d7 Mon Sep 17 00:00:00 2001 From: David Wrighton Date: Mon, 12 Aug 2024 10:02:06 -0700 Subject: [PATCH 38/38] Adjust cdac naming to new model --- docs/design/datacontracts/RuntimeTypeSystem.md | 16 +++++++++------- .../src/Contracts/RuntimeTypeSystem.cs | 12 +++++++----- .../src/Contracts/RuntimeTypeSystem_1.cs | 8 ++++---- .../managed/cdacreader/src/Legacy/SOSDacImpl.cs | 2 +- .../cdacreader/src/Legacy/TypeNameBuilder.cs | 8 ++++---- 5 files changed, 25 insertions(+), 21 deletions(-) diff --git a/docs/design/datacontracts/RuntimeTypeSystem.md b/docs/design/datacontracts/RuntimeTypeSystem.md index ca4cd8986d317f..3e6fefa0790735 100644 --- a/docs/design/datacontracts/RuntimeTypeSystem.md +++ b/docs/design/datacontracts/RuntimeTypeSystem.md @@ -121,19 +121,21 @@ partial interface IRuntimeTypeSystem : IContract // An array method is also a StoredSigMethodDesc public virtual bool IsArrayMethod(MethodDescHandle methodDesc, out ArrayFunctionType functionType); - // Return true if a MethodDesc represents a dynamically generated method, either an IL Stub dynamically + // Return true if a MethodDesc represents a method without metadata method, either an IL Stub dynamically // generated by the runtime, or a MethodDesc that describes a method represented by the System.Reflection.Emit.DynamicMethod class - // A dynamic method is also a StoredSigMethodDesc - public virtual bool IsDynamicMethod(MethodDescHandle methodDesc, out ReadOnlySpan methodName); + // Or something else similar. + // A no metadata method is also a StoredSigMethodDesc + public virtual bool IsNoMetadataMethod(MethodDescHandle methodDesc, out ReadOnlySpan methodName); // A StoredSigMethodDesc is a MethodDesc for which the signature isn't found in metadata. public virtual bool IsStoredSigMethodDesc(MethodDescHandle methodDesc, out ReadOnlySpan signature); // Return true for a MethodDesc that describes a method represented by the System.Reflection.Emit.DynamicMethod class - // A LCG method is also a StoredSigMethodDesc - public virtual bool IsLCGMethod(MethodDescHandle methodDesc); + // A DynamicMethod is also a StoredSigMethodDesc, and a NoMetadataMethod + public virtual bool IsDynamicMethod(MethodDescHandle methodDesc); // Return true if a MethodDesc represents an IL Stub dynamically generated by the runtime + // A IL Stub method is also a StoredSigMethodDesc, and a NoMetadataMethod public virtual bool IsILStub(MethodDescHandle methodDesc); } ``` @@ -749,7 +751,7 @@ And the various apis are implemented with the following algorithms return true; } - public bool IsDynamicMethod(MethodDescHandle methodDescHandle, out ReadOnlySpan methodName) + public bool IsNoMetadataMethod(MethodDescHandle methodDescHandle, out ReadOnlySpan methodName) { MethodDesc methodDesc = _methodDescs[methodDescHandle.Address]; @@ -789,7 +791,7 @@ And the various apis are implemented with the following algorithms return true; } - public bool IsLCGMethod(MethodDescHandle methodDescHandle) + public bool IsDynamicMethod(MethodDescHandle methodDescHandle) { MethodDesc methodDesc = _methodDescs[methodDescHandle.Address]; diff --git a/src/native/managed/cdacreader/src/Contracts/RuntimeTypeSystem.cs b/src/native/managed/cdacreader/src/Contracts/RuntimeTypeSystem.cs index 9b69dac7500a9d..5a87fe72f1535a 100644 --- a/src/native/managed/cdacreader/src/Contracts/RuntimeTypeSystem.cs +++ b/src/native/managed/cdacreader/src/Contracts/RuntimeTypeSystem.cs @@ -148,18 +148,20 @@ static IContract IContract.Create(Target target, int version) // An array method is also a StoredSigMethodDesc public virtual bool IsArrayMethod(MethodDescHandle methodDesc, out ArrayFunctionType functionType) => throw new NotImplementedException(); - // Return true if a MethodDesc represents a dynamically generated method, either an IL Stub dynamically + // Return true if a MethodDesc represents a method without metadata method, either an IL Stub dynamically // generated by the runtime, or a MethodDesc that describes a method represented by the System.Reflection.Emit.DynamicMethod class - // A dynamic method is also a StoredSigMethodDesc - public virtual bool IsDynamicMethod(MethodDescHandle methodDesc, out ReadOnlySpan methodName) => throw new NotImplementedException(); + // Or something else similar. + // A no metadata method is also a StoredSigMethodDesc + public virtual bool IsNoMetadataMethod(MethodDescHandle methodDesc, out ReadOnlySpan methodName) => throw new NotImplementedException(); // A StoredSigMethodDesc is a MethodDesc for which the signature isn't found in metadata. public virtual bool IsStoredSigMethodDesc(MethodDescHandle methodDesc, out ReadOnlySpan signature) => throw new NotImplementedException(); // Return true for a MethodDesc that describes a method represented by the System.Reflection.Emit.DynamicMethod class - // A LCG method is also a StoredSigMethodDesc - public virtual bool IsLCGMethod(MethodDescHandle methodDesc) => throw new NotImplementedException(); + // A DynamicMethod is also a StoredSigMethodDesc, and a NoMetadataMethod + public virtual bool IsDynamicMethod(MethodDescHandle methodDesc) => throw new NotImplementedException(); // Return true if a MethodDesc represents an IL Stub dynamically generated by the runtime + // A IL Stub method is also a StoredSigMethodDesc, and a NoMetadataMethod public virtual bool IsILStub(MethodDescHandle methodDesc) => throw new NotImplementedException(); #endregion MethodDesc 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 a4f0186be88c90..cc15327ae16a71 100644 --- a/src/native/managed/cdacreader/src/Contracts/RuntimeTypeSystem_1.cs +++ b/src/native/managed/cdacreader/src/Contracts/RuntimeTypeSystem_1.cs @@ -225,7 +225,7 @@ private DynamicMethodDesc(Target target, TargetPointer methodDescPointer) public byte[] MethodName { get; } public DynamicMethodDescExtendedFlags ExtendedFlags => (DynamicMethodDescExtendedFlags)_storedSigDesc.ExtendedFlags; - public bool IsLCGMethod => ExtendedFlags.HasFlag(DynamicMethodDescExtendedFlags.IsLCGMethod); + public bool IsDynamicMethod => ExtendedFlags.HasFlag(DynamicMethodDescExtendedFlags.IsLCGMethod); public bool IsILStub => ExtendedFlags.HasFlag(DynamicMethodDescExtendedFlags.IsILStub); } @@ -712,7 +712,7 @@ public bool IsArrayMethod(MethodDescHandle methodDescHandle, out ArrayFunctionTy return true; } - public bool IsDynamicMethod(MethodDescHandle methodDescHandle, out ReadOnlySpan methodName) + public bool IsNoMetadataMethod(MethodDescHandle methodDescHandle, out ReadOnlySpan methodName) { MethodDesc methodDesc = _methodDescs[methodDescHandle.Address]; @@ -746,7 +746,7 @@ public bool IsStoredSigMethodDesc(MethodDescHandle methodDescHandle, out ReadOnl return true; } - public bool IsLCGMethod(MethodDescHandle methodDescHandle) + public bool IsDynamicMethod(MethodDescHandle methodDescHandle) { MethodDesc methodDesc = _methodDescs[methodDescHandle.Address]; @@ -755,7 +755,7 @@ public bool IsLCGMethod(MethodDescHandle methodDescHandle) return false; } - return AsDynamicMethodDesc(methodDesc).IsLCGMethod; + return AsDynamicMethodDesc(methodDesc).IsDynamicMethod; } public bool IsILStub(MethodDescHandle methodDescHandle) diff --git a/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs b/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs index 73649f9802b7d0..c5dc6ce8719efa 100644 --- a/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs +++ b/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs @@ -136,7 +136,7 @@ public unsafe int GetMethodDescName(ulong methodDesc, uint count, char* name, ui catch { hr = HResults.E_FAIL; - if (rtsContract.IsDynamicMethod(methodDescHandle, out _)) + if (rtsContract.IsNoMetadataMethod(methodDescHandle, out _)) { // In heap dumps, trying to format the signature can fail // in certain cases. diff --git a/src/native/managed/cdacreader/src/Legacy/TypeNameBuilder.cs b/src/native/managed/cdacreader/src/Legacy/TypeNameBuilder.cs index b5f89821d7c228..3e7dac6536494b 100644 --- a/src/native/managed/cdacreader/src/Legacy/TypeNameBuilder.cs +++ b/src/native/managed/cdacreader/src/Legacy/TypeNameBuilder.cs @@ -68,10 +68,10 @@ public static void AppendMethodImpl(Target target, StringBuilder stringBuilder, TypeHandle th = default; Contracts.ModuleHandle module = default; - bool isDynamicMethod = runtimeTypeSystem.IsDynamicMethod(method, out methodNameSpan); - if (isDynamicMethod) + bool isNoMetadataMethod = runtimeTypeSystem.IsNoMetadataMethod(method, out methodNameSpan); + if (isNoMetadataMethod) { - if (runtimeTypeSystem.IsLCGMethod(method)) + if (runtimeTypeSystem.IsDynamicMethod(method)) { stringBuilder.Append("DynamicClass"); } @@ -88,7 +88,7 @@ public static void AppendMethodImpl(Target target, StringBuilder stringBuilder, stringBuilder.Append('.'); - if (isDynamicMethod) + if (isNoMetadataMethod) { stringBuilder.Append(Encoding.UTF8.GetString(methodNameSpan)); }