From 6431e393f71d17548a8d22a172bdcf5706eaae86 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 28 Jul 2026 19:51:38 +0000 Subject: [PATCH 1/5] Add DacDbi read-write metadata APIs Co-authored-by: rcj1 <77995559+rcj1@users.noreply.github.com> --- docs/design/datacontracts/EcmaMetadata.md | 5 + src/coreclr/debug/daccess/CMakeLists.txt | 1 + src/coreclr/debug/daccess/dacdbiimpl.cpp | 418 ++++++++++++++++++ src/coreclr/debug/daccess/dacdbiimpl.h | 4 + src/coreclr/debug/inc/dacdbiinterface.h | 9 + src/coreclr/md/inc/liteweightstgdb.h | 2 + src/coreclr/md/inc/metamodel.h | 2 + src/coreclr/md/inc/metamodelrw.h | 2 + .../Contracts/IEcmaMetadata.cs | 1 + .../Contracts/EcmaMetadata_1.cs | 13 +- .../Dbi/DacDbiImpl.cs | 76 ++++ .../Dbi/IDacDbiInterface.cs | 6 + 12 files changed, 537 insertions(+), 2 deletions(-) diff --git a/docs/design/datacontracts/EcmaMetadata.md b/docs/design/datacontracts/EcmaMetadata.md index 42714b2c8d0a14..d2604fcc8539a3 100644 --- a/docs/design/datacontracts/EcmaMetadata.md +++ b/docs/design/datacontracts/EcmaMetadata.md @@ -8,6 +8,7 @@ This contract provides methods to get a view of the ECMA-335 metadata for a give TargetSpan GetReadOnlyMetadataAddress(ModuleHandle handle); TargetSpan GetReadWriteSavedMetadataAddress(ModuleHandle handle); System.Reflection.Metadata.MetadataReader? GetMetadata(ModuleHandle handle); +byte[] GetReadWriteMetadata(ModuleHandle handle); ``` Types from other contracts: @@ -172,6 +173,10 @@ MetadataReader? GetMetadata(ModuleHandle handle) } ``` +`GetReadWriteMetadata` reconstructs the module's writable `MDInternalRW` metadata as a contiguous +ECMA-335 metadata image and returns its bytes. It throws `ArgumentException` when the module does +not have writable metadata. + ### Helper Methods ``` csharp diff --git a/src/coreclr/debug/daccess/CMakeLists.txt b/src/coreclr/debug/daccess/CMakeLists.txt index fc6b8ef3c68fa8..243a5af75171da 100644 --- a/src/coreclr/debug/daccess/CMakeLists.txt +++ b/src/coreclr/debug/daccess/CMakeLists.txt @@ -42,6 +42,7 @@ convert_to_absolute_path(DACCESS_SOURCES ${DACCESS_SOURCES}) add_library_clr(daccess ${DACCESS_SOURCES}) set_target_properties(daccess PROPERTIES DAC_COMPONENT TRUE) +target_compile_definitions(daccess PRIVATE FEATURE_METADATA_INTERNAL_APIS) target_precompile_headers(daccess PRIVATE [["stdafx.h"]]) target_link_libraries(daccess PRIVATE cdac_api) diff --git a/src/coreclr/debug/daccess/dacdbiimpl.cpp b/src/coreclr/debug/daccess/dacdbiimpl.cpp index 05eea0a3062532..071c187ae95e0d 100644 --- a/src/coreclr/debug/daccess/dacdbiimpl.cpp +++ b/src/coreclr/debug/daccess/dacdbiimpl.cpp @@ -31,6 +31,12 @@ #include "request_common.h" #include "conditionalweaktable.h" +#include "metamodelro.h" +#include "metamodelrw.h" +#include "liteweightstgdb.h" +#include "mdinternalrw.h" +#include "stgpool.h" + #ifndef USE_DAC_TABLE_RVA #include #define CAN_USE_CDAC @@ -3896,6 +3902,418 @@ HRESULT STDMETHODCALLTYPE DacDbiInterfaceImpl::GetMetadata(VMPTR_Module vmModule return hr; } +namespace +{ + const BYTE HEAP_STRING_4 = 0x01; + const BYTE HEAP_GUID_4 = 0x02; + const BYTE HEAP_BLOB_4 = 0x04; + + const ULONG64 MaxPoolBytes = 100000000; + const ULONG32 MaxPoolSegments = 1000; + const ULONG32 MaxTableCount = 64; + + FORCEINLINE ULONG32 AlignUp4(ULONG32 value) + { + return (value + 3) & ~(ULONG32)3; + } + + struct PoolData + { + NewArrayHolder Data; + ULONG32 Size; + + PoolData() : Data(NULL), Size(0) {} + }; + + void ReadStoragePool(TADDR poolAddress, PoolData & result) + { + TADDR segData[MaxPoolSegments]; + ULONG32 segSize[MaxPoolSegments]; + ULONG32 segCount = 0; + ULONG64 totalSize = 0; + + StgPool * pPool = dac_cast(poolAddress); + StgPoolSeg * pSeg = pPool; + for (ULONG32 iteration = 0; ; iteration++) + { + ULONG32 dataSize = pSeg->GetDataSize(); + if (iteration >= MaxPoolSegments || + totalSize > MaxPoolBytes || + dataSize > MaxPoolBytes - totalSize) + { + ThrowHR(CLDB_E_FILE_CORRUPT); + } + if (dataSize > 0) + { + segData[segCount] = (TADDR)pSeg->GetSegData(); + segSize[segCount] = dataSize; + segCount++; + totalSize += dataSize; + } + + TADDR nextSegment = (TADDR)pSeg->GetNextSeg(); + if (nextSegment == (TADDR)NULL) + { + break; + } + pSeg = dac_cast(nextSegment); + } + + result.Size = (ULONG32)totalSize; + result.Data = new BYTE[result.Size == 0 ? 1 : result.Size]; + ULONG32 offset = 0; + for (ULONG32 i = 0; i < segCount; i++) + { + DacReadAll(segData[i], result.Data + offset, segSize[i], true); + offset += segSize[i]; + } + } + + class MetadataBlobBuilder + { + public: + MetadataBlobBuilder() : m_pData(NULL), m_count(0), m_capacity(0) {} + ~MetadataBlobBuilder() { delete[] m_pData; } + + ULONG32 Count() const { return m_count; } + const BYTE * Data() const { return m_pData; } + + void WriteByte(BYTE value) + { + EnsureAdditionalCapacity(1); + m_pData[m_count++] = value; + } + + void WriteUInt16(UINT16 value) { WriteRaw(&value, sizeof(value)); } + void WriteUInt32(UINT32 value) { WriteRaw(&value, sizeof(value)); } + void WriteUInt64(UINT64 value) { WriteRaw(&value, sizeof(value)); } + void WriteBytes(const BYTE * pData, ULONG32 size) + { + if (size != 0) + { + WriteRaw(pData, size); + } + } + + void WriteZeros(ULONG32 count) + { + EnsureAdditionalCapacity(count); + memset(m_pData + m_count, 0, count); + m_count += count; + } + + ULONG32 Reserve4() + { + ULONG32 offset = m_count; + WriteUInt32(0); + return offset; + } + + void PatchUInt32(ULONG32 offset, UINT32 value) + { + memcpy(m_pData + offset, &value, sizeof(value)); + } + + void WriteAlignedString(const char * value, ULONG32 length) + { + if (length == UINT32_MAX) + { + ThrowHR(CLDB_E_FILE_CORRUPT); + } + + ULONG32 total = AlignUp4(length + 1); + EnsureAdditionalCapacity(total); + if (length != 0) + { + memcpy(m_pData + m_count, value, length); + } + memset(m_pData + m_count + length, 0, total - length); + m_count += total; + } + + void WriteAlignedHeap(const BYTE * pData, ULONG32 size) + { + WriteBytes(pData, size); + WriteZeros(AlignUp4(size) - size); + } + + private: + void WriteRaw(const void * pData, ULONG32 size) + { + EnsureAdditionalCapacity(size); + memcpy(m_pData + m_count, pData, size); + m_count += size; + } + + void EnsureAdditionalCapacity(ULONG32 additional) + { + if (additional > UINT32_MAX - m_count) + { + ThrowHR(CLDB_E_FILE_CORRUPT); + } + + ULONG32 needed = m_count + additional; + if (needed <= m_capacity) + { + return; + } + + ULONG32 newCapacity = (m_capacity == 0) ? 256 : m_capacity; + while (newCapacity < needed) + { + if (newCapacity > UINT32_MAX / 2) + { + newCapacity = needed; + break; + } + newCapacity *= 2; + } + + BYTE * pNew = new BYTE[newCapacity]; + if (m_count != 0) + { + memcpy(pNew, m_pData, m_count); + } + delete[] m_pData; + m_pData = pNew; + m_capacity = newCapacity; + } + + BYTE * m_pData; + ULONG32 m_count; + ULONG32 m_capacity; + }; + + ULONG32 WriteStreamHeader(MetadataBlobBuilder & builder, const char * name, ULONG32 size) + { + ULONG32 offsetField = builder.Reserve4(); + builder.WriteUInt32(size); + builder.WriteAlignedString(name, (ULONG32)strlen(name)); + return offsetField; + } +} + +void DacDbiInterfaceImpl::SerializeReadWriteMetadata(Module * pModule, BYTE ** ppBlob, ULONG32 * pcbBlob) +{ + PEAssembly * pPEAssembly = pModule->GetPEAssembly(); + TADDR mdRWAddr = pPEAssembly->GetMDInternalRWAddress(); + if (mdRWAddr == (TADDR)NULL) + { + ThrowHR(E_INVALIDARG); + } + + MDInternalRW * pMDInternalRW = dac_cast(mdRWAddr); + CLiteWeightStgdbRW * pStgdb = dac_cast((TADDR)pMDInternalRW->m_pStgdb); + CMiniMdRW * pMiniMd = dac_cast(PTR_HOST_MEMBER_TADDR(CLiteWeightStgdbRW, pStgdb, m_MiniMd)); + + ULONG32 tableCount = pMiniMd->GetCountTables(); + if (tableCount > MaxTableCount) + { + ThrowHR(CLDB_E_FILE_CORRUPT); + } + + ULONG32 rowCounts[MaxTableCount]; + for (ULONG32 i = 0; i < tableCount; i++) + { + rowCounts[i] = pMiniMd->m_Schema.m_cRecs[i]; + } + + ULONG64 sorted = pMiniMd->m_Schema.m_sorted; + BYTE heaps = pMiniMd->m_Schema.m_heaps; + + bool largeStringHeap = (heaps & HEAP_STRING_4) != 0; + bool largeGuidHeap = (heaps & HEAP_GUID_4) != 0; + bool largeBlobHeap = (heaps & HEAP_BLOB_4) != 0; + bool all4ByteColumns = pMiniMd->m_fAll4ByteColumns != FALSE; + + PoolData stringHeap; + PoolData blobHeap; + PoolData userStringHeap; + PoolData guidHeap; + ReadStoragePool(PTR_HOST_MEMBER_TADDR(CMiniMdRW, pMiniMd, m_StringHeap), stringHeap); + ReadStoragePool(PTR_HOST_MEMBER_TADDR(CMiniMdRW, pMiniMd, m_BlobHeap), blobHeap); + ReadStoragePool(PTR_HOST_MEMBER_TADDR(CMiniMdRW, pMiniMd, m_UserStringHeap), userStringHeap); + ReadStoragePool(PTR_HOST_MEMBER_TADDR(CMiniMdRW, pMiniMd, m_GuidHeap), guidHeap); + + NewArrayHolder tables = new PoolData[tableCount == 0 ? 1 : tableCount]; + TADDR tablesBase = PTR_HOST_MEMBER_TADDR(CMiniMdRW, pMiniMd, m_Tables); + ULONG32 tableStride = (ULONG32)sizeof(MetaData::TableRW); + for (ULONG32 i = 0; i < tableCount; i++) + { + ReadStoragePool(tablesBase + i * tableStride, tables[i]); + } + + const ULONG32 MetadataRootVersionLengthOffset = 12; + const ULONG32 MetadataRootVersionStringOffset = 16; + const ULONG32 MaxMetadataVersionLength = 256; + BYTE versionBuffer[MaxMetadataVersionLength]; + ULONG32 versionLength = 0; + TADDR metadataRootAddress = (TADDR)pStgdb->m_pvMd; + if (metadataRootAddress != (TADDR)NULL) + { + ULONG32 rawVersionLength = *dac_cast(metadataRootAddress + MetadataRootVersionLengthOffset); + if (rawVersionLength != 0) + { + ULONG32 toRead = rawVersionLength < MaxMetadataVersionLength ? rawVersionLength : MaxMetadataVersionLength; + DacReadAll(metadataRootAddress + MetadataRootVersionStringOffset, versionBuffer, toRead, true); + for (versionLength = 0; versionLength < toRead; versionLength++) + { + if (versionBuffer[versionLength] == 0) + { + break; + } + } + } + } + + MetadataBlobBuilder builder; + builder.WriteUInt32(0x424A5342); + builder.WriteUInt16(1); + builder.WriteUInt16(1); + builder.WriteUInt32(0); + builder.WriteUInt32(AlignUp4(versionLength + 1)); + builder.WriteAlignedString((const char *)versionBuffer, versionLength); + builder.WriteUInt16(0); + + UINT16 numStreams = 5; + if (all4ByteColumns) + { + numStreams++; + } + builder.WriteUInt16(numStreams); + + if (all4ByteColumns) + { + ULONG32 jtdOffset = WriteStreamHeader(builder, "#JTD", 0); + builder.PatchUInt32(jtdOffset, builder.Count()); + } + + ULONG32 stringsOffset = WriteStreamHeader(builder, "#Strings", AlignUp4(stringHeap.Size)); + ULONG32 blobOffset = WriteStreamHeader(builder, "#Blob", AlignUp4(blobHeap.Size)); + ULONG32 guidOffset = WriteStreamHeader(builder, "#GUID", AlignUp4(guidHeap.Size)); + ULONG32 userStringOffset = WriteStreamHeader(builder, "#US", AlignUp4(userStringHeap.Size)); + ULONG32 tablesOffset = builder.Reserve4(); + ULONG32 tablesSize = builder.Reserve4(); + builder.WriteAlignedString("#-", 2); + + builder.PatchUInt32(stringsOffset, builder.Count()); + builder.WriteAlignedHeap(stringHeap.Data, stringHeap.Size); + builder.PatchUInt32(blobOffset, builder.Count()); + builder.WriteAlignedHeap(blobHeap.Data, blobHeap.Size); + builder.PatchUInt32(guidOffset, builder.Count()); + builder.WriteAlignedHeap(guidHeap.Data, guidHeap.Size); + builder.PatchUInt32(userStringOffset, builder.Count()); + builder.WriteAlignedHeap(userStringHeap.Data, userStringHeap.Size); + + ULONG32 tableStreamStart = builder.Count(); + builder.PatchUInt32(tablesOffset, tableStreamStart); + builder.WriteUInt32(0); + builder.WriteByte(2); + builder.WriteByte(0); + BYTE heapSizes = + (BYTE)((largeStringHeap ? HEAP_STRING_4 : 0) | + (largeGuidHeap ? HEAP_GUID_4 : 0) | + (largeBlobHeap ? HEAP_BLOB_4 : 0)); + builder.WriteByte(heapSizes); + builder.WriteByte(1); + + ULONG64 validTables = 0; + for (ULONG32 i = 0; i < tableCount; i++) + { + if (rowCounts[i] != 0) + { + validTables |= (ULONG64)1 << i; + } + } + + ULONG64 sortedTables = 0; + for (ULONG32 i = 0; i < tableCount; i++) + { + if ((sorted & ((ULONG64)1 << i)) != 0) + { + sortedTables |= (ULONG64)1 << i; + } + } + + builder.WriteUInt64(validTables); + builder.WriteUInt64(sortedTables); + + for (ULONG32 i = 0; i < tableCount; i++) + { + if (rowCounts[i] != 0) + { + builder.WriteUInt32(rowCounts[i]); + } + } + + for (ULONG32 i = 0; i < tableCount; i++) + { + builder.WriteBytes(tables[i].Data, tables[i].Size); + } + + builder.PatchUInt32(tablesSize, builder.Count() - tableStreamStart); + + ULONG32 blobSize = builder.Count(); + BYTE * pBlob = new BYTE[blobSize == 0 ? 1 : blobSize]; + memcpy(pBlob, builder.Data(), blobSize); + *ppBlob = pBlob; + *pcbBlob = blobSize; +} + +HRESULT STDMETHODCALLTYPE DacDbiInterfaceImpl::GetReadWriteMetadataSize(VMPTR_Module vmModule, OUT ULONG32 * pSize) +{ + DD_ENTER_MAY_THROW; + + HRESULT hr = S_OK; + EX_TRY + { + if (pSize == NULL) + { + ThrowHR(E_POINTER); + } + + *pSize = 0; + + Module * pModule = vmModule.GetDacPtr(); + BYTE * pRawBlob = NULL; + ULONG32 blobSize = 0; + SerializeReadWriteMetadata(pModule, &pRawBlob, &blobSize); + NewArrayHolder blob(pRawBlob); + *pSize = blobSize; + } + EX_CATCH_HRESULT(hr); + return hr; +} + +HRESULT STDMETHODCALLTYPE DacDbiInterfaceImpl::FillReadWriteMetadata(VMPTR_Module vmModule, BYTE * pBuffer, ULONG32 cbBuffer) +{ + DD_ENTER_MAY_THROW; + + HRESULT hr = S_OK; + EX_TRY + { + if (pBuffer == NULL) + { + ThrowHR(E_INVALIDARG); + } + + Module * pModule = vmModule.GetDacPtr(); + BYTE * pRawBlob = NULL; + ULONG32 blobSize = 0; + SerializeReadWriteMetadata(pModule, &pRawBlob, &blobSize); + NewArrayHolder blob(pRawBlob); + + if (cbBuffer < blobSize) + { + ThrowHR(HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER)); + } + + memcpy(pBuffer, pRawBlob, blobSize); + } + EX_CATCH_HRESULT(hr); + return hr; +} + // Implementation of IDacDbiInterface::GetSymbolsBuffer HRESULT STDMETHODCALLTYPE DacDbiInterfaceImpl::GetSymbolsBuffer(VMPTR_Module vmModule, OUT TargetBuffer * pTargetBuffer, OUT SymbolFormat * pSymbolFormat) { diff --git a/src/coreclr/debug/daccess/dacdbiimpl.h b/src/coreclr/debug/daccess/dacdbiimpl.h index 8facf853bb444e..8a3963f688e203 100644 --- a/src/coreclr/debug/daccess/dacdbiimpl.h +++ b/src/coreclr/debug/daccess/dacdbiimpl.h @@ -152,8 +152,12 @@ class DacDbiInterfaceImpl : OUT UINT32 *pState); HRESULT STDMETHODCALLTYPE EnumerateAsyncLocals(VMPTR_MethodDesc vmMethod, CORDB_ADDRESS codeAddr, UINT32 state, FP_ASYNC_LOCAL_CALLBACK fpCallback, CALLBACK_DATA pUserData); HRESULT STDMETHODCALLTYPE GetGenericArgTokenIndex(VMPTR_MethodDesc vmMethod, OUT UINT32* pIndex); + HRESULT STDMETHODCALLTYPE GetReadWriteMetadataSize(VMPTR_Module vmModule, OUT ULONG32 * pSize); + HRESULT STDMETHODCALLTYPE FillReadWriteMetadata(VMPTR_Module vmModule, BYTE * pBuffer, ULONG32 cbBuffer); private: + void SerializeReadWriteMetadata(Module * pModule, BYTE ** ppBlob, ULONG32 * pcbBlob); + void TypeHandleToExpandedTypeInfoImpl(AreValueTypesBoxed boxed, TypeHandle typeHandle, DebuggerIPCE_ExpandedTypeData * pTypeInfo); diff --git a/src/coreclr/debug/inc/dacdbiinterface.h b/src/coreclr/debug/inc/dacdbiinterface.h index 35f837b5f965d9..c63ec22fa6082d 100644 --- a/src/coreclr/debug/inc/dacdbiinterface.h +++ b/src/coreclr/debug/inc/dacdbiinterface.h @@ -2226,6 +2226,15 @@ IDacDbiInterface : public IUnknown VMPTR_MethodDesc vmMethod, OUT UINT32* pTokenIndex) = 0; + virtual HRESULT STDMETHODCALLTYPE GetReadWriteMetadataSize( + VMPTR_Module vmModule, + OUT ULONG32* pSize) = 0; + + virtual HRESULT STDMETHODCALLTYPE FillReadWriteMetadata( + VMPTR_Module vmModule, + BYTE* pBuffer, + ULONG32 cbBuffer) = 0; + // The following tag tells the DD-marshalling tool to stop scanning. // END_MARSHAL diff --git a/src/coreclr/md/inc/liteweightstgdb.h b/src/coreclr/md/inc/liteweightstgdb.h index 440fabbc28261c..f11022ab59fd3a 100644 --- a/src/coreclr/md/inc/liteweightstgdb.h +++ b/src/coreclr/md/inc/liteweightstgdb.h @@ -27,6 +27,7 @@ class StgIO; #endif class TiggerStorage; +class DacDbiInterfaceImpl; //***************************************************************************** // This class provides common definitions for heap segments. It is both the @@ -37,6 +38,7 @@ template class CLiteWeightStgdb { friend class VerifyLayoutsMD; + friend class ::DacDbiInterfaceImpl; public: CLiteWeightStgdb() : m_pvMd(NULL), m_cbMd(0) {} diff --git a/src/coreclr/md/inc/metamodel.h b/src/coreclr/md/inc/metamodel.h index 322c295ca291be..c0019660e0bc1a 100644 --- a/src/coreclr/md/inc/metamodel.h +++ b/src/coreclr/md/inc/metamodel.h @@ -400,11 +400,13 @@ class CMiniMdSchema : public CMiniMdSchemaBase // To make that happen would be a substantial refactoring job as RegMeta // always embeds CMiniMdRW even when it was opened for ReadOnly. //***************************************************************************** +class DacDbiInterfaceImpl; class CMiniMdBase : public IMetaModelCommonRO { friend class VerifyLayoutsMD; // verifies class layout doesn't accidentally change friend struct ::cdac_data; + friend class ::DacDbiInterfaceImpl; public: CMiniMdBase(); diff --git a/src/coreclr/md/inc/metamodelrw.h b/src/coreclr/md/inc/metamodelrw.h index 47661f5ebe2986..8504e2013e4e78 100644 --- a/src/coreclr/md/inc/metamodelrw.h +++ b/src/coreclr/md/inc/metamodelrw.h @@ -205,12 +205,14 @@ class MDInternalRW; class UTSemReadWrite; template class CLiteWeightStgdb; +class DacDbiInterfaceImpl; //***************************************************************************** // Read/Write MiniMd. //***************************************************************************** class CMiniMdRW : public CMiniMdTemplate { public: + friend class ::DacDbiInterfaceImpl; friend class CLiteWeightStgdb; friend class CLiteWeightStgdbRW; friend class CMiniMdTemplate; diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IEcmaMetadata.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IEcmaMetadata.cs index c961742d40dbcc..2ac6b53e7c7e91 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IEcmaMetadata.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IEcmaMetadata.cs @@ -13,6 +13,7 @@ public interface IEcmaMetadata : IContract TargetSpan GetReadWriteSavedMetadataAddress(ModuleHandle handle) => throw new NotImplementedException(); MetadataReader? GetMetadata(ModuleHandle module) => throw new NotImplementedException(); + byte[] GetReadWriteMetadata(ModuleHandle handle) => throw new NotImplementedException(); } public readonly struct EcmaMetadata : IEcmaMetadata 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 511cfa432e78b0..ccb748642cb53d 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 @@ -135,7 +135,7 @@ private TargetSpan GetWebcilReadOnlyMetadataAddress(ModuleHandle handle, TargetP } case AvailableMetadataType.ReadWrite: { - var targetEcmaMetadata = GetReadWriteMetadata(handle); + TargetEcmaMetadata targetEcmaMetadata = GetTargetEcmaMetadata(handle); // From the multiple different target spans, we need to build a single // contiguous ECMA-335 metadata blob. @@ -296,6 +296,15 @@ static void Write4ByteAlignedString(BlobBuilder builder, string value) } } + public unsafe byte[] GetReadWriteMetadata(ModuleHandle handle) + { + if (GetAvailableMetadataType(handle) != AvailableMetadataType.ReadWrite) + throw new ArgumentException("Module does not have read/write metadata.", nameof(handle)); + + MetadataReader reader = GetMetadata(handle)!; + return new ReadOnlySpan(reader.MetadataPointer, reader.MetadataLength).ToArray(); + } + private struct EcmaMetadataSchema { public EcmaMetadataSchema(string metadataVersion, bool largeStringHeap, bool largeBlobHeap, bool largeGuidHeap, int[] rowCount, bool[] isSorted, bool variableSizedColumnsAre4BytesLong) @@ -401,7 +410,7 @@ public TargetSpan GetReadWriteSavedMetadataAddress(ModuleHandle handle) return new TargetSpan(dynamicMetadata.Data, dynamicMetadata.Size); } - private TargetEcmaMetadata GetReadWriteMetadata(ModuleHandle handle) + private TargetEcmaMetadata GetTargetEcmaMetadata(ModuleHandle handle) { TargetPointer peAssemblyPtr = target.Contracts.Loader.GetPEAssembly(handle); Data.PEAssembly peAssembly = target.ProcessedData.GetOrAdd(peAssemblyPtr); diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/DacDbiImpl.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/DacDbiImpl.cs index 3e35310885852c..60da2e101fe1c2 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/DacDbiImpl.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/DacDbiImpl.cs @@ -391,6 +391,82 @@ public int GetMetadata(ulong vmModule, DacDbiTargetBuffer* pTargetBuffer) return hr; } + public int GetReadWriteMetadataSize(ulong vmModule, uint* pSize) + { + int hr = HResults.S_OK; + try + { + if (pSize == null) + throw new ArgumentNullException(nameof(pSize)); + if (vmModule == 0) + throw new ArgumentException("Module pointer must be non-zero.", nameof(vmModule)); + + *pSize = 0; + Contracts.ILoader loader = _target.Contracts.Loader; + Contracts.ModuleHandle handle = loader.GetModuleHandleFromModulePtr(new TargetPointer(vmModule)); + byte[] blob = _target.Contracts.EcmaMetadata.GetReadWriteMetadata(handle); + *pSize = checked((uint)blob.Length); + } + catch (System.Exception ex) + { + hr = ex.HResult; + } +#if DEBUG + if (_legacy is not null) + { + uint sizeLocal; + int hrLocal = _legacy.GetReadWriteMetadataSize(vmModule, &sizeLocal); + Debug.ValidateHResult(hr, hrLocal); + if (hr == HResults.S_OK) + { + Debug.Assert(*pSize == sizeLocal, $"cDAC size: {*pSize}, DAC size: {sizeLocal}"); + } + } +#endif + return hr; + } + + public int FillReadWriteMetadata(ulong vmModule, byte* pBuffer, uint cbBuffer) + { + int hr = HResults.S_OK; + try + { + if (pBuffer == null) + throw new ArgumentNullException(nameof(pBuffer)); + if (vmModule == 0) + throw new ArgumentException("Module pointer must be non-zero.", nameof(vmModule)); + + Contracts.ILoader loader = _target.Contracts.Loader; + Contracts.ModuleHandle handle = loader.GetModuleHandleFromModulePtr(new TargetPointer(vmModule)); + byte[] blob = _target.Contracts.EcmaMetadata.GetReadWriteMetadata(handle); + if (cbBuffer < (uint)blob.Length) + throw Marshal.GetExceptionForHR(CorDbgHResults.ERROR_INSUFFICIENT_BUFFER)!; + + blob.AsSpan().CopyTo(new Span(pBuffer, blob.Length)); + } + catch (System.Exception ex) + { + hr = ex.HResult; + } +#if DEBUG + if (_legacy is not null) + { + byte[] bufferLocal = new byte[cbBuffer]; + int hrLocal; + fixed (byte* pLocal = bufferLocal) + { + hrLocal = _legacy.FillReadWriteMetadata(vmModule, pLocal, cbBuffer); + } + Debug.ValidateHResult(hr, hrLocal); + if (hr == HResults.S_OK) + { + Debug.Assert(new Span(pBuffer, checked((int)cbBuffer)).SequenceEqual(bufferLocal), "cDAC and DAC read-write metadata buffers differ."); + } + } +#endif + return hr; + } + public int GetSymbolsBuffer(ulong vmModule, DacDbiTargetBuffer* pTargetBuffer, SymbolFormat* pSymbolFormat) { int hr = HResults.S_OK; diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/IDacDbiInterface.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/IDacDbiInterface.cs index f08d2c66147bce..388c9b15e8d623 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/IDacDbiInterface.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/IDacDbiInterface.cs @@ -929,4 +929,10 @@ int EnumerateAsyncLocals(ulong vmMethod, ulong codeAddr, uint state, [PreserveSig] int GetGenericArgTokenIndex(ulong vmMethod, uint* pIndex); + + [PreserveSig] + int GetReadWriteMetadataSize(ulong vmModule, uint* pSize); + + [PreserveSig] + int FillReadWriteMetadata(ulong vmModule, byte* pBuffer, uint cbBuffer); } From 189919395e2c882175b598981e61326ac5c93d7a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 28 Jul 2026 20:05:44 +0000 Subject: [PATCH 2/5] Address DacDbi metadata review findings Co-authored-by: rcj1 <77995559+rcj1@users.noreply.github.com> --- src/coreclr/debug/daccess/dacdbiimpl.cpp | 2 +- .../Contracts/EcmaMetadata_1.cs | 331 +++++++++--------- .../Dbi/DacDbiImpl.cs | 4 +- 3 files changed, 175 insertions(+), 162 deletions(-) diff --git a/src/coreclr/debug/daccess/dacdbiimpl.cpp b/src/coreclr/debug/daccess/dacdbiimpl.cpp index 071c187ae95e0d..21b727b57b6456 100644 --- a/src/coreclr/debug/daccess/dacdbiimpl.cpp +++ b/src/coreclr/debug/daccess/dacdbiimpl.cpp @@ -3910,7 +3910,7 @@ namespace const ULONG64 MaxPoolBytes = 100000000; const ULONG32 MaxPoolSegments = 1000; - const ULONG32 MaxTableCount = 64; + const ULONG32 MaxTableCount = TBL_COUNT; FORCEINLINE ULONG32 AlignUp4(ULONG32 value) { 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 ccb748642cb53d..81ec4f20a553a0 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 @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using System.IO; using System.Numerics; using System.Reflection.Metadata; using System.Reflection.Metadata.Ecma335; @@ -19,6 +18,7 @@ internal sealed class EcmaMetadata_1(Target target) : IEcmaMetadata private const byte HEAP_GUID_4 = 0x02; private const byte HEAP_BLOB_4 = 0x04; private readonly Dictionary _metadata = []; + private readonly Dictionary _readWriteMetadataBlob = []; private readonly Dictionary _readOnlyMetadataAddress = []; public void Flush(FlushScope scope) @@ -26,6 +26,7 @@ public void Flush(FlushScope scope) if (scope == FlushScope.All) { _metadata.Clear(); + _readWriteMetadataBlob.Clear(); _readOnlyMetadataAddress.Clear(); } } @@ -135,174 +136,184 @@ private TargetSpan GetWebcilReadOnlyMetadataAddress(ModuleHandle handle, TargetP } case AvailableMetadataType.ReadWrite: { - TargetEcmaMetadata targetEcmaMetadata = GetTargetEcmaMetadata(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 + 1, 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((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 = new(builder.ReserveBytes(4)); - BlobWriter tablesSize = new(builder.ReserveBytes(4)); - Write4ByteAlignedString(builder, "#-"); - - // Write the heap-style Streams - - stringsOffset.WriteInt32(builder.Count); - WriteAlignedHeap(builder, targetEcmaMetadata.StringHeap); - - blobOffset.WriteInt32(builder.Count); - WriteAlignedHeap(builder, targetEcmaMetadata.BlobHeap); - - guidOffset.WriteInt32(builder.Count); - WriteAlignedHeap(builder, targetEcmaMetadata.GuidHeap); - - userStringOffset.WriteInt32(builder.Count); - WriteAlignedHeap(builder, targetEcmaMetadata.UserStringHeap); - - // Write tables stream - 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 ? (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 - - 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 (byte[] table in targetEcmaMetadata.Tables) - { - 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); - - 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 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; - // 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); - } - } + byte[] data = GetReadWriteMetadata(handle); + return MetadataReaderProvider.FromMetadataImage(ImmutableCollectionsMarshal.AsImmutableArray(data)); } default: throw new NotImplementedException(); } } - public unsafe byte[] GetReadWriteMetadata(ModuleHandle handle) + public byte[] GetReadWriteMetadata(ModuleHandle handle) { - if (GetAvailableMetadataType(handle) != AvailableMetadataType.ReadWrite) + if (GetAvailableMetadataType(handle) is not (AvailableMetadataType.ReadWrite or AvailableMetadataType.ReadWriteSavedCopy)) + { throw new ArgumentException("Module does not have read/write metadata.", nameof(handle)); + } + uint generation = GetMetadataGeneration(handle); + + if (_readWriteMetadataBlob.TryGetValue(handle, out (uint Generation, byte[] Blob) cached) && cached.Generation == generation) + { + return cached.Blob; + } + + byte[] blob = BuildReadWriteMetadataBlob(GetTargetEcmaMetadata(handle)); + _readWriteMetadataBlob[handle] = (generation, blob); + return blob; + } - MetadataReader reader = GetMetadata(handle)!; - return new ReadOnlySpan(reader.MetadataPointer, reader.MetadataLength).ToArray(); + private static byte[] BuildReadWriteMetadataBlob(TargetEcmaMetadata targetEcmaMetadata) + { + // 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 + 1, 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((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 = new(builder.ReserveBytes(4)); + BlobWriter tablesSize = new(builder.ReserveBytes(4)); + Write4ByteAlignedString(builder, "#-"); + + // Write the heap-style Streams + + stringsOffset.WriteInt32(builder.Count); + WriteAlignedHeap(builder, targetEcmaMetadata.StringHeap); + + blobOffset.WriteInt32(builder.Count); + WriteAlignedHeap(builder, targetEcmaMetadata.BlobHeap); + + guidOffset.WriteInt32(builder.Count); + WriteAlignedHeap(builder, targetEcmaMetadata.GuidHeap); + + userStringOffset.WriteInt32(builder.Count); + WriteAlignedHeap(builder, targetEcmaMetadata.UserStringHeap); + + // Write tables stream + 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 ? (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 + + 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 (byte[] table in targetEcmaMetadata.Tables) + { + builder.WriteBytes(table); + } + + // Patch the #- stream size now that the full table stream has been written. + tablesSize.WriteInt32(builder.Count - tableStreamStart); + + return builder.ToArray(); + + 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 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; + // 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); + } + } } private struct EcmaMetadataSchema diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/DacDbiImpl.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/DacDbiImpl.cs index 60da2e101fe1c2..660c8b4f0b7456 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/DacDbiImpl.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/DacDbiImpl.cs @@ -429,6 +429,7 @@ public int GetReadWriteMetadataSize(ulong vmModule, uint* pSize) public int FillReadWriteMetadata(ulong vmModule, byte* pBuffer, uint cbBuffer) { int hr = HResults.S_OK; + int blobSize = 0; try { if (pBuffer == null) @@ -439,6 +440,7 @@ public int FillReadWriteMetadata(ulong vmModule, byte* pBuffer, uint cbBuffer) Contracts.ILoader loader = _target.Contracts.Loader; Contracts.ModuleHandle handle = loader.GetModuleHandleFromModulePtr(new TargetPointer(vmModule)); byte[] blob = _target.Contracts.EcmaMetadata.GetReadWriteMetadata(handle); + blobSize = blob.Length; if (cbBuffer < (uint)blob.Length) throw Marshal.GetExceptionForHR(CorDbgHResults.ERROR_INSUFFICIENT_BUFFER)!; @@ -460,7 +462,7 @@ public int FillReadWriteMetadata(ulong vmModule, byte* pBuffer, uint cbBuffer) Debug.ValidateHResult(hr, hrLocal); if (hr == HResults.S_OK) { - Debug.Assert(new Span(pBuffer, checked((int)cbBuffer)).SequenceEqual(bufferLocal), "cDAC and DAC read-write metadata buffers differ."); + Debug.Assert(new Span(pBuffer, blobSize).SequenceEqual(bufferLocal.AsSpan(0, blobSize)), "cDAC and DAC read-write metadata buffers differ."); } } #endif From 0006b7d7826052b224042caf1b944f6793cb8133 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 28 Jul 2026 20:09:18 +0000 Subject: [PATCH 3/5] Fix DacDbi metadata validation edge cases Co-authored-by: rcj1 <77995559+rcj1@users.noreply.github.com> --- src/coreclr/debug/daccess/dacdbiimpl.cpp | 12 +++++++++++- .../Contracts/EcmaMetadata_1.cs | 6 ++++-- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/src/coreclr/debug/daccess/dacdbiimpl.cpp b/src/coreclr/debug/daccess/dacdbiimpl.cpp index 21b727b57b6456..f7c1e9a36a2cbb 100644 --- a/src/coreclr/debug/daccess/dacdbiimpl.cpp +++ b/src/coreclr/debug/daccess/dacdbiimpl.cpp @@ -4095,7 +4095,17 @@ namespace void DacDbiInterfaceImpl::SerializeReadWriteMetadata(Module * pModule, BYTE ** ppBlob, ULONG32 * pcbBlob) { + if (pModule == NULL) + { + ThrowHR(E_INVALIDARG); + } + PEAssembly * pPEAssembly = pModule->GetPEAssembly(); + if (pPEAssembly == NULL) + { + ThrowHR(E_INVALIDARG); + } + TADDR mdRWAddr = pPEAssembly->GetMDInternalRWAddress(); if (mdRWAddr == (TADDR)NULL) { @@ -4294,7 +4304,7 @@ HRESULT STDMETHODCALLTYPE DacDbiInterfaceImpl::FillReadWriteMetadata(VMPTR_Modul { if (pBuffer == NULL) { - ThrowHR(E_INVALIDARG); + ThrowHR(E_POINTER); } Module * pModule = vmModule.GetDacPtr(); 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 81ec4f20a553a0..8f5339816087aa 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 @@ -8,6 +8,7 @@ using System.Reflection.Metadata.Ecma335; using System.Reflection.PortableExecutable; using System.Runtime.InteropServices; +using System.Text; namespace Microsoft.Diagnostics.DataContractReader.Contracts; @@ -179,7 +180,7 @@ private static byte[] BuildReadWriteMetadataBlob(TargetEcmaMetadata targetEcmaMe builder.WriteUInt32(0); string version = targetEcmaMetadata.Schema.MetadataVersion; - builder.WriteInt32(AlignUp(version.Length + 1, 4)); + builder.WriteInt32(AlignUp(Encoding.UTF8.GetByteCount(version) + 1, 4)); Write4ByteAlignedString(builder, version); // reserved @@ -307,9 +308,10 @@ static void Write4ByteAlignedString(BlobBuilder builder, string value) builder.WriteUTF8(value); builder.WriteByte(0); int stringEnd = builder.Count; + int byteCount = Encoding.UTF8.GetByteCount(value); // 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++) + for (int i = stringEnd; i < bufferStart + AlignUp(byteCount + 1, 4); i++) { builder.WriteByte(0); } From 04b5ce69d01cccc818a3d91a7b156196a597d06a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 28 Jul 2026 22:18:55 +0000 Subject: [PATCH 4/5] Use DacDbi APIs for writable metadata Co-authored-by: rcj1 <77995559+rcj1@users.noreply.github.com> --- src/coreclr/debug/di/module.cpp | 29 +++++++++++++---------------- 1 file changed, 13 insertions(+), 16 deletions(-) diff --git a/src/coreclr/debug/di/module.cpp b/src/coreclr/debug/di/module.cpp index a5713d65af1e60..31d5c0b147b017 100644 --- a/src/coreclr/debug/di/module.cpp +++ b/src/coreclr/debug/di/module.cpp @@ -495,23 +495,20 @@ void CordbModule::RefreshMetaData() IfFailThrow(GetProcess()->GetDAC()->GetPEFileMDInternalRW(m_vmPEFile, &remoteMDInternalRWAddr)); if (remoteMDInternalRWAddr != (TADDR)NULL) { - // we should only be doing this once to initialize, we don't support reopen with this technique _ASSERTE(m_pIMImport == NULL); - ULONG32 mdStructuresVersion; - HRESULT hr = GetProcess()->GetDAC()->GetMDStructuresVersion(&mdStructuresVersion); - IfFailThrow(hr); - ULONG32 mdStructuresDefines; - hr = GetProcess()->GetDAC()->GetDefinesBitField(&mdStructuresDefines); - IfFailThrow(hr); - IMetaDataDispenserCustom* pDispCustom = NULL; - hr = GetProcess()->GetDispenser()->QueryInterface(IID_IMetaDataDispenserCustom, (void**)&pDispCustom); - IfFailThrow(hr); - IMDCustomDataSource* pDataSource = NULL; - hr = CreateRemoteMDInternalRWSource(remoteMDInternalRWAddr, GetProcess()->GetDataTarget(), mdStructuresDefines, mdStructuresVersion, &pDataSource); - IfFailThrow(hr); - IMetaDataImport* pImport = NULL; - hr = pDispCustom->OpenScopeOnCustomDataSource(pDataSource, 0, IID_IMetaDataImport, (IUnknown**)&m_pIMImport); - IfFailThrow(hr); + ULONG32 cbSize = 0; + IfFailThrow(GetProcess()->GetDAC()->GetReadWriteMetadataSize(m_vmModule, &cbSize)); + CoTaskMemHolder pBuffer{ (BYTE*)CoTaskMemAlloc(cbSize) }; + if (pBuffer == NULL) + { + ThrowOutOfMemory(); + } + + IfFailThrow(GetProcess()->GetDAC()->FillReadWriteMetadata(m_vmModule, pBuffer, cbSize)); + IMetaDataDispenserEx * pDisp = GetProcess()->GetDispenser(); + _ASSERTE(pDisp != NULL); + IfFailThrow(pDisp->OpenScopeOnMemory(pBuffer, cbSize, ofTakeOwnership, IID_IMetaDataImport, (IUnknown**)&m_pIMImport)); + pBuffer.Detach(); UpdateInternalMetaData(); return; } From 359d77550ae5044ccc95bf56218648d1abaf990f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 28 Jul 2026 22:29:11 +0000 Subject: [PATCH 5/5] Fix writable metadata ownership transfer Co-authored-by: rcj1 <77995559+rcj1@users.noreply.github.com> --- src/coreclr/debug/di/module.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/coreclr/debug/di/module.cpp b/src/coreclr/debug/di/module.cpp index 31d5c0b147b017..2b9c0f18deb168 100644 --- a/src/coreclr/debug/di/module.cpp +++ b/src/coreclr/debug/di/module.cpp @@ -507,8 +507,9 @@ void CordbModule::RefreshMetaData() IfFailThrow(GetProcess()->GetDAC()->FillReadWriteMetadata(m_vmModule, pBuffer, cbSize)); IMetaDataDispenserEx * pDisp = GetProcess()->GetDispenser(); _ASSERTE(pDisp != NULL); - IfFailThrow(pDisp->OpenScopeOnMemory(pBuffer, cbSize, ofTakeOwnership, IID_IMetaDataImport, (IUnknown**)&m_pIMImport)); + HRESULT hr = pDisp->OpenScopeOnMemory(pBuffer, cbSize, ofTakeOwnership, IID_IMetaDataImport, (IUnknown**)&m_pIMImport); pBuffer.Detach(); + IfFailThrow(hr); UpdateInternalMetaData(); return; }