From 0e59774db1c0a856fa51a1de868eb1bb6c0f2346 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 17 Apr 2026 20:01:18 +0000 Subject: [PATCH 1/2] Implement DacDbi layouts and add RuntimeTypeSystem contracts Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/14d41630-8260-4774-af08-174c95098f28 Co-authored-by: rcj1 <77995559+rcj1@users.noreply.github.com> --- .../design/datacontracts/RuntimeTypeSystem.md | 19 +++ src/coreclr/debug/daccess/dacdbiimpl.cpp | 12 +- src/coreclr/debug/daccess/dacdbiimpl.h | 4 +- src/coreclr/debug/di/process.cpp | 4 +- src/coreclr/debug/inc/dacdbiinterface.h | 4 +- src/coreclr/inc/dacdbi.idl | 4 +- .../Contracts/IRuntimeTypeSystem.cs | 6 + .../Contracts/RuntimeTypeSystem_1.cs | 19 +++ .../Dbi/DacDbiImpl.cs | 132 +++++++++++++++++- .../Dbi/IDacDbiInterface.cs | 18 ++- .../DumpTests/DacDbi/DacDbiObjectDumpTests.cs | 75 ++++++++++ .../DumpTests/RuntimeTypeSystemDumpTests.cs | 26 ++++ .../managed/cdac/tests/MethodTableTests.cs | 55 ++++++++ .../MockDescriptors.RuntimeTypeSystem.cs | 12 ++ 14 files changed, 370 insertions(+), 20 deletions(-) diff --git a/docs/design/datacontracts/RuntimeTypeSystem.md b/docs/design/datacontracts/RuntimeTypeSystem.md index 98f76c62771b5d..93ac138f76ba31 100644 --- a/docs/design/datacontracts/RuntimeTypeSystem.md +++ b/docs/design/datacontracts/RuntimeTypeSystem.md @@ -90,6 +90,11 @@ partial interface IRuntimeTypeSystem : IContract // HasTypeParam will return true for cases where this is the interop view, and false for normal valuetypes. public virtual CorElementType GetSignatureCorElementType(TypeHandle typeHandle); + // Internal element type of the type. Unlike GetSignatureCorElementType, this returns the underlying + // primitive type for enums and PrimitiveValueType categories (e.g. I4 for an enum with int underlying type). + // For arrays, reference types, and TypeDescs, behaves identically to GetSignatureCorElementType. + public virtual CorElementType GetInternalCorElementType(TypeHandle typeHandle); + // return true if the TypeHandle represents an enum type. bool IsEnum(TypeHandle typeHandle); // return true if the TypeHandle represents an array, and set the rank to either 0 (if the type is not an array), or the rank number if it is. @@ -747,6 +752,20 @@ Contracts used: return default(CorElementType); } + // Internal element type: returns the underlying primitive type for enums and + // PrimitiveValueType categories. For all other types, identical to GetSignatureCorElementType. + public CorElementType GetInternalCorElementType(TypeHandle typeHandle) + { + CorElementType sigType = GetSignatureCorElementType(typeHandle); + if (sigType == CorElementType.ValueType && typeHandle.IsMethodTable()) + { + CorElementType internalType = (CorElementType)GetClassData(typeHandle).InternalCorElementType; + if (internalType != CorElementType.ValueType) + return internalType; + } + return sigType; + } + // Enums have Category_PrimitiveValueType in their MethodTable flags and their // InternalCorElementType is a primitive type (I1, U1, I2, U2, I4, U4, I8, U8), // not ValueType. Regular primitive value types (IntPtr/UIntPtr) have Category_TruePrimitive. diff --git a/src/coreclr/debug/daccess/dacdbiimpl.cpp b/src/coreclr/debug/daccess/dacdbiimpl.cpp index baf957d807e7f8..d3cea0e6ac8be5 100644 --- a/src/coreclr/debug/daccess/dacdbiimpl.cpp +++ b/src/coreclr/debug/daccess/dacdbiimpl.cpp @@ -7490,17 +7490,17 @@ HRESULT STDMETHODCALLTYPE DacDbiInterfaceImpl::GetObjectFields(COR_TYPEID id, UL } -HRESULT STDMETHODCALLTYPE DacDbiInterfaceImpl::GetTypeLayout(COR_TYPEID id, COR_TYPE_LAYOUT *pLayout) +HRESULT STDMETHODCALLTYPE DacDbiInterfaceImpl::GetTypeLayout(CORDB_ADDRESS id, COR_TYPE_LAYOUT *pLayout) { if (pLayout == NULL) return E_POINTER; - if (id.token1 == 0) + if (id == 0) return CORDBG_E_CLASS_NOT_LOADED; DD_ENTER_MAY_THROW; - PTR_MethodTable mt = PTR_MethodTable(TO_TADDR(id.token1)); + PTR_MethodTable mt = PTR_MethodTable(TO_TADDR(id)); PTR_MethodTable parentMT = mt->GetParentMethodTable(); COR_TYPEID parent = {parentMT.GetAddr(), 0}; @@ -7520,17 +7520,17 @@ HRESULT STDMETHODCALLTYPE DacDbiInterfaceImpl::GetTypeLayout(COR_TYPEID id, COR_ return S_OK; } -HRESULT STDMETHODCALLTYPE DacDbiInterfaceImpl::GetArrayLayout(COR_TYPEID id, COR_ARRAY_LAYOUT *pLayout) +HRESULT STDMETHODCALLTYPE DacDbiInterfaceImpl::GetArrayLayout(CORDB_ADDRESS id, COR_ARRAY_LAYOUT *pLayout) { if (pLayout == NULL) return E_POINTER; - if (id.token1 == 0) + if (id == 0) return CORDBG_E_CLASS_NOT_LOADED; DD_ENTER_MAY_THROW; - PTR_MethodTable mt = PTR_MethodTable(TO_TADDR(id.token1)); + PTR_MethodTable mt = PTR_MethodTable(TO_TADDR(id)); if (!mt->IsStringOrArray()) return E_INVALIDARG; diff --git a/src/coreclr/debug/daccess/dacdbiimpl.h b/src/coreclr/debug/daccess/dacdbiimpl.h index 6b118b6d3d199a..b307a83d14d8cc 100644 --- a/src/coreclr/debug/daccess/dacdbiimpl.h +++ b/src/coreclr/debug/daccess/dacdbiimpl.h @@ -136,8 +136,8 @@ class DacDbiInterfaceImpl : HRESULT STDMETHODCALLTYPE GetTypeIDForType(VMPTR_TypeHandle vmTypeHandle, COR_TYPEID *pID); HRESULT STDMETHODCALLTYPE GetObjectFields(COR_TYPEID id, ULONG32 celt, COR_FIELD *layout, ULONG32 *pceltFetched); - HRESULT STDMETHODCALLTYPE GetTypeLayout(COR_TYPEID id, COR_TYPE_LAYOUT *pLayout); - HRESULT STDMETHODCALLTYPE GetArrayLayout(COR_TYPEID id, COR_ARRAY_LAYOUT *pLayout); + HRESULT STDMETHODCALLTYPE GetTypeLayout(CORDB_ADDRESS id, COR_TYPE_LAYOUT *pLayout); + HRESULT STDMETHODCALLTYPE GetArrayLayout(CORDB_ADDRESS id, COR_ARRAY_LAYOUT *pLayout); HRESULT STDMETHODCALLTYPE GetGCHeapInformation(OUT COR_HEAPINFO * pHeapInfo); HRESULT STDMETHODCALLTYPE GetPEFileMDInternalRW(VMPTR_PEAssembly vmPEAssembly, OUT TADDR* pAddrMDInternalRW); #ifdef FEATURE_CODE_VERSIONING diff --git a/src/coreclr/debug/di/process.cpp b/src/coreclr/debug/di/process.cpp index 7ef5087c5af15b..0822d1e218d499 100644 --- a/src/coreclr/debug/di/process.cpp +++ b/src/coreclr/debug/di/process.cpp @@ -2484,7 +2484,7 @@ COM_METHOD CordbProcess::GetArrayLayout(COR_TYPEID id, COR_ARRAY_LAYOUT *pLayout HRESULT hr = S_OK; PUBLIC_API_BEGIN(this); - hr = GetProcess()->GetDAC()->GetArrayLayout(id, pLayout); + hr = GetProcess()->GetDAC()->GetArrayLayout((CORDB_ADDRESS)id.token1, pLayout); PUBLIC_API_END(hr); return hr; @@ -2498,7 +2498,7 @@ COM_METHOD CordbProcess::GetTypeLayout(COR_TYPEID id, COR_TYPE_LAYOUT *pLayout) HRESULT hr = S_OK; PUBLIC_API_BEGIN(this); - hr = GetProcess()->GetDAC()->GetTypeLayout(id, pLayout); + hr = GetProcess()->GetDAC()->GetTypeLayout((CORDB_ADDRESS)id.token1, pLayout); PUBLIC_API_END(hr); return hr; diff --git a/src/coreclr/debug/inc/dacdbiinterface.h b/src/coreclr/debug/inc/dacdbiinterface.h index 3e2cfa6061d336..3feece638f1c45 100644 --- a/src/coreclr/debug/inc/dacdbiinterface.h +++ b/src/coreclr/debug/inc/dacdbiinterface.h @@ -2143,9 +2143,9 @@ IDacDbiInterface : public IUnknown virtual HRESULT STDMETHODCALLTYPE GetObjectFields(COR_TYPEID id, ULONG32 celt, OUT COR_FIELD * layout, OUT ULONG32 * pceltFetched) = 0; - virtual HRESULT STDMETHODCALLTYPE GetTypeLayout(COR_TYPEID id, COR_TYPE_LAYOUT * pLayout) = 0; + virtual HRESULT STDMETHODCALLTYPE GetTypeLayout(CORDB_ADDRESS id, COR_TYPE_LAYOUT * pLayout) = 0; - virtual HRESULT STDMETHODCALLTYPE GetArrayLayout(COR_TYPEID id, COR_ARRAY_LAYOUT * pLayout) = 0; + virtual HRESULT STDMETHODCALLTYPE GetArrayLayout(CORDB_ADDRESS id, COR_ARRAY_LAYOUT * pLayout) = 0; virtual HRESULT STDMETHODCALLTYPE GetGCHeapInformation(OUT COR_HEAPINFO * pHeapInfo) = 0; diff --git a/src/coreclr/inc/dacdbi.idl b/src/coreclr/inc/dacdbi.idl index 871864038a1711..207f2ea11e344c 100644 --- a/src/coreclr/inc/dacdbi.idl +++ b/src/coreclr/inc/dacdbi.idl @@ -402,8 +402,8 @@ interface IDacDbiInterface : IUnknown HRESULT GetTypeID([in] CORDB_ADDRESS obj, [out] COR_TYPEID * pType); HRESULT GetTypeIDForType([in] VMPTR_TypeHandle vmTypeHandle, [out] COR_TYPEID * pId); HRESULT GetObjectFields([in] COR_TYPEID id, [in] ULONG32 celt, [out] COR_FIELD * layout, [out] ULONG32 * pceltFetched); - HRESULT GetTypeLayout([in] COR_TYPEID id, [out] COR_TYPE_LAYOUT * pLayout); - HRESULT GetArrayLayout([in] COR_TYPEID id, [out] COR_ARRAY_LAYOUT * pLayout); + HRESULT GetTypeLayout([in] CORDB_ADDRESS id, [out] COR_TYPE_LAYOUT * pLayout); + HRESULT GetArrayLayout([in] CORDB_ADDRESS id, [out] COR_ARRAY_LAYOUT * pLayout); HRESULT GetGCHeapInformation([out] COR_HEAPINFO * pHeapInfo); // PE File diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IRuntimeTypeSystem.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IRuntimeTypeSystem.cs index 4db9c40c20e1f5..4647a49d8df9bb 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IRuntimeTypeSystem.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IRuntimeTypeSystem.cs @@ -115,6 +115,7 @@ public interface IRuntimeTypeSystem : IContract // True if the MethodTable is the sentinel value associated with unallocated space in the managed heap bool IsFreeObjectMethodTable(TypeHandle typeHandle) => throw new NotImplementedException(); bool IsString(TypeHandle typeHandle) => throw new NotImplementedException(); + bool IsObjRef(TypeHandle typeHandle) => throw new NotImplementedException(); // True if the MethodTable represents a type that contains managed references bool ContainsGCPointers(TypeHandle typeHandle) => throw new NotImplementedException(); // True if the type requires 8-byte alignment on platforms that don't 8-byte align by default (FEATURE_64BIT_ALIGNMENT) @@ -155,6 +156,11 @@ public interface IRuntimeTypeSystem : IContract // HasTypeParam will return true for cases where this is the interop view CorElementType GetSignatureCorElementType(TypeHandle typeHandle) => throw new NotImplementedException(); + // Internal element type of the type. Unlike GetSignatureCorElementType, this returns the underlying primitive + // type for enums (e.g. I4 for an enum with int underlying type) and for PrimitiveValueType categories. + // For arrays, reference types, and TypeDescs, behaves identically to GetSignatureCorElementType. + CorElementType GetInternalCorElementType(TypeHandle typeHandle) => throw new NotImplementedException(); + // return true if the TypeHandle represents an enum type. bool IsEnum(TypeHandle typeHandle) => throw new NotImplementedException(); diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeTypeSystem_1.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeTypeSystem_1.cs index 496f64f2608b92..82a4ca9d69c411 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeTypeSystem_1.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeTypeSystem_1.cs @@ -550,6 +550,12 @@ private Data.EEClass GetClassData(TypeHandle typeHandle) public bool IsFreeObjectMethodTable(TypeHandle typeHandle) => FreeObjectMethodTablePointer == typeHandle.Address; public bool IsString(TypeHandle typeHandle) => !typeHandle.IsMethodTable() ? false : _methodTables[typeHandle.Address].Flags.IsString; + public bool IsObjRef(TypeHandle typeHandle) + { + CorElementType elementType = GetSignatureCorElementType(typeHandle); + // Keep this aligned with CorTypeInfo::IsObjRef semantics for signature element types. + return elementType is CorElementType.String or CorElementType.Class or CorElementType.Array or CorElementType.Object or CorElementType.SzArray; + } public bool ContainsGCPointers(TypeHandle typeHandle) => !typeHandle.IsMethodTable() ? false : _methodTables[typeHandle.Address].Flags.ContainsGCPointers; public bool RequiresAlign8(TypeHandle typeHandle) => !typeHandle.IsMethodTable() ? false : _methodTables[typeHandle.Address].Flags.RequiresAlign8; public bool IsContinuation(TypeHandle typeHandle) => typeHandle.IsMethodTable() @@ -778,6 +784,19 @@ public CorElementType GetSignatureCorElementType(TypeHandle typeHandle) return default; } + public CorElementType GetInternalCorElementType(TypeHandle typeHandle) + { + CorElementType sigType = GetSignatureCorElementType(typeHandle); + if (sigType == CorElementType.ValueType && typeHandle.IsMethodTable()) + { + CorElementType internalType = (CorElementType)GetClassData(typeHandle).InternalCorElementType; + if (internalType != CorElementType.ValueType) + return internalType; + } + + return sigType; + } + public bool IsEnum(TypeHandle typeHandle) { // Enums have Category_PrimitiveValueType in their MethodTable flags and their 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 30eaa905e1b078..1825c79cd1ade9 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 @@ -1087,11 +1087,135 @@ public int GetTypeIDForType(ulong vmTypeHandle, COR_TYPEID* pId) public int GetObjectFields(nint id, uint celt, COR_FIELD* layout, uint* pceltFetched) => _legacy is not null ? _legacy.GetObjectFields(id, celt, layout, pceltFetched) : HResults.E_NOTIMPL; - public int GetTypeLayout(nint id, COR_TYPE_LAYOUT* pLayout) - => _legacy is not null ? _legacy.GetTypeLayout(id, pLayout) : HResults.E_NOTIMPL; + public int GetTypeLayout(ulong id, COR_TYPE_LAYOUT* pLayout) + { + int hr = HResults.S_OK; + try + { + if (pLayout is null) + throw new NullReferenceException(nameof(pLayout)); + + if (id == 0) + throw Marshal.GetExceptionForHR(CorDbgHResults.CORDBG_E_CLASS_NOT_LOADED)!; + + IRuntimeTypeSystem rts = _target.Contracts.RuntimeTypeSystem; + TypeHandle typeHandle = rts.GetTypeHandle(new TargetPointer((ulong)id)); + + TargetPointer parentMT = rts.GetParentMethodTable(typeHandle); + pLayout->parentID.token1 = parentMT.Value; + pLayout->parentID.token2 = 0; + pLayout->objectSize = rts.GetBaseSize(typeHandle); + ushort numInstanceFields = rts.GetNumInstanceFields(typeHandle); + if (parentMT != TargetPointer.Null) + { + TypeHandle parentHandle = rts.GetTypeHandle(parentMT); + numInstanceFields -= rts.GetNumInstanceFields(parentHandle); + } + pLayout->numFields = numInstanceFields; + pLayout->boxOffset = rts.IsObjRef(typeHandle) ? 0u : (uint)_target.PointerSize; + pLayout->type = (int)(rts.IsString(typeHandle) ? CorElementType.String : rts.GetInternalCorElementType(typeHandle)); + } + catch (System.Exception ex) + { + hr = ex.HResult; + } + +#if DEBUG + if (_legacy is not null) + { + COR_TYPE_LAYOUT resultLocal; + int hrLocal = _legacy.GetTypeLayout(id, &resultLocal); + Debug.ValidateHResult(hr, hrLocal); + if (hr == HResults.S_OK) + { + Debug.Assert(pLayout->parentID.token1 == resultLocal.parentID.token1, $"cDAC: {pLayout->parentID.token1:x}, DAC: {resultLocal.parentID.token1:x}"); + Debug.Assert(pLayout->parentID.token2 == resultLocal.parentID.token2, $"cDAC: {pLayout->parentID.token2:x}, DAC: {resultLocal.parentID.token2:x}"); + Debug.Assert(pLayout->objectSize == resultLocal.objectSize, $"cDAC: {pLayout->objectSize}, DAC: {resultLocal.objectSize}"); + Debug.Assert(pLayout->numFields == resultLocal.numFields, $"cDAC: {pLayout->numFields}, DAC: {resultLocal.numFields}"); + Debug.Assert(pLayout->boxOffset == resultLocal.boxOffset, $"cDAC: {pLayout->boxOffset}, DAC: {resultLocal.boxOffset}"); + Debug.Assert(pLayout->type == resultLocal.type, $"cDAC: {pLayout->type}, DAC: {resultLocal.type}"); + } + } +#endif + + return hr; + } + + public int GetArrayLayout(ulong id, COR_ARRAY_LAYOUT* pLayout) + { + int hr = HResults.S_OK; + try + { + if (pLayout is null) + throw new NullReferenceException(nameof(pLayout)); + + if (id == 0) + throw Marshal.GetExceptionForHR(CorDbgHResults.CORDBG_E_CLASS_NOT_LOADED)!; + IRuntimeTypeSystem rts = _target.Contracts.RuntimeTypeSystem; + TypeHandle arrayOrStringTypeHandle = rts.GetTypeHandle(new TargetPointer(id)); + uint pointerSize = (uint)_target.PointerSize; + + if (rts.IsString(arrayOrStringTypeHandle)) + { + TypeHandle charTypeHandle = rts.GetPrimitiveType(CorElementType.Char); + pLayout->componentID.token1 = charTypeHandle.Address.Value; + pLayout->componentID.token2 = 0; + pLayout->componentType = CorElementType.Char; + pLayout->firstElementOffset = pointerSize + 4; + pLayout->elementSize = sizeof(char); + pLayout->countOffset = pointerSize; + pLayout->rankSize = 4; + pLayout->numRanks = 1; + pLayout->rankOffset = pointerSize; + } + else + { + if (!rts.IsArray(arrayOrStringTypeHandle, out uint rank)) + throw Marshal.GetExceptionForHR(HResults.E_INVALIDARG)!; + + TypeHandle componentTypeHandle = rts.GetTypeParam(arrayOrStringTypeHandle); + CorElementType componentType = rts.IsString(componentTypeHandle) ? CorElementType.String : rts.GetInternalCorElementType(componentTypeHandle); + pLayout->componentID.token1 = componentTypeHandle.Address.Value; + pLayout->componentID.token2 = 0; + pLayout->componentType = componentType; + Target.TypeInfo objectHeaderTypeInfo = _target.GetTypeInfo(DataType.ObjectHeader); + uint objectHeaderSize = (uint)objectHeaderTypeInfo.Size!.Value; + pLayout->firstElementOffset = rts.GetBaseSize(arrayOrStringTypeHandle) - objectHeaderSize; + pLayout->elementSize = rts.GetComponentSize(arrayOrStringTypeHandle); + pLayout->countOffset = pointerSize; + pLayout->rankSize = 4; + pLayout->numRanks = rank; + pLayout->rankOffset = rank > 1 ? pointerSize * 2 : pointerSize; + } + } + catch (System.Exception ex) + { + hr = ex.HResult; + } + +#if DEBUG + if (_legacy is not null) + { + COR_ARRAY_LAYOUT resultLocal; + int hrLocal = _legacy.GetArrayLayout(id, &resultLocal); + Debug.ValidateHResult(hr, hrLocal); + if (hr == HResults.S_OK) + { + Debug.Assert(pLayout->componentID.token1 == resultLocal.componentID.token1, $"cDAC: {pLayout->componentID.token1:x}, DAC: {resultLocal.componentID.token1:x}"); + Debug.Assert(pLayout->componentID.token2 == resultLocal.componentID.token2, $"cDAC: {pLayout->componentID.token2:x}, DAC: {resultLocal.componentID.token2:x}"); + Debug.Assert(pLayout->componentType == resultLocal.componentType, $"cDAC: {pLayout->componentType}, DAC: {resultLocal.componentType}"); + Debug.Assert(pLayout->firstElementOffset == resultLocal.firstElementOffset, $"cDAC: {pLayout->firstElementOffset}, DAC: {resultLocal.firstElementOffset}"); + Debug.Assert(pLayout->elementSize == resultLocal.elementSize, $"cDAC: {pLayout->elementSize}, DAC: {resultLocal.elementSize}"); + Debug.Assert(pLayout->countOffset == resultLocal.countOffset, $"cDAC: {pLayout->countOffset}, DAC: {resultLocal.countOffset}"); + Debug.Assert(pLayout->rankSize == resultLocal.rankSize, $"cDAC: {pLayout->rankSize}, DAC: {resultLocal.rankSize}"); + Debug.Assert(pLayout->numRanks == resultLocal.numRanks, $"cDAC: {pLayout->numRanks}, DAC: {resultLocal.numRanks}"); + Debug.Assert(pLayout->rankOffset == resultLocal.rankOffset, $"cDAC: {pLayout->rankOffset}, DAC: {resultLocal.rankOffset}"); + } + } +#endif - public int GetArrayLayout(nint id, nint pLayout) - => _legacy is not null ? _legacy.GetArrayLayout(id, pLayout) : HResults.E_NOTIMPL; + return hr; + } public int GetGCHeapInformation(COR_HEAPINFO* pHeapInfo) { 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 d26605e3b86f5a..6c4087e1134f2e 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 @@ -3,6 +3,7 @@ using System.Runtime.InteropServices; using System.Runtime.InteropServices.Marshalling; +using CorElementType = Microsoft.Diagnostics.DataContractReader.Contracts.CorElementType; namespace Microsoft.Diagnostics.DataContractReader.Legacy; @@ -127,6 +128,19 @@ public struct COR_TYPE_LAYOUT public int type; } +[StructLayout(LayoutKind.Sequential)] +public struct COR_ARRAY_LAYOUT +{ + public COR_TYPEID componentID; + public CorElementType componentType; + public uint firstElementOffset; + public uint elementSize; + public uint countOffset; + public uint rankSize; + public uint numRanks; + public uint rankOffset; +} + [StructLayout(LayoutKind.Sequential)] public struct COR_FIELD { @@ -505,10 +519,10 @@ public unsafe partial interface IDacDbiInterface int GetObjectFields(nint id, uint celt, COR_FIELD* layout, uint* pceltFetched); [PreserveSig] - int GetTypeLayout(nint id, COR_TYPE_LAYOUT* pLayout); + int GetTypeLayout(ulong id, COR_TYPE_LAYOUT* pLayout); [PreserveSig] - int GetArrayLayout(nint id, nint pLayout); + int GetArrayLayout(ulong id, COR_ARRAY_LAYOUT* pLayout); [PreserveSig] int GetGCHeapInformation(COR_HEAPINFO* pHeapInfo); diff --git a/src/native/managed/cdac/tests/DumpTests/DacDbi/DacDbiObjectDumpTests.cs b/src/native/managed/cdac/tests/DumpTests/DacDbi/DacDbiObjectDumpTests.cs index 282387e49d3355..7a55d727617c5c 100644 --- a/src/native/managed/cdac/tests/DumpTests/DacDbi/DacDbiObjectDumpTests.cs +++ b/src/native/managed/cdac/tests/DumpTests/DacDbi/DacDbiObjectDumpTests.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using Microsoft.Diagnostics.DataContractReader.Legacy; +using Microsoft.Diagnostics.DataContractReader.Contracts; using Xunit; namespace Microsoft.Diagnostics.DataContractReader.DumpTests; @@ -44,4 +45,78 @@ public unsafe void GetHandleAddressFromVmHandle_IsIdentity(TestConfiguration con Assert.Equal(testAddr, result); } + [ConditionalTheory] + [MemberData(nameof(TestConfigurations))] + public unsafe void GetTypeLayout_Object_CrossValidatesContract(TestConfiguration config) + { + InitializeDumpTest(config); + DacDbiImpl dbi = CreateDacDbi(); + + TargetPointer objectMT = Target.ReadPointer(Target.ReadGlobalPointer("ObjectMethodTable")); + TypeHandle objectHandle = Target.Contracts.RuntimeTypeSystem.GetTypeHandle(objectMT); + + COR_TYPE_LAYOUT layout; + int hr = dbi.GetTypeLayout(objectMT.Value, &layout); + Assert.Equal(System.HResults.S_OK, hr); + + Assert.Equal(Target.Contracts.RuntimeTypeSystem.GetParentMethodTable(objectHandle).Value, layout.parentID.token1); + Assert.Equal(Target.Contracts.RuntimeTypeSystem.GetBaseSize(objectHandle), layout.objectSize); + Assert.Equal(Target.Contracts.RuntimeTypeSystem.GetNumInstanceFields(objectHandle), layout.numFields); + Assert.Equal(0u, layout.boxOffset); + Assert.Equal((int)CorElementType.Class, layout.type); + } + + [ConditionalTheory] + [MemberData(nameof(TestConfigurations))] + public unsafe void GetArrayLayout_ObjectArray_CrossValidatesContract(TestConfiguration config) + { + InitializeDumpTest(config); + DacDbiImpl dbi = CreateDacDbi(); + IRuntimeTypeSystem rts = Target.Contracts.RuntimeTypeSystem; + + TargetPointer arrayMT = Target.ReadPointer(Target.ReadGlobalPointer("ObjectArrayMethodTable")); + TypeHandle arrayHandle = rts.GetTypeHandle(arrayMT); + TypeHandle componentHandle = rts.GetTypeParam(arrayHandle); + Assert.True(rts.IsArray(arrayHandle, out uint rank)); + + COR_ARRAY_LAYOUT layout; + int hr = dbi.GetArrayLayout(arrayMT.Value, &layout); + Assert.Equal(System.HResults.S_OK, hr); + + CorElementType expectedComponentType = rts.IsString(componentHandle) + ? CorElementType.String + : rts.GetSignatureCorElementType(componentHandle); + + Assert.Equal(componentHandle.Address.Value, layout.componentID.token1); + Assert.Equal(expectedComponentType, layout.componentType); + Assert.Equal((uint)Target.PointerSize, layout.elementSize); + Assert.Equal((uint)Target.PointerSize, layout.countOffset); + Assert.Equal((uint)sizeof(uint), layout.rankSize); + Assert.Equal(rank, layout.numRanks); + Assert.Equal((uint)Target.PointerSize, layout.rankOffset); + } + + [ConditionalTheory] + [MemberData(nameof(TestConfigurations))] + public unsafe void GetArrayLayout_String_HasExpectedLayout(TestConfiguration config) + { + InitializeDumpTest(config); + DacDbiImpl dbi = CreateDacDbi(); + IRuntimeTypeSystem rts = Target.Contracts.RuntimeTypeSystem; + + TargetPointer stringMT = Target.ReadPointer(Target.ReadGlobalPointer("StringMethodTable")); + COR_ARRAY_LAYOUT layout; + int hr = dbi.GetArrayLayout(stringMT.Value, &layout); + Assert.Equal(System.HResults.S_OK, hr); + + Assert.Equal(rts.GetPrimitiveType(CorElementType.Char).Address.Value, layout.componentID.token1); + Assert.Equal(CorElementType.Char, layout.componentType); + Assert.Equal((uint)Target.PointerSize + sizeof(uint), layout.firstElementOffset); + Assert.Equal((uint)sizeof(char), layout.elementSize); + Assert.Equal((uint)Target.PointerSize, layout.countOffset); + Assert.Equal((uint)sizeof(uint), layout.rankSize); + Assert.Equal(1u, layout.numRanks); + Assert.Equal((uint)Target.PointerSize, layout.rankOffset); + } + } diff --git a/src/native/managed/cdac/tests/DumpTests/RuntimeTypeSystemDumpTests.cs b/src/native/managed/cdac/tests/DumpTests/RuntimeTypeSystemDumpTests.cs index f1a082b2b20ba7..1a3f9c0173033a 100644 --- a/src/native/managed/cdac/tests/DumpTests/RuntimeTypeSystemDumpTests.cs +++ b/src/native/managed/cdac/tests/DumpTests/RuntimeTypeSystemDumpTests.cs @@ -228,6 +228,32 @@ public void RuntimeTypeSystem_StringCorElementTypeIsClass(TestConfiguration conf Assert.Equal(CorElementType.Class, corType); } + [ConditionalTheory] + [MemberData(nameof(TestConfigurations))] + public void RuntimeTypeSystem_IsObjRef_AreConsistent(TestConfiguration config) + { + InitializeDumpTest(config); + IRuntimeTypeSystem rts = Target.Contracts.RuntimeTypeSystem; + ILoader loader = Target.Contracts.Loader; + + TargetPointer objectMT = Target.ReadPointer(Target.ReadGlobalPointer("ObjectMethodTable")); + TargetPointer stringMT = Target.ReadPointer(Target.ReadGlobalPointer("StringMethodTable")); + TargetPointer objectArrayMT = Target.ReadPointer(Target.ReadGlobalPointer("ObjectArrayMethodTable")); + + TypeHandle objectHandle = rts.GetTypeHandle(objectMT); + TypeHandle stringHandle = rts.GetTypeHandle(stringMT); + TypeHandle objectArrayHandle = rts.GetTypeHandle(objectArrayMT); + + TargetPointer systemAssembly = loader.GetSystemAssembly(); + ModuleHandle coreLibModule = loader.GetModuleHandleFromAssemblyPtr(systemAssembly); + TypeHandle intPtrHandle = rts.GetTypeByNameAndModule("IntPtr", "System", coreLibModule); + + Assert.True(rts.IsObjRef(objectHandle)); + Assert.True(rts.IsObjRef(stringHandle)); + Assert.True(rts.IsObjRef(objectArrayHandle)); + Assert.False(rts.IsObjRef(intPtrHandle)); + } + [ConditionalTheory] [MemberData(nameof(TestConfigurations))] public void RuntimeTypeSystem_ObjectMethodTableHasIntroducedMethods(TestConfiguration config) diff --git a/src/native/managed/cdac/tests/MethodTableTests.cs b/src/native/managed/cdac/tests/MethodTableTests.cs index 44cdb5c3ee66be..b5316c1b39531a 100644 --- a/src/native/managed/cdac/tests/MethodTableTests.cs +++ b/src/native/managed/cdac/tests/MethodTableTests.cs @@ -509,6 +509,61 @@ public void ValidateContinuationMethodTablePointer(MockTarget.Architecture arch) Assert.True(contract.IsContinuation(continuationTypeHandle)); } + [Theory] + [ClassData(typeof(MockTarget.StdArch))] + public void IsObjRef_ReturnsExpectedValues(MockTarget.Architecture arch) + { + TargetPointer objectTypePtr = default; + TargetPointer stringTypePtr = default; + TargetPointer szArrayTypePtr = default; + TargetPointer truePrimitiveTypePtr = default; + + TestPlaceholderTarget target = CreateTarget( + arch, + rtsBuilder => + { + TargetTestHelpers helpers = rtsBuilder.Builder.TargetTestHelpers; + objectTypePtr = rtsBuilder.SystemObjectMethodTable.Address; + + MockEEClass stringEEClass = rtsBuilder.AddEEClass("System.String"); + MockMethodTable stringMethodTable = rtsBuilder.AddMethodTable("System.String"); + stringMethodTable.MTFlags = (uint)MethodTableFlags_1.WFLAGS_HIGH.HasComponentSize | 2; + stringMethodTable.BaseSize = helpers.StringBaseSize; + stringMethodTable.ParentMethodTable = objectTypePtr; + stringTypePtr = stringMethodTable.Address; + stringEEClass.MethodTable = stringTypePtr; + stringMethodTable.EEClassOrCanonMT = stringEEClass.Address; + + MockEEClass szArrayEEClass = rtsBuilder.AddEEClass("System.Int32[]"); + MockMethodTable szArrayMethodTable = rtsBuilder.AddMethodTable("System.Int32[]"); + szArrayMethodTable.MTFlags = + (uint)MethodTableFlags_1.WFLAGS_HIGH.HasComponentSize + | (uint)MethodTableFlags_1.WFLAGS_HIGH.Category_Array + | (uint)MethodTableFlags_1.WFLAGS_HIGH.Category_IfArrayThenSzArray + | 4; + szArrayMethodTable.BaseSize = helpers.ArrayBaseBaseSize; + szArrayMethodTable.ParentMethodTable = objectTypePtr; + szArrayTypePtr = szArrayMethodTable.Address; + szArrayEEClass.MethodTable = szArrayTypePtr; + szArrayMethodTable.EEClassOrCanonMT = szArrayEEClass.Address; + + MockEEClass truePrimitiveEEClass = rtsBuilder.AddEEClass("System.IntPtr"); + truePrimitiveEEClass.InternalCorElementType = (byte)CorElementType.I; + MockMethodTable truePrimitiveMethodTable = rtsBuilder.AddMethodTable("System.IntPtr"); + truePrimitiveMethodTable.MTFlags = (uint)MethodTableFlags_1.WFLAGS_HIGH.Category_TruePrimitive; + truePrimitiveMethodTable.BaseSize = helpers.ObjectBaseSize; + truePrimitiveTypePtr = truePrimitiveMethodTable.Address; + truePrimitiveEEClass.MethodTable = truePrimitiveTypePtr; + truePrimitiveMethodTable.EEClassOrCanonMT = truePrimitiveEEClass.Address; + }); + + IRuntimeTypeSystem contract = target.Contracts.RuntimeTypeSystem; + Assert.True(contract.IsObjRef(contract.GetTypeHandle(objectTypePtr))); + Assert.True(contract.IsObjRef(contract.GetTypeHandle(stringTypePtr))); + Assert.True(contract.IsObjRef(contract.GetTypeHandle(szArrayTypePtr))); + Assert.False(contract.IsObjRef(contract.GetTypeHandle(truePrimitiveTypePtr))); + } + [Theory] [MemberData(nameof(StdArchBool))] public void RequiresAlign8(MockTarget.Architecture arch, bool flagSet) diff --git a/src/native/managed/cdac/tests/MockDescriptors/MockDescriptors.RuntimeTypeSystem.cs b/src/native/managed/cdac/tests/MockDescriptors/MockDescriptors.RuntimeTypeSystem.cs index 8510203e0e5ae0..b16a2073654902 100644 --- a/src/native/managed/cdac/tests/MockDescriptors/MockDescriptors.RuntimeTypeSystem.cs +++ b/src/native/managed/cdac/tests/MockDescriptors/MockDescriptors.RuntimeTypeSystem.cs @@ -139,6 +139,18 @@ public ushort NumMethods set => WriteUInt16Field(NumMethodsFieldName, value); } + public byte InternalCorElementType + { + get => ReadByteField(InternalCorElementTypeFieldName); + set => WriteByteField(InternalCorElementTypeFieldName, value); + } + + public ushort NumInstanceFields + { + get => ReadUInt16Field(NumInstanceFieldsFieldName); + set => WriteUInt16Field(NumInstanceFieldsFieldName, value); + } + public ushort NumNonVirtualSlots { get => ReadUInt16Field(NumNonVirtualSlotsFieldName); From 153491e28e421f96b911cd7a84d4c6dde37eb235 Mon Sep 17 00:00:00 2001 From: rcj1 Date: Fri, 8 May 2026 12:25:51 -0700 Subject: [PATCH 2/2] jkotas comments --- docs/design/datacontracts/RuntimeTypeSystem.md | 9 ++++++--- .../Contracts/RuntimeTypeSystem_1.cs | 2 +- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/docs/design/datacontracts/RuntimeTypeSystem.md b/docs/design/datacontracts/RuntimeTypeSystem.md index 558b79776a8b37..3f5ec3330641c7 100644 --- a/docs/design/datacontracts/RuntimeTypeSystem.md +++ b/docs/design/datacontracts/RuntimeTypeSystem.md @@ -52,6 +52,8 @@ partial interface IRuntimeTypeSystem : IContract // True if the MethodTable is the sentinel value associated with unallocated space in the managed heap public virtual bool IsFreeObjectMethodTable(TypeHandle typeHandle); public virtual bool IsString(TypeHandle typeHandle); + // True if the type is a GC-collectable object reference. + public virtual bool IsObjRef(TypeHandle typeHandle); // True if the MethodTable represents a type that contains managed references public virtual bool ContainsGCPointers(TypeHandle typeHandle); // True if the type requires 8-byte alignment on platforms that don't 8-byte align by default (FEATURE_64BIT_ALIGNMENT) @@ -96,7 +98,7 @@ partial interface IRuntimeTypeSystem : IContract public virtual CorElementType GetSignatureCorElementType(TypeHandle typeHandle); // Internal element type of the type. Unlike GetSignatureCorElementType, this returns the underlying - // primitive type for enums and PrimitiveValueType categories (e.g. I4 for an enum with int underlying type). + // primitive type for enums (e.g. I4 for an enum with int underlying type). // For arrays, reference types, and TypeDescs, behaves identically to GetSignatureCorElementType. public virtual CorElementType GetInternalCorElementType(TypeHandle typeHandle); @@ -568,6 +570,8 @@ Contracts used: public bool IsString(TypeHandle TypeHandle) => !typeHandle.IsMethodTable() ? false : _methodTables[TypeHandle.Address].Flags.IsString; + public bool IsObjRef(TypeHandle typeHandle) => // Returns true if GetSignatureCorElementType returns Class, Array, or SzArray. + public bool ContainsGCPointers(TypeHandle TypeHandle) => !typeHandle.IsMethodTable() ? false : _methodTables[TypeHandle.Address].Flags.ContainsGCPointers; public bool RequiresAlign8(TypeHandle typeHandle) => !typeHandle.IsMethodTable() ? false : _methodTables[typeHandle.Address].Flags.RequiresAlign8; @@ -828,8 +832,7 @@ Contracts used: return default(CorElementType); } - // Internal element type: returns the underlying primitive type for enums and - // PrimitiveValueType categories. For all other types, identical to GetSignatureCorElementType. + // Internal element type: returns the underlying primitive type for enums. For all other types, identical to GetSignatureCorElementType. public CorElementType GetInternalCorElementType(TypeHandle typeHandle) { CorElementType sigType = GetSignatureCorElementType(typeHandle); diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeTypeSystem_1.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeTypeSystem_1.cs index 1b6c00b6d1c112..b01296de44b209 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeTypeSystem_1.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeTypeSystem_1.cs @@ -565,7 +565,7 @@ public bool IsObjRef(TypeHandle typeHandle) { CorElementType elementType = GetSignatureCorElementType(typeHandle); // Keep this aligned with CorTypeInfo::IsObjRef semantics for signature element types. - return elementType is CorElementType.String or CorElementType.Class or CorElementType.Array or CorElementType.Object or CorElementType.SzArray; + return elementType is CorElementType.Class or CorElementType.Array or CorElementType.SzArray; } public bool ContainsGCPointers(TypeHandle typeHandle) => !typeHandle.IsMethodTable() ? false : _methodTables[typeHandle.Address].Flags.ContainsGCPointers; public bool RequiresAlign8(TypeHandle typeHandle) => !typeHandle.IsMethodTable() ? false : _methodTables[typeHandle.Address].Flags.RequiresAlign8;