diff --git a/docs/design/datacontracts/RuntimeTypeSystem.md b/docs/design/datacontracts/RuntimeTypeSystem.md index 08adc08c19855b..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) @@ -95,6 +97,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 (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); + bool IsValueType(TypeHandle typeHandle); // return true if the TypeHandle represents an enum type. bool IsEnum(TypeHandle typeHandle); @@ -563,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; @@ -823,6 +832,19 @@ Contracts used: return default(CorElementType); } + // 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); + if (sigType == CorElementType.ValueType && typeHandle.IsMethodTable()) + { + CorElementType internalType = (CorElementType)GetClassData(typeHandle).InternalCorElementType; + if (internalType != CorElementType.ValueType) + return internalType; + } + return sigType; + } + public bool IsValueType(TypeHandle typeHandle) { // if methodtable: check WFLAGS_HIGH for Category_ValueType diff --git a/src/coreclr/debug/daccess/dacdbiimpl.cpp b/src/coreclr/debug/daccess/dacdbiimpl.cpp index 2785108723ebba..a773654e33bc25 100644 --- a/src/coreclr/debug/daccess/dacdbiimpl.cpp +++ b/src/coreclr/debug/daccess/dacdbiimpl.cpp @@ -7353,17 +7353,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}; @@ -7383,17 +7383,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 54d1a5bf000d05..36a077548987cd 100644 --- a/src/coreclr/debug/daccess/dacdbiimpl.h +++ b/src/coreclr/debug/daccess/dacdbiimpl.h @@ -133,8 +133,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 b47cc24cf3b563..5a1527ce0810dc 100644 --- a/src/coreclr/debug/di/process.cpp +++ b/src/coreclr/debug/di/process.cpp @@ -2392,7 +2392,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; @@ -2406,7 +2406,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 0b8797a4eccb8f..c024524724e633 100644 --- a/src/coreclr/debug/inc/dacdbiinterface.h +++ b/src/coreclr/debug/inc/dacdbiinterface.h @@ -2042,9 +2042,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 f8e647b376d45a..edbeec2e00e1b4 100644 --- a/src/coreclr/inc/dacdbi.idl +++ b/src/coreclr/inc/dacdbi.idl @@ -393,8 +393,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 6bdde7301068bb..ef78fc575dfb66 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) @@ -162,6 +163,11 @@ public interface IRuntimeTypeSystem : IContract CorElementType GetSignatureCorElementType(TypeHandle typeHandle) => throw new NotImplementedException(); bool IsValueType(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 706c016420e3ba..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 @@ -561,6 +561,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.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; public bool IsContinuation(TypeHandle typeHandle) => typeHandle.IsMethodTable() @@ -864,6 +870,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 IsValueType(TypeHandle typeHandle) { if (typeHandle.IsMethodTable()) 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 9ec9c2f30c53e0..aace58d74b2219 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 @@ -1681,11 +1681,135 @@ public int GetTypeIDForType(ulong vmTypeHandle, COR_TYPEID* pId) public int GetObjectFields(nint id, uint celt, COR_FIELD* layout, uint* pceltFetched) => LegacyFallbackHelper.CanFallback() && _legacy is not null ? _legacy.GetObjectFields(id, celt, layout, pceltFetched) : HResults.E_NOTIMPL; - public int GetTypeLayout(nint id, COR_TYPE_LAYOUT* pLayout) - => LegacyFallbackHelper.CanFallback() && _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) - => LegacyFallbackHelper.CanFallback() && _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 107627c28b6e86..b2742bc06481fd 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 { @@ -494,10 +508,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 bfc99d47991f09..cb96724be12e54 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; @@ -45,4 +46,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 7abd590dff17e1..8a049c6a436af8 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 e8c15ad11f6734..6ceafece039de6 100644 --- a/src/native/managed/cdac/tests/MethodTableTests.cs +++ b/src/native/managed/cdac/tests/MethodTableTests.cs @@ -510,6 +510,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] [ClassData(typeof(MockTarget.StdArch))] public void IsValueTypeReturnsTrueForValueTypeCategories(MockTarget.Architecture arch) diff --git a/src/native/managed/cdac/tests/MockDescriptors/MockDescriptors.RuntimeTypeSystem.cs b/src/native/managed/cdac/tests/MockDescriptors/MockDescriptors.RuntimeTypeSystem.cs index ccf99761dd4dd4..6ad4ed4996405a 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);