diff --git a/docs/design/datacontracts/EcmaMetadata.md b/docs/design/datacontracts/EcmaMetadata.md index a5b445f10dce6a..50054a06fa2924 100644 --- a/docs/design/datacontracts/EcmaMetadata.md +++ b/docs/design/datacontracts/EcmaMetadata.md @@ -23,9 +23,36 @@ Data descriptors used: | --- | --- | --- | | `Module` | `Base` | Pointer to start of PE file in memory | | `Module` | `DynamicMetadata` | Pointer to saved metadata for reflection emit modules | +| `Module` | `MetadataGeneration` | Counter incremented each time a dynamic module re-serializes its saved metadata copy | | `Module` | `FieldDefToDescMap` | Mapping table | | `DynamicMetadata` | `Size` | Size of the dynamic metadata blob (as a 32bit uint) | | `DynamicMetadata` | `Data` | Start of dynamic metadata data array | +| `PEAssembly` | `MDImport` | An `MDInternalRW` when `MetadataGeneration` is non-zero and module is not dynamic | +| `MDInternalRW` | `Stgdb` | Pointer to the read-write storage database | +| `CLiteWeightStgdbRW` | `MiniMd` | Address of the embedded `CMiniMdRW` model | +| `CLiteWeightStgdbRW` | `MetadataAddress` | Pointer to the metadata image | +| `CMiniMdRW` | `Schema` | Address of the embedded `CMiniMdSchema` | +| `CMiniMdRW` | `TableCount` | Number of valid tables | +| `CMiniMdRW` | `All4ByteColumns` | Whether all variable-width columns are 4 bytes wide | +| `CMiniMdRW` | `Tables` | Address of the first table's record storage pool | +| `CMiniMdRW` | `StringHeap` | Address of the string heap's storage pool | +| `CMiniMdRW` | `BlobHeap` | Address of the blob heap's storage pool | +| `CMiniMdRW` | `UserStringHeap` | Address of the user-string heap's storage pool | +| `CMiniMdRW` | `GuidHeap` | Address of the GUID heap's storage pool | +| `CMiniMdSchema` | `Heaps` | Heap-size flags byte | +| `CMiniMdSchema` | `Sorted` | Sorted-table bit mask | +| `CMiniMdSchema` | `RecordCounts` | Address of the inline per-table row count array | +| `StgPool` | `SegData` | Pointer to the head segment's data | +| `StgPool` | `NextSegment` | Pointer to the next pool segment | +| `StgPool` | `DataSize` | Live byte count of the head segment | +| `StgPoolSeg` | `SegData` | Pointer to this extension segment's data | +| `StgPoolSeg` | `NextSegment` | Pointer to the next pool segment, or null | +| `StgPoolSeg` | `DataSize` | Live byte count of this extension segment | + +Contracts used: +| Contract Name | +| --- | +| `Loader` | ```csharp @@ -75,152 +102,12 @@ MetadataReader? GetMetadata(ModuleHandle handle) } case AvailableMetadataType.ReadWrite: { - var targetEcmaMetadata = GetReadWriteMetadata(handle); - - // From the multiple different target spans, we need to build a single - // contiguous ECMA-335 metadata blob. - BlobBuilder builder = new BlobBuilder(); - builder.WriteUInt32(0x424A5342); - - // major version - builder.WriteUInt16(1); - - // minor version - builder.WriteUInt16(1); - - // reserved - builder.WriteUInt32(0); - - string version = targetEcmaMetadata.Schema.MetadataVersion; - builder.WriteInt32(AlignUp(version.Length, 4)); - Write4ByteAlignedString(builder, version); - - // reserved - builder.WriteUInt16(0); - - // number of streams - ushort numStreams = 5; // #Strings, #US, #Blob, #GUID, #~ (metadata) - if (targetEcmaMetadata.Schema.VariableSizedColumnsAreAll4BytesLong) - { - // We direct MetadataReader to use 4-byte encoding for all variable-sized columns - // by providing the marker stream for a "minimal delta" image. - numStreams++; - } - builder.WriteUInt16(numStreams); - - // Write Stream headers - if (targetEcmaMetadata.Schema.VariableSizedColumnsAreAll4BytesLong) - { - // Write the #JTD stream to indicate that all variable-sized columns are 4 bytes long. - WriteStreamHeader(builder, "#JTD", 0).WriteInt32(builder.Count); - } - - BlobWriter stringsOffset = WriteStreamHeader(builder, "#Strings", (int)AlignUp(targetEcmaMetadata.StringHeap.Size, 4ul)); - BlobWriter blobOffset = WriteStreamHeader(builder, "#Blob", (int)targetEcmaMetadata.BlobHeap.Size); - BlobWriter guidOffset = WriteStreamHeader(builder, "#GUID", (int)targetEcmaMetadata.GuidHeap.Size); - BlobWriter userStringOffset = WriteStreamHeader(builder, "#US", (int)targetEcmaMetadata.UserStringHeap.Size); - - // We'll use the "uncompressed" tables stream name as the runtime may have created the *Ptr tables - // that are only present in the uncompressed tables stream. - BlobWriter tablesOffset = WriteStreamHeader(builder, "#-", 0); - - // Write the heap-style Streams - - stringsOffset.WriteInt32(builder.Count); - WriteTargetSpan(builder, targetEcmaMetadata.StringHeap); - for (ulong i = targetEcmaMetadata.StringHeap.Size; i < AlignUp(targetEcmaMetadata.StringHeap.Size, 4ul); i++) - { - builder.WriteByte(0); - } - - blobOffset.WriteInt32(builder.Count); - WriteTargetSpan(builder, targetEcmaMetadata.BlobHeap); - - guidOffset.WriteInt32(builder.Count); - WriteTargetSpan(builder, targetEcmaMetadata.GuidHeap); - - userStringOffset.WriteInt32(builder.Count); - WriteTargetSpan(builder, targetEcmaMetadata.UserStringHeap); - - // Write tables stream - tablesOffset.WriteInt32(builder.Count); - - // Write tables stream header - builder.WriteInt32(0); // reserved - builder.WriteByte(2); // major version - builder.WriteByte(0); // minor version - uint heapSizes = - (targetEcmaMetadata.Schema.LargeStringHeap ? 1u << 0 : 0) | - (targetEcmaMetadata.Schema.LargeBlobHeap ? 1u << 1 : 0) | - (targetEcmaMetadata.Schema.LargeGuidHeap ? 1u << 2 : 0); - - builder.WriteByte((byte)heapSizes); - builder.WriteByte(1); // reserved - - ulong validTables = 0; - for (int i = 0; i < targetEcmaMetadata.Schema.RowCount.Length; i++) - { - if (targetEcmaMetadata.Schema.RowCount[i] != 0) - { - validTables |= 1ul << i; - } - } - - ulong sortedTables = 0; - for (int i = 0; i < targetEcmaMetadata.Schema.IsSorted.Length; i++) - { - if (targetEcmaMetadata.Schema.IsSorted[i]) - { - sortedTables |= 1ul << i; - } - } - - builder.WriteUInt64(validTables); - builder.WriteUInt64(sortedTables); - - foreach (int rowCount in targetEcmaMetadata.Schema.RowCount) - { - if (rowCount > 0) - { - builder.WriteInt32(rowCount); - } - } - - // Write the tables - foreach (TargetSpan span in targetEcmaMetadata.Tables) - { - WriteTargetSpan(builder, span); - } - - MemoryStream metadataStream = new MemoryStream(); - builder.WriteContentTo(metadataStream); - return MetadataReaderProvider.FromMetadataStream(metadataStream).GetMetadataReader(); - - void WriteTargetSpan(BlobBuilder builder, TargetSpan span) - { - Blob blob = builder.ReserveBytes(checked((int)span.Size)); - _target.ReadBuffer(span.Address, blob.GetBytes().AsSpan()); - } - - static BlobWriter WriteStreamHeader(BlobBuilder builder, string name, int size) - { - BlobWriter offset = new(builder.ReserveBytes(4)); - builder.WriteInt32(size); - Write4ByteAlignedString(builder, name); - return offset; - } - - static void Write4ByteAlignedString(BlobBuilder builder, string value) - { - int bufferStart = builder.Count; - builder.WriteUTF8(value); - builder.WriteByte(0); - int stringEnd = builder.Count; - for (int i = stringEnd; i < bufferStart + AlignUp(value.Length, 4); i++) - { - builder.WriteByte(0); - } - } + // From the ModuleHandle, walk Module -> PEAssembly -> MDInternalRW -> CLiteWeightStgdbRW -> CMiniMdRW. + // Read the schema and whether every variable-width column is 4 bytes, which selects the #JTD "minimal delta" marker when reserializing. + // Each heap and each entry of the Tables array is a storage pool; read its head segment with the + // StgPool descriptor and walk the rest of the chain as bare StgPoolSeg via NextSegment, concatenating every segment's + // data into a contiguous buffer. The resulting heaps and per-table record blobs, + // together with the schema, are returned as TargetEcmaMetadata and reserialized into an ECMA-335 image. } } } @@ -229,68 +116,6 @@ MetadataReader? GetMetadata(ModuleHandle handle) ### Helper Methods ``` csharp -using System; -using System.Numerics; - -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; -} - -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] enum AvailableMetadataType @@ -303,38 +128,32 @@ enum AvailableMetadataType AvailableMetadataType GetAvailableMetadataType(ModuleHandle handle) { - Data.Module module = new Data.Module(Target, handle.Address); - AvailableMetadataType flags = AvailableMetadataType.None; TargetPointer dynamicMetadata = Target.ReadPointer(handle.Address + /* Module::DynamicMetadata offset */); + uint metadataGeneration = Target.Read(handle.Address + /* Module::MetadataGeneration offset */); if (dynamicMetadata != TargetPointer.Null) + { flags |= AvailableMetadataType.ReadWriteSavedCopy; + } + else if (metadataGeneration != 0) + { + flags |= AvailableMetadataType.ReadWrite; + } else + { flags |= AvailableMetadataType.ReadOnly; + } return flags; } TargetSpan GetReadWriteSavedMetadataAddress(ModuleHandle handle) { - Data.Module module = new Data.Module(Target, handle.Address); TargetPointer dynamicMetadata = Target.ReadPointer(handle.Address + /* Module::DynamicMetadata offset */); - ulong size = Target.Read(handle.Address + /* DynamicMetadata::Size offset */); TargetPointer result = handle.Address + /* DynamicMetadata::Data offset */; return new(result, size); } - -TargetEcmaMetadata GetReadWriteMetadata(ModuleHandle handle) -{ - // [cdac] TODO. -} - -T AlignUp(T input, T alignment) - where T : IBinaryInteger -{ - return input + (alignment - T.One) & ~(alignment - T.One); -} ``` diff --git a/src/coreclr/md/enc/liteweightstgdbrw.cpp b/src/coreclr/md/enc/liteweightstgdbrw.cpp index df43ef1bcfdcb3..5a867df0e1f98c 100644 --- a/src/coreclr/md/enc/liteweightstgdbrw.cpp +++ b/src/coreclr/md/enc/liteweightstgdbrw.cpp @@ -186,6 +186,7 @@ CLiteWeightStgdbRW::InitFileForRead( if (SUCCEEDED(pStorage->OpenStream(MINIMAL_MD_STREAM, &cbData, &pvData))) { m_MiniMd.m_fMinimalDelta = TRUE; + m_MiniMd.m_fAll4ByteColumns = TRUE; } // Load the string pool. diff --git a/src/coreclr/md/enc/metamodelrw.cpp b/src/coreclr/md/enc/metamodelrw.cpp index 6d9f52fd4b7b67..7bb292e6f515ad 100644 --- a/src/coreclr/md/enc/metamodelrw.cpp +++ b/src/coreclr/md/enc/metamodelrw.cpp @@ -711,6 +711,7 @@ CMiniMdRW::CMiniMdRW() m_pHostFilter(0), m_pTokenRemapManager(0), m_fMinimalDelta(FALSE), + m_fAll4ByteColumns(FALSE), m_rENCRecs(0) { #ifdef _DEBUG @@ -1276,6 +1277,7 @@ CMiniMdRW::ComputeGrowLimits( m_limIx = USHRT_MAX << 1; m_limRid = USHRT_MAX << 1; m_eGrow = eg_grown; + m_fAll4ByteColumns = TRUE; } } // CMiniMdRW::ComputeGrowLimits @@ -3556,6 +3558,7 @@ CMiniMdRW::ExpandTables() // Remember that we've grown. m_eGrow = eg_grown; + m_fAll4ByteColumns = TRUE; m_maxRid = m_maxIx = UINT32_MAX; ErrExit: diff --git a/src/coreclr/md/inc/VerifyLayouts.inc b/src/coreclr/md/inc/VerifyLayouts.inc index 6521e4b474fe4d..97f841d10c8bf3 100644 --- a/src/coreclr/md/inc/VerifyLayouts.inc +++ b/src/coreclr/md/inc/VerifyLayouts.inc @@ -200,6 +200,7 @@ ALIGN_FIELD(CMiniMdRW, m_bSortable, sizeof(BYTE)*TBL_COUNT, sizeof(BYTE)) FIELD(CMiniMdRW, dbg_m_pLock, sizeof(void*)) #endif FIELD(CMiniMdRW, m_fMinimalDelta, 4) +FIELD(CMiniMdRW, m_fAll4ByteColumns, 4) FIELD(CMiniMdRW, m_rENCRecs, sizeof(void*)) END_TYPE(CMiniMdRW, 8) diff --git a/src/coreclr/md/inc/liteweightstgdb.h b/src/coreclr/md/inc/liteweightstgdb.h index 9b9536e5cb04b5..440fabbc28261c 100644 --- a/src/coreclr/md/inc/liteweightstgdb.h +++ b/src/coreclr/md/inc/liteweightstgdb.h @@ -15,6 +15,7 @@ #include "metadata.h" #include "metamodelro.h" #include "metamodelrw.h" +#include "cdacdata.h" #include "stgtiggerstorage.h" @@ -79,6 +80,7 @@ void CLiteWeightStgdb::Uninit() class CLiteWeightStgdbRW : public CLiteWeightStgdb { + friend struct ::cdac_data; friend class RegMeta; friend class VerifyLayoutsMD; friend HRESULT TranslateSigHelper( @@ -235,4 +237,11 @@ class CLiteWeightStgdbRW : public CLiteWeightStgdb #endif }; // class CLiteWeightStgdbRW +template<> +struct cdac_data +{ + static constexpr size_t MiniMd = offsetof(CLiteWeightStgdbRW, m_MiniMd); + static constexpr size_t MetadataAddress = offsetof(CLiteWeightStgdbRW, m_pvMd); +}; + #endif // __LiteWeightStgdb_h__ diff --git a/src/coreclr/md/inc/metamodel.h b/src/coreclr/md/inc/metamodel.h index 22ace7113f2aea..322c295ca291be 100644 --- a/src/coreclr/md/inc/metamodel.h +++ b/src/coreclr/md/inc/metamodel.h @@ -16,6 +16,7 @@ #include #include #include +#include "cdacdata.h" #include "../datablob.h" #include "../debug_metadata.h" @@ -403,6 +404,7 @@ class CMiniMdBase : public IMetaModelCommonRO { friend class VerifyLayoutsMD; // verifies class layout doesn't accidentally change + friend struct ::cdac_data; public: CMiniMdBase(); @@ -587,6 +589,13 @@ class CMiniMdBase : public IMetaModelCommonRO BOOL UsesAllocatedMemory(CMiniColDef* pCols); }; +template<> +struct cdac_data +{ + static constexpr size_t Schema = offsetof(CMiniMdBase, m_Schema); + static constexpr size_t TableCount = offsetof(CMiniMdBase, m_TblCount); +}; + #ifdef FEATURE_METADATA_RELEASE_MEMORY_ON_REOPEN #define MINIMD_POSSIBLE_INTERNAL_POINTER_EXPOSED() MarkUnsafeToDelete() diff --git a/src/coreclr/md/inc/metamodelrw.h b/src/coreclr/md/inc/metamodelrw.h index 175beaaeb20ff0..47661f5ebe2986 100644 --- a/src/coreclr/md/inc/metamodelrw.h +++ b/src/coreclr/md/inc/metamodelrw.h @@ -20,6 +20,7 @@ #include "metadatahash.h" #include "rwutil.h" #include "shash.h" +#include "cdacdata.h" #include "../heaps/export.h" #include "../tables/export.h" @@ -220,6 +221,7 @@ class CMiniMdRW : public CMiniMdTemplate friend class FilterTable; friend class ImportHelper; friend class VerifyLayoutsMD; + friend struct ::cdac_data; CMiniMdRW(); ~CMiniMdRW(); @@ -1328,6 +1330,7 @@ class CMiniMdRW : public CMiniMdTemplate private: BOOL m_fMinimalDelta; + BOOL m_fAll4ByteColumns; public: BOOL IsMinimalDelta() @@ -1396,4 +1399,15 @@ class CMiniMdRW : public CMiniMdTemplate }; // class CMiniMdRW : public CMiniMdTemplate +template<> +struct cdac_data +{ + static constexpr size_t All4ByteColumns = offsetof(CMiniMdRW, m_fAll4ByteColumns); + static constexpr size_t Tables = offsetof(CMiniMdRW, m_Tables); + static constexpr size_t StringHeap = offsetof(CMiniMdRW, m_StringHeap); + static constexpr size_t BlobHeap = offsetof(CMiniMdRW, m_BlobHeap); + static constexpr size_t UserStringHeap = offsetof(CMiniMdRW, m_UserStringHeap); + static constexpr size_t GuidHeap = offsetof(CMiniMdRW, m_GuidHeap); +}; + #endif // _METAMODELRW_H_ diff --git a/src/coreclr/md/inc/stgpool.h b/src/coreclr/md/inc/stgpool.h index 702f7542672827..0593326b2a4414 100644 --- a/src/coreclr/md/inc/stgpool.h +++ b/src/coreclr/md/inc/stgpool.h @@ -27,6 +27,7 @@ #include "sarray.h" #include "memoryrange.h" #include "../datablob.h" +#include "cdacdata.h" //***************************************************************************** // NOTE: @@ -49,8 +50,6 @@ class StgStringPool; class StgBlobPool; class StgCodePool; -template struct cdac_data; - // Perform binary search on index table. // class RIDBinarySearch : public CBinarySearch @@ -84,6 +83,7 @@ class RIDBinarySearch : public CBinarySearch class StgPoolSeg { friend class VerifyLayoutsMD; + friend struct ::cdac_data; public: StgPoolSeg() : m_pSegData((BYTE*)m_zeros), @@ -422,6 +422,7 @@ friend class StgBlobPool; friend class RecordPool; friend class CBlobPoolHash; friend class VerifyLayoutsMD; +friend struct ::cdac_data; public: StgPool(ULONG ulGrowInc=512, UINT32 nAlignment=4) : @@ -1210,6 +1211,22 @@ class StgBlobPool : public StgPool CBlobPoolHash m_Hash; // Hash table for lookups. }; // class StgBlobPool +template<> +struct cdac_data +{ + static constexpr size_t SegData = offsetof(StgPoolSeg, m_pSegData); + static constexpr size_t NextSegment = offsetof(StgPoolSeg, m_pNextSeg); + static constexpr size_t DataSize = offsetof(StgPoolSeg, m_cbSegNext); +}; + +template<> +struct cdac_data +{ + static constexpr size_t SegData = offsetof(StgPool, m_pSegData); + static constexpr size_t NextSegment = offsetof(StgPool, m_pNextSeg); + static constexpr size_t DataSize = offsetof(StgPool, m_cbSegNext); +}; + #ifdef _MSC_VER #pragma warning (default : 4355) #endif diff --git a/src/coreclr/vm/ceeload.cpp b/src/coreclr/vm/ceeload.cpp index 76655714601019..650e989b23e4f8 100644 --- a/src/coreclr/vm/ceeload.cpp +++ b/src/coreclr/vm/ceeload.cpp @@ -373,6 +373,7 @@ Module::Module(Assembly *pAssembly, PEAssembly *pPEAssembly) m_loaderAllocator = NULL; m_pDynamicMetadata = (TADDR)NULL; + m_dwMetadataGeneration = 0; m_pPEAssembly->AddRef(); } @@ -665,6 +666,7 @@ void Module::ApplyMetaData() // Ensure for MethodDef ulCount = GetMDImport()->GetCountWithTokenKind(mdtMethodDef) + 1; EnsureMethodDefCanBeStored(TokenFromRid(ulCount, mdtMethodDef)); + m_dwMetadataGeneration++; } // @@ -4032,6 +4034,7 @@ void ReflectionModule::CaptureModuleMetaDataToMemory() delete (uint32_t*)m_pDynamicMetadata; m_pDynamicMetadata = (TADDR)pBuffer.Extract(); + m_dwMetadataGeneration++; } // diff --git a/src/coreclr/vm/ceeload.h b/src/coreclr/vm/ceeload.h index 8a8349272025bc..218bc6e4ea5e62 100644 --- a/src/coreclr/vm/ceeload.h +++ b/src/coreclr/vm/ceeload.h @@ -1682,6 +1682,10 @@ class Module : public ModuleBase protected: TADDR m_pDynamicMetadata; + // Incremented each time a dynamic module re-serializes its metadata. + // Indicates update to out-of-process readers. + uint32_t m_dwMetadataGeneration; + public: #if !defined(DACCESS_COMPILE) PTR_Assembly GetNativeMetadataAssemblyRefFromCache(DWORD rid) @@ -1712,6 +1716,7 @@ struct cdac_data static constexpr size_t Flags = offsetof(Module, m_dwTransientFlags); static constexpr size_t LoaderAllocator = offsetof(Module, m_loaderAllocator); static constexpr size_t DynamicMetadata = offsetof(Module, m_pDynamicMetadata); + static constexpr size_t MetadataGeneration = offsetof(Module, m_dwMetadataGeneration); static constexpr size_t SimpleName = offsetof(Module, m_pSimpleName); static constexpr size_t Path = offsetof(Module, m_path); static constexpr size_t FileName = offsetof(Module, m_fileName); diff --git a/src/coreclr/vm/datadescriptor/CMakeLists.txt b/src/coreclr/vm/datadescriptor/CMakeLists.txt index dabe48a83d6fb7..66562719a37b0c 100644 --- a/src/coreclr/vm/datadescriptor/CMakeLists.txt +++ b/src/coreclr/vm/datadescriptor/CMakeLists.txt @@ -11,6 +11,8 @@ add_library(runtime_descriptor_interface INTERFACE) target_include_directories(runtime_descriptor_interface INTERFACE ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR}) + +target_compile_definitions(runtime_descriptor_interface INTERFACE -DFEATURE_METADATA_INTERNAL_APIS) add_dependencies(runtime_descriptor_interface cee_wks_core) generate_data_descriptors( LIBRARY_NAME cdac_contract_descriptor diff --git a/src/coreclr/vm/datadescriptor/datadescriptor.h b/src/coreclr/vm/datadescriptor/datadescriptor.h index 229284445cef9f..297a073fe58bd2 100644 --- a/src/coreclr/vm/datadescriptor/datadescriptor.h +++ b/src/coreclr/vm/datadescriptor/datadescriptor.h @@ -35,3 +35,7 @@ #ifdef HAVE_GCCOVER #include "gccover.h" #endif // HAVE_GCCOVER + +#include "stgpool.h" +#include "liteweightstgdb.h" +#include "mdinternalrw.h" diff --git a/src/coreclr/vm/datadescriptor/datadescriptor.inc b/src/coreclr/vm/datadescriptor/datadescriptor.inc index 065f950ab7b3c5..0f068d5490150b 100644 --- a/src/coreclr/vm/datadescriptor/datadescriptor.inc +++ b/src/coreclr/vm/datadescriptor/datadescriptor.inc @@ -306,6 +306,7 @@ CDAC_TYPE_FIELD(Module, T_POINTER, Base, cdac_data::Base) CDAC_TYPE_FIELD(Module, T_UINT32, Flags, cdac_data::Flags) CDAC_TYPE_FIELD(Module, T_POINTER, LoaderAllocator, cdac_data::LoaderAllocator) CDAC_TYPE_FIELD(Module, T_POINTER, DynamicMetadata, cdac_data::DynamicMetadata) +CDAC_TYPE_FIELD(Module, T_UINT32, MetadataGeneration, cdac_data::MetadataGeneration) CDAC_TYPE_FIELD(Module, T_POINTER, SimpleName, cdac_data::SimpleName) CDAC_TYPE_FIELD(Module, T_POINTER, Path, cdac_data::Path) CDAC_TYPE_FIELD(Module, T_POINTER, FileName, cdac_data::FileName) @@ -439,6 +440,7 @@ CDAC_TYPE_BEGIN(PEAssembly) CDAC_TYPE_INDETERMINATE(PEAssembly) CDAC_TYPE_FIELD(PEAssembly, T_POINTER, PEImage, cdac_data::PEImage) CDAC_TYPE_FIELD(PEAssembly, T_POINTER, AssemblyBinder, cdac_data::AssemblyBinder) +CDAC_TYPE_FIELD(PEAssembly, T_POINTER, MDImport, cdac_data::MDImport) CDAC_TYPE_END(PEAssembly) CDAC_TYPE_BEGIN(AssemblyBinder) @@ -614,6 +616,54 @@ CDAC_TYPE_FIELD(DynamicMetadata, T_UINT32, Size, cdac_data::Siz CDAC_TYPE_FIELD(DynamicMetadata, T_ARRAY(T_UINT8), Data, cdac_data::Data) CDAC_TYPE_END(DynamicMetadata) +CDAC_TYPE_BEGIN(MDInternalRW) +CDAC_TYPE_INDETERMINATE(MDInternalRW) +CDAC_TYPE_FIELD(MDInternalRW, T_POINTER, Stgdb, offsetof(MDInternalRW, m_pStgdb)) +CDAC_TYPE_END(MDInternalRW) + +CDAC_TYPE_BEGIN(CLiteWeightStgdbRW) +CDAC_TYPE_INDETERMINATE(CLiteWeightStgdbRW) +CDAC_TYPE_FIELD(CLiteWeightStgdbRW, TYPE(CMiniMdRW), MiniMd, cdac_data::MiniMd) +CDAC_TYPE_FIELD(CLiteWeightStgdbRW, T_POINTER, MetadataAddress, cdac_data::MetadataAddress) +CDAC_TYPE_END(CLiteWeightStgdbRW) + +CDAC_TYPE_BEGIN(CMiniMdRW) +CDAC_TYPE_INDETERMINATE(CMiniMdRW) +CDAC_TYPE_FIELD(CMiniMdRW, TYPE(CMiniMdSchema), Schema, cdac_data::Schema) +CDAC_TYPE_FIELD(CMiniMdRW, T_UINT32, TableCount, cdac_data::TableCount) +CDAC_TYPE_FIELD(CMiniMdRW, T_UINT32, All4ByteColumns, cdac_data::All4ByteColumns) +CDAC_TYPE_FIELD(CMiniMdRW, TYPE(TableRW), Tables, cdac_data::Tables) +CDAC_TYPE_FIELD(CMiniMdRW, TYPE(StgPool), StringHeap, cdac_data::StringHeap) +CDAC_TYPE_FIELD(CMiniMdRW, TYPE(StgPool), BlobHeap, cdac_data::BlobHeap) +CDAC_TYPE_FIELD(CMiniMdRW, TYPE(StgPool), UserStringHeap, cdac_data::UserStringHeap) +CDAC_TYPE_FIELD(CMiniMdRW, TYPE(StgPool), GuidHeap, cdac_data::GuidHeap) +CDAC_TYPE_END(CMiniMdRW) + +CDAC_TYPE_BEGIN(CMiniMdSchema) +CDAC_TYPE_INDETERMINATE(CMiniMdSchema) +CDAC_TYPE_FIELD(CMiniMdSchema, T_UINT8, Heaps, offsetof(CMiniMdSchema, m_heaps)) +CDAC_TYPE_FIELD(CMiniMdSchema, T_UINT64, Sorted, offsetof(CMiniMdSchema, m_sorted)) +CDAC_TYPE_FIELD(CMiniMdSchema, T_ARRAY(T_UINT32), RecordCounts, offsetof(CMiniMdSchema, m_cRecs)) +CDAC_TYPE_END(CMiniMdSchema) + +CDAC_TYPE_BEGIN(TableRW) +CDAC_TYPE_SIZE(sizeof(MetaData::TableRW)) +CDAC_TYPE_END(TableRW) + +CDAC_TYPE_BEGIN(StgPoolSeg) +CDAC_TYPE_INDETERMINATE(StgPoolSeg) +CDAC_TYPE_FIELD(StgPoolSeg, T_POINTER, SegData, cdac_data::SegData) +CDAC_TYPE_FIELD(StgPoolSeg, T_POINTER, NextSegment, cdac_data::NextSegment) +CDAC_TYPE_FIELD(StgPoolSeg, T_UINT32, DataSize, cdac_data::DataSize) +CDAC_TYPE_END(StgPoolSeg) + +CDAC_TYPE_BEGIN(StgPool) +CDAC_TYPE_INDETERMINATE(StgPool) +CDAC_TYPE_FIELD(StgPool, T_POINTER, SegData, cdac_data::SegData) +CDAC_TYPE_FIELD(StgPool, T_POINTER, NextSegment, cdac_data::NextSegment) +CDAC_TYPE_FIELD(StgPool, T_UINT32, DataSize, cdac_data::DataSize) +CDAC_TYPE_END(StgPool) + #ifdef STRESS_LOG CDAC_TYPE_BEGIN(StressLog) CDAC_TYPE_SIZE(sizeof(StressLog)) diff --git a/src/coreclr/vm/peassembly.h b/src/coreclr/vm/peassembly.h index fe1aa193ab1970..f8d4244e75778f 100644 --- a/src/coreclr/vm/peassembly.h +++ b/src/coreclr/vm/peassembly.h @@ -434,6 +434,9 @@ struct cdac_data { static constexpr size_t PEImage = offsetof(PEAssembly, m_PEImage); static constexpr size_t AssemblyBinder = offsetof(PEAssembly, m_pAssemblyBinder); +#ifndef DACCESS_COMPILE + static constexpr size_t MDImport = offsetof(PEAssembly, m_pMDImport); +#endif }; typedef ReleaseHolder PEAssemblyHolder; diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/EcmaMetadata_1.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/EcmaMetadata_1.cs index 00bcd71d07fe72..060bcdb15c03d8 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/EcmaMetadata_1.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/EcmaMetadata_1.cs @@ -6,6 +6,7 @@ using System.IO; using System.Numerics; using System.Reflection.Metadata; +using System.Reflection.Metadata.Ecma335; using System.Reflection.PortableExecutable; using System.Runtime.InteropServices; @@ -13,13 +14,20 @@ namespace Microsoft.Diagnostics.DataContractReader.Contracts; internal sealed class EcmaMetadata_1(Target target) : IEcmaMetadata { - private readonly Dictionary _metadata = []; + // Heap index size flags (ECMA-335 II.24.2.6) + private const byte HEAP_STRING_4 = 0x01; + private const byte HEAP_GUID_4 = 0x02; + private const byte HEAP_BLOB_4 = 0x04; + private readonly Dictionary _metadata = []; private readonly Dictionary _readOnlyMetadataAddress = []; public void Flush(FlushScope scope) { - _metadata.Clear(); - _readOnlyMetadataAddress.Clear(); + if (scope == FlushScope.All) + { + _metadata.Clear(); + _readOnlyMetadataAddress.Clear(); + } } public TargetSpan GetReadOnlyMetadataAddress(ModuleHandle handle) @@ -49,16 +57,20 @@ public TargetSpan GetReadOnlyMetadataAddress(ModuleHandle handle) public MetadataReader? GetMetadata(ModuleHandle handle) { - if (_metadata.TryGetValue(handle, out MetadataReaderProvider? result)) - { - return result?.GetMetadataReader(); - } - else + uint generation = GetMetadataGeneration(handle); + + if (_metadata.TryGetValue(handle, out (uint Generation, MetadataReaderProvider? Provider) cached)) { - MetadataReaderProvider? provider = GetMetadataProvider(handle); - _metadata.Add(handle, provider); - return provider?.GetMetadataReader(); + if (cached.Generation == generation) + { + return cached.Provider?.GetMetadataReader(); + } + cached.Provider?.Dispose(); } + + MetadataReaderProvider? provider = GetMetadataProvider(handle); + _metadata[handle] = (generation, provider); + return provider?.GetMetadataReader(); } private MetadataReaderProvider? GetMetadataProvider(ModuleHandle handle) @@ -102,7 +114,7 @@ public TargetSpan GetReadOnlyMetadataAddress(ModuleHandle handle) builder.WriteUInt32(0); string version = targetEcmaMetadata.Schema.MetadataVersion; - builder.WriteInt32(AlignUp(version.Length, 4)); + builder.WriteInt32(AlignUp(version.Length + 1, 4)); Write4ByteAlignedString(builder, version); // reserved @@ -125,44 +137,44 @@ public TargetSpan GetReadOnlyMetadataAddress(ModuleHandle handle) WriteStreamHeader(builder, "#JTD", 0).WriteInt32(builder.Count); } - BlobWriter stringsOffset = WriteStreamHeader(builder, "#Strings", (int)AlignUp(targetEcmaMetadata.StringHeap.Size, 4ul)); - BlobWriter blobOffset = WriteStreamHeader(builder, "#Blob", (int)targetEcmaMetadata.BlobHeap.Size); - BlobWriter guidOffset = WriteStreamHeader(builder, "#GUID", (int)targetEcmaMetadata.GuidHeap.Size); - BlobWriter userStringOffset = WriteStreamHeader(builder, "#US", (int)targetEcmaMetadata.UserStringHeap.Size); + BlobWriter stringsOffset = WriteStreamHeader(builder, "#Strings", (int)AlignUp((ulong)targetEcmaMetadata.StringHeap.Length, 4ul)); + BlobWriter blobOffset = WriteStreamHeader(builder, "#Blob", (int)AlignUp((ulong)targetEcmaMetadata.BlobHeap.Length, 4ul)); + BlobWriter guidOffset = WriteStreamHeader(builder, "#GUID", (int)AlignUp((ulong)targetEcmaMetadata.GuidHeap.Length, 4ul)); + BlobWriter userStringOffset = WriteStreamHeader(builder, "#US", (int)AlignUp((ulong)targetEcmaMetadata.UserStringHeap.Length, 4ul)); // We'll use the "uncompressed" tables stream name as the runtime may have created the *Ptr tables // that are only present in the uncompressed tables stream. - BlobWriter tablesOffset = WriteStreamHeader(builder, "#-", 0); + BlobWriter tablesOffset = new(builder.ReserveBytes(4)); + BlobWriter tablesSize = new(builder.ReserveBytes(4)); + Write4ByteAlignedString(builder, "#-"); // Write the heap-style Streams stringsOffset.WriteInt32(builder.Count); - WriteTargetSpan(builder, targetEcmaMetadata.StringHeap); - for (ulong i = targetEcmaMetadata.StringHeap.Size; i < AlignUp(targetEcmaMetadata.StringHeap.Size, 4ul); i++) - { - builder.WriteByte(0); - } + WriteAlignedHeap(builder, targetEcmaMetadata.StringHeap); blobOffset.WriteInt32(builder.Count); - WriteTargetSpan(builder, targetEcmaMetadata.BlobHeap); + WriteAlignedHeap(builder, targetEcmaMetadata.BlobHeap); guidOffset.WriteInt32(builder.Count); - WriteTargetSpan(builder, targetEcmaMetadata.GuidHeap); + WriteAlignedHeap(builder, targetEcmaMetadata.GuidHeap); userStringOffset.WriteInt32(builder.Count); - WriteTargetSpan(builder, targetEcmaMetadata.UserStringHeap); + WriteAlignedHeap(builder, targetEcmaMetadata.UserStringHeap); // Write tables stream - tablesOffset.WriteInt32(builder.Count); + int tableStreamStart = builder.Count; + tablesOffset.WriteInt32(tableStreamStart); // Write tables stream header builder.WriteInt32(0); // reserved + // ECMA-335 II.24.2.6: MajorVersion shall be 2, MinorVersion shall be 0. builder.WriteByte(2); // major version builder.WriteByte(0); // minor version uint heapSizes = - (targetEcmaMetadata.Schema.LargeStringHeap ? 1u << 0 : 0) | - (targetEcmaMetadata.Schema.LargeBlobHeap ? 1u << 1 : 0) | - (targetEcmaMetadata.Schema.LargeGuidHeap ? 1u << 2 : 0); + (targetEcmaMetadata.Schema.LargeStringHeap ? (uint)HEAP_STRING_4 : 0) | + (targetEcmaMetadata.Schema.LargeGuidHeap ? (uint)HEAP_GUID_4 : 0) | + (targetEcmaMetadata.Schema.LargeBlobHeap ? (uint)HEAP_BLOB_4 : 0); builder.WriteByte((byte)heapSizes); builder.WriteByte(1); // reserved @@ -197,21 +209,19 @@ public TargetSpan GetReadOnlyMetadataAddress(ModuleHandle handle) } // Write the tables - foreach (TargetSpan span in targetEcmaMetadata.Tables) + foreach (byte[] table in targetEcmaMetadata.Tables) { - WriteTargetSpan(builder, span); + builder.WriteBytes(table); } + // Patch the #- stream size now that the full table stream has been written. + tablesSize.WriteInt32(builder.Count - tableStreamStart); + MemoryStream metadataStream = new MemoryStream(); builder.WriteContentTo(metadataStream); + metadataStream.Position = 0; return MetadataReaderProvider.FromMetadataStream(metadataStream); - void WriteTargetSpan(BlobBuilder builder, TargetSpan span) - { - Blob blob = builder.ReserveBytes(checked((int)span.Size)); - target.ReadBuffer(span.Address, blob.GetBytes().AsSpan()); - } - static BlobWriter WriteStreamHeader(BlobBuilder builder, string name, int size) { BlobWriter offset = new(builder.ReserveBytes(4)); @@ -220,13 +230,24 @@ static BlobWriter WriteStreamHeader(BlobBuilder builder, string name, int size) return offset; } + static void WriteAlignedHeap(BlobBuilder builder, byte[] heap) + { + builder.WriteBytes(heap); + for (int i = heap.Length; i < (int)AlignUp((ulong)heap.Length, 4ul); i++) + { + builder.WriteByte(0); + } + } + static void Write4ByteAlignedString(BlobBuilder builder, string value) { int bufferStart = builder.Count; builder.WriteUTF8(value); builder.WriteByte(0); int stringEnd = builder.Count; - for (int i = stringEnd; i < bufferStart + AlignUp(value.Length, 4); i++) + // The name field occupies the null-terminated string padded to a 4-byte boundary, + // i.e. AlignUp(length + 1, 4) bytes (the +1 accounts for the null terminator). + for (int i = stringEnd; i < bufferStart + AlignUp(value.Length + 1, 4); i++) { builder.WriteByte(0); } @@ -273,11 +294,11 @@ public EcmaMetadataSchema(string metadataVersion, bool largeStringHeap, bool lar private sealed class TargetEcmaMetadata { public TargetEcmaMetadata(EcmaMetadataSchema schema, - TargetSpan[] tables, - TargetSpan stringHeap, - TargetSpan userStringHeap, - TargetSpan blobHeap, - TargetSpan guidHeap) + byte[][] tables, + byte[] stringHeap, + byte[] userStringHeap, + byte[] blobHeap, + byte[] guidHeap) { Schema = schema; _tables = tables; @@ -289,12 +310,12 @@ public TargetEcmaMetadata(EcmaMetadataSchema schema, 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; } + private byte[][] _tables; + public ReadOnlySpan Tables => _tables; + public byte[] StringHeap { get; init; } + public byte[] UserStringHeap { get; init; } + public byte[] BlobHeap { get; init; } + public byte[] GuidHeap { get; init; } } [Flags] @@ -313,14 +334,27 @@ private AvailableMetadataType GetAvailableMetadataType(ModuleHandle handle) AvailableMetadataType flags = AvailableMetadataType.None; if (module.DynamicMetadata != TargetPointer.Null) + { flags |= AvailableMetadataType.ReadWriteSavedCopy; + } + else if (module.MetadataGeneration != 0) + { + flags |= AvailableMetadataType.ReadWrite; + } else + { flags |= AvailableMetadataType.ReadOnly; + } - // TODO(cdac) implement direct reading of unsaved ReadWrite metadata return flags; } + private uint GetMetadataGeneration(ModuleHandle handle) + { + Data.Module module = target.ProcessedData.GetOrAdd(handle.Address); + return module.MetadataGeneration; + } + private TargetSpan GetReadWriteSavedMetadataAddress(ModuleHandle handle) { Data.Module module = target.ProcessedData.GetOrAdd(handle.Address); @@ -331,7 +365,97 @@ private TargetSpan GetReadWriteSavedMetadataAddress(ModuleHandle handle) private TargetEcmaMetadata GetReadWriteMetadata(ModuleHandle handle) { - throw new NotImplementedException(); + TargetPointer peAssemblyPtr = target.Contracts.Loader.GetPEAssembly(handle); + Data.PEAssembly peAssembly = target.ProcessedData.GetOrAdd(peAssemblyPtr); + Data.MDInternalRW mdRW = target.ProcessedData.GetOrAdd(peAssembly.MDImport); + Data.CLiteWeightStgdbRW stgdb = target.ProcessedData.GetOrAdd(mdRW.Stgdb); + Data.CMiniMdRW miniMd = target.ProcessedData.GetOrAdd(stgdb.MiniMd); + Data.CMiniMdSchema schema = target.ProcessedData.GetOrAdd(miniMd.Schema); + + int tableCount = checked((int)miniMd.TableCount); + if ((uint)tableCount > (uint)MetadataTokens.TableCount) + { + throw new InvalidOperationException($"Unexpected metadata table count {tableCount}."); + } + + // ECMA-335 II.24.2.6 + int[] rowCounts = new int[tableCount]; + for (int i = 0; i < tableCount; i++) + { + rowCounts[i] = checked((int)target.Read(schema.RecordCounts + (ulong)(i * sizeof(uint)))); + } + + // ECMA-335 II.24.2.6 + bool[] isSorted = new bool[tableCount]; + for (int i = 0; i < tableCount; i++) + { + isSorted[i] = (schema.Sorted & (1UL << i)) != 0; + } + + bool largeStringHeap = (schema.Heaps & HEAP_STRING_4) != 0; + bool largeGuidHeap = (schema.Heaps & HEAP_GUID_4) != 0; + bool largeBlobHeap = (schema.Heaps & HEAP_BLOB_4) != 0; + byte[] stringHeap = ReadStoragePool(miniMd.StringHeap); + byte[] blobHeap = ReadStoragePool(miniMd.BlobHeap); + byte[] userStringHeap = ReadStoragePool(miniMd.UserStringHeap); + byte[] guidHeap = ReadStoragePool(miniMd.GuidHeap); + + // Coalesce the record data for each table. + byte[][] tables = new byte[tableCount][]; + for (int i = 0; i < tableCount; i++) + { + tables[i] = ReadStoragePool(miniMd.TableSegments[i]); + } + + string version = EcmaMetadataUtils.ReadMetadataVersion(target, stgdb.MetadataAddress); + + EcmaMetadataSchema ecmaSchema = new EcmaMetadataSchema( + version, + largeStringHeap, + largeBlobHeap, + largeGuidHeap, + rowCounts, + isSorted, + miniMd.All4ByteColumns); + return new TargetEcmaMetadata(ecmaSchema, tables, stringHeap, userStringHeap, blobHeap, guidHeap); + } + + private byte[] ReadStoragePool(TargetPointer poolAddress) + { + List<(TargetPointer Data, uint Size)> segments = []; + long totalSize = 0; + + Data.StgPool head = target.ProcessedData.GetOrAdd(poolAddress); + TargetPointer segData = head.SegData; + uint dataSize = head.DataSize; + TargetPointer nextSegment = head.NextSegment; + + while (true) + { + if (dataSize > 0) + { + segments.Add((segData, dataSize)); + totalSize += dataSize; + } + if (nextSegment == TargetPointer.Null) + { + break; + } + + Data.StgPoolSeg segment = target.ProcessedData.GetOrAdd(nextSegment); + segData = segment.SegData; + dataSize = segment.DataSize; + nextSegment = segment.NextSegment; + } + + byte[] result = new byte[checked((int)totalSize)]; + int offset = 0; + foreach ((TargetPointer data, uint size) in segments) + { + target.ReadBuffer(data, result.AsSpan(offset, checked((int)size))); + offset += (int)size; + } + return result; } private static T AlignUp(T input, T alignment) diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/CLiteWeightStgdbRW.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/CLiteWeightStgdbRW.cs new file mode 100644 index 00000000000000..59ddaaf1c87b13 --- /dev/null +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/CLiteWeightStgdbRW.cs @@ -0,0 +1,11 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.Diagnostics.DataContractReader.Data; + +[CdacType(nameof(DataType.CLiteWeightStgdbRW))] +internal sealed partial class CLiteWeightStgdbRW : IData +{ + [FieldAddress] public TargetPointer MiniMd { get; } + [Field] public TargetPointer MetadataAddress { get; } +} diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/CMiniMdRW.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/CMiniMdRW.cs new file mode 100644 index 00000000000000..dd11f64c260475 --- /dev/null +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/CMiniMdRW.cs @@ -0,0 +1,35 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Diagnostics.CodeAnalysis; + +namespace Microsoft.Diagnostics.DataContractReader.Data; + +[CdacType(nameof(DataType.CMiniMdRW))] +internal sealed partial class CMiniMdRW : IData +{ + [FieldAddress] public TargetPointer Schema { get; } + [Field] public uint TableCount { get; } + [Field(UnderlyingBoolType = typeof(uint))] public bool All4ByteColumns { get; } + [FieldAddress] public TargetPointer Tables { get; } + [FieldAddress] public TargetPointer StringHeap { get; } + [FieldAddress] public TargetPointer BlobHeap { get; } + [FieldAddress] public TargetPointer UserStringHeap { get; } + [FieldAddress] public TargetPointer GuidHeap { get; } + public TargetPointer[] TableSegments { get; private set; } + + [MemberNotNull(nameof(TableSegments))] + partial void OnInit(Target target, TargetPointer address) + { + int tableCount = checked((int)TableCount); + uint tableStride = target.GetTypeInfo(DataType.TableRW).Size + ?? throw new InvalidOperationException("TableRW size is required to index the tables array."); + + TableSegments = new TargetPointer[tableCount]; + for (int i = 0; i < tableCount; i++) + { + TableSegments[i] = Tables + (ulong)i * tableStride; + } + } +} diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/CMiniMdSchema.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/CMiniMdSchema.cs new file mode 100644 index 00000000000000..f1b6838a6c5236 --- /dev/null +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/CMiniMdSchema.cs @@ -0,0 +1,12 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.Diagnostics.DataContractReader.Data; + +[CdacType(nameof(DataType.CMiniMdSchema))] +internal sealed partial class CMiniMdSchema : IData +{ + [Field] public byte Heaps { get; } + [Field] public ulong Sorted { get; } + [FieldAddress] public TargetPointer RecordCounts { get; } +} diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/MDInternalRW.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/MDInternalRW.cs new file mode 100644 index 00000000000000..e5228692e8bc9d --- /dev/null +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/MDInternalRW.cs @@ -0,0 +1,10 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.Diagnostics.DataContractReader.Data; + +[CdacType(nameof(DataType.MDInternalRW))] +internal sealed partial class MDInternalRW : IData +{ + [Field] public TargetPointer Stgdb { get; } +} diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Module.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Module.cs index 58d29e1d81ea2e..2f0a5b69928097 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Module.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Module.cs @@ -15,6 +15,7 @@ internal sealed partial class Module : IData [Field] public TargetPointer Base { get; } [Field] public TargetPointer LoaderAllocator { get; } [Field] public TargetPointer DynamicMetadata { get; } + [Field] public uint MetadataGeneration { get; } [Field] public TargetPointer SimpleName { get; } [Field] public TargetPointer Path { get; } [Field] public TargetPointer FileName { get; } diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/PEAssembly.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/PEAssembly.cs index 6697676a619f86..57361746ca6f04 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/PEAssembly.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/PEAssembly.cs @@ -8,4 +8,5 @@ internal sealed partial class PEAssembly : IData { [Field] public TargetPointer PEImage { get; } [Field] public TargetPointer AssemblyBinder { get; } + [Field] public TargetPointer MDImport { get; } } diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/StgPool.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/StgPool.cs new file mode 100644 index 00000000000000..08f8df30dfc9f4 --- /dev/null +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/StgPool.cs @@ -0,0 +1,12 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.Diagnostics.DataContractReader.Data; + +[CdacType(nameof(DataType.StgPool))] +internal sealed partial class StgPool : IData +{ + [Field] public TargetPointer SegData { get; } + [Field] public TargetPointer NextSegment { get; } + [Field] public uint DataSize { get; } +} diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/StgPoolSeg.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/StgPoolSeg.cs new file mode 100644 index 00000000000000..abfcd573540f24 --- /dev/null +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/StgPoolSeg.cs @@ -0,0 +1,12 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.Diagnostics.DataContractReader.Data; + +[CdacType(nameof(DataType.StgPoolSeg))] +internal sealed partial class StgPoolSeg : IData +{ + [Field] public TargetPointer SegData { get; } + [Field] public TargetPointer NextSegment { get; } + [Field] public uint DataSize { get; } +} diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/DataType.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/DataType.cs index 4abbf4ce732740..895ba670c69370 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/DataType.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/DataType.cs @@ -71,6 +71,13 @@ public enum DataType FnPtrTypeDesc, FieldDesc, DynamicMetadata, + MDInternalRW, + CLiteWeightStgdbRW, + CMiniMdRW, + CMiniMdSchema, + TableRW, + StgPool, + StgPoolSeg, StressLog, StressLogModuleDesc, StressLogHeader, diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/EcmaMetadataUtils.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/EcmaMetadataUtils.cs index cc0ee0647f916f..8deb61e64a5611 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/EcmaMetadataUtils.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/EcmaMetadataUtils.cs @@ -1,7 +1,9 @@ // 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.Text; namespace Microsoft.Diagnostics.DataContractReader; @@ -38,4 +40,37 @@ public static uint CreateFieldDef(uint tokenParts) Debug.Assert((tokenParts & 0xff000000) == 0, $"Token type should not be set in {nameof(tokenParts)}"); return (uint)TokenType.mdtFieldDef | tokenParts; } + + // ECMA-335 II.24.2.1 metadata root: + // Signature(4) | MajorVersion(2) | MinorVersion(2) | Reserved(4) | VersionLength(4) | Version[VersionLength] + private const ulong MetadataRootVersionLengthOffset = 12; + private const ulong MetadataRootVersionStringOffset = 16; + private const uint MaxMetadataVersionLength = 256; + + // Reads the metadata version string from the metadata root (ECMA-335 II.24.2.1) at the given + // address. Returns an empty string when the address is null or no version string is present. + public static string ReadMetadataVersion(Target target, TargetPointer metadataRootAddress) + { + if (metadataRootAddress == TargetPointer.Null) + { + return string.Empty; + } + + uint versionLength = target.Read(metadataRootAddress + MetadataRootVersionLengthOffset); + if (versionLength == 0) + { + return string.Empty; + } + + int length = (int)Math.Min(versionLength, MaxMetadataVersionLength); + Span buffer = stackalloc byte[length]; + target.ReadBuffer(metadataRootAddress + MetadataRootVersionStringOffset, buffer); + int terminator = buffer.IndexOf((byte)0); + if (terminator >= 0) + { + buffer = buffer[..terminator]; + } + + return Encoding.UTF8.GetString(buffer); + } } diff --git a/src/native/managed/cdac/tests/UnitTests/LoaderTests.cs b/src/native/managed/cdac/tests/UnitTests/LoaderTests.cs index 7b0464e190e4ef..e7b14cb1df7370 100644 --- a/src/native/managed/cdac/tests/UnitTests/LoaderTests.cs +++ b/src/native/managed/cdac/tests/UnitTests/LoaderTests.cs @@ -521,6 +521,7 @@ private static (TestPlaceholderTarget Target, TargetPointer PEAssemblyAddr, Targ var peAssemblyLayout = helpers.LayoutFields([ new(nameof(Data.PEAssembly.PEImage), DataType.pointer), new(nameof(Data.PEAssembly.AssemblyBinder), DataType.pointer), + new(nameof(Data.PEAssembly.MDImport), DataType.pointer), ]); var peImageLayout = helpers.LayoutFields([ new(nameof(Data.PEImage.LoadedImageLayout), DataType.pointer), @@ -739,6 +740,7 @@ public void IsModuleMapped_ReturnsExpected(MockTarget.Architecture arch, uint fo var peAssemblyLayout = helpers.LayoutFields([ new(nameof(Data.PEAssembly.PEImage), DataType.pointer), new(nameof(Data.PEAssembly.AssemblyBinder), DataType.pointer), + new(nameof(Data.PEAssembly.MDImport), DataType.pointer), ]); var peImageLayout = helpers.LayoutFields([ new(nameof(Data.PEImage.LoadedImageLayout), DataType.pointer), @@ -1091,6 +1093,7 @@ private static (TestPlaceholderTarget Target, TargetPointer ModuleAddr) CreatePE var peAssemblyLayout = helpers.LayoutFields([ new(nameof(Data.PEAssembly.PEImage), DataType.pointer), new(nameof(Data.PEAssembly.AssemblyBinder), DataType.pointer), + new(nameof(Data.PEAssembly.MDImport), DataType.pointer), ]); var peImageLayout = helpers.LayoutFields([ new(nameof(Data.PEImage.LoadedImageLayout), DataType.pointer), diff --git a/src/native/managed/cdac/tests/UnitTests/MockDescriptors/MockDescriptors.Loader.cs b/src/native/managed/cdac/tests/UnitTests/MockDescriptors/MockDescriptors.Loader.cs index 0333d5e80bea9e..58b792eb29af91 100644 --- a/src/native/managed/cdac/tests/UnitTests/MockDescriptors/MockDescriptors.Loader.cs +++ b/src/native/managed/cdac/tests/UnitTests/MockDescriptors/MockDescriptors.Loader.cs @@ -64,6 +64,7 @@ internal sealed class MockLoaderModule : TypedView private const string FlagsFieldName = "Flags"; private const string LoaderAllocatorFieldName = "LoaderAllocator"; private const string DynamicMetadataFieldName = "DynamicMetadata"; + private const string MetadataGenerationFieldName = "MetadataGeneration"; private const string SimpleNameFieldName = "SimpleName"; private const string PathFieldName = "Path"; private const string FileNameFieldName = "FileName"; @@ -88,6 +89,7 @@ public static Layout CreateLayout(MockTarget.Architecture arch .AddUInt32Field(FlagsFieldName) .AddPointerField(LoaderAllocatorFieldName) .AddPointerField(DynamicMetadataFieldName) + .AddUInt32Field(MetadataGenerationFieldName) .AddPointerField(SimpleNameFieldName) .AddPointerField(PathFieldName) .AddPointerField(FileNameFieldName) diff --git a/src/native/managed/cdac/tests/UnitTests/MockDescriptors/MockDescriptors.RuntimeMutableTypeSystem.cs b/src/native/managed/cdac/tests/UnitTests/MockDescriptors/MockDescriptors.RuntimeMutableTypeSystem.cs index 992957fdc947c7..213dfcf867be6c 100644 --- a/src/native/managed/cdac/tests/UnitTests/MockDescriptors/MockDescriptors.RuntimeMutableTypeSystem.cs +++ b/src/native/managed/cdac/tests/UnitTests/MockDescriptors/MockDescriptors.RuntimeMutableTypeSystem.cs @@ -32,6 +32,7 @@ public static Layout CreateLayout(MockTarget.Architecture archite .AddPointerField(nameof(Data.Module.Base)) .AddPointerField(nameof(Data.Module.LoaderAllocator)) .AddPointerField(nameof(Data.Module.DynamicMetadata)) + .AddUInt32Field(nameof(Data.Module.MetadataGeneration)) .AddPointerField(nameof(Data.Module.SimpleName)) .AddPointerField(nameof(Data.Module.Path)) .AddPointerField(nameof(Data.Module.FileName))