diff --git a/docs/design/datacontracts/RuntimeTypeSystem.md b/docs/design/datacontracts/RuntimeTypeSystem.md new file mode 100644 index 0000000000000..f8382ee4fdc59 --- /dev/null +++ b/docs/design/datacontracts/RuntimeTypeSystem.md @@ -0,0 +1,224 @@ +# Contract RuntimeTypeSystem + +This contract is for exploring the properties of the runtime types of values on the managed heap or on the stack in a .NET process. + +## APIs of contract + +A `MethodTable` is the runtime representation of the type information about a value. Given a `TargetPointer` address, the `RuntimeTypeSystem` contract provides a `MethodTableHandle` for querying the `MethodTable`. + +``` csharp +struct MethodTableHandle +{ + // no public properties or constructors + + internal TargetPointer Address { get; } +} +``` + +``` csharp + #region MethodTable inspection APIs + public virtual MethodTableHandle GetMethodTableHandle(TargetPointer targetPointer); + + public virtual TargetPointer GetModule(MethodTableHandle methodTable); + // A canonical method table is either the MethodTable itself, or in the case of a generic instantiation, it is the + // MethodTable of the prototypical instance. + public virtual TargetPointer GetCanonicalMethodTable(MethodTableHandle methodTable); + public virtual TargetPointer GetParentMethodTable(MethodTableHandle methodTable); + + public virtual uint GetBaseSize(MethodTableHandle methodTable); + // The component size is only available for strings and arrays. It is the size of the element type of the array, or the size of an ECMA 335 character (2 bytes) + public virtual uint GetComponentSize(MethodTableHandle methodTable); + + // True if the MethodTable is the sentinel value associated with unallocated space in the managed heap + public virtual bool IsFreeObjectMethodTable(MethodTableHandle methodTable); + public virtual bool IsString(MethodTableHandle methodTable); + // True if the MethodTable represents a type that contains managed references + public virtual bool ContainsGCPointers(MethodTableHandle methodTable); + public virtual bool IsDynamicStatics(MethodTableHandle methodTable); + public virtual ushort GetNumMethods(MethodTableHandle methodTable); + public virtual ushort GetNumInterfaces(MethodTableHandle methodTable); + + // Returns an ECMA-335 TypeDef table token for this type, or for its generic type definition if it is a generic instantiation + public virtual uint GetTypeDefToken(MethodTableHandle methodTable); + // Returns the ECMA 335 TypeDef table Flags value (a bitmask of TypeAttributes) for this type, + // or for its generic type definition if it is a generic instantiation + public virtual uint GetTypeDefTypeAttributes(MethodTableHandle methodTable); + #endregion MethodTable inspection APIs +``` + +## Version 1 + +The `MethodTable` inspection APIs are implemented in terms of the following flags on the runtime `MethodTable` structure: + +``` csharp +internal partial struct RuntimeTypeSystem_1 +{ + // The lower 16-bits of the MTFlags field are used for these flags, + // if WFLAGS_HIGH.HasComponentSize is unset + [Flags] + internal enum WFLAGS_LOW : uint + { + GenericsMask = 0x00000030, + GenericsMask_NonGeneric = 0x00000000, // no instantiation + + StringArrayValues = GenericsMask_NonGeneric, + } + + // Upper bits of MTFlags + [Flags] + internal enum WFLAGS_HIGH : uint + { + Category_Mask = 0x000F0000, + Category_Array = 0x00080000, + Category_Array_Mask = 0x000C0000, + Category_Interface = 0x000C0000, + ContainsGCPointers = 0x01000000, + HasComponentSize = 0x80000000, // This is set if lower 16 bits is used for the component size, + // otherwise the lower bits are used for WFLAGS_LOW + } + + [Flags] + internal enum WFLAGS2_ENUM : uint + { + DynamicStatics = 0x0002, + } + + // Encapsulates the MethodTable flags v1 uses + internal struct MethodTableFlags + { + public uint MTFlags { get; } + public uint MTFlags2 { get; } + public uint BaseSize { get; } + + public WFLAGS_LOW GetFlag(WFLAGS_LOW mask) { ... /* mask & lower 16 bits of MTFlags */ } + public WFLAGS_HIGH GetFlag(WFLAGS_HIGH mask) { ... /* mask & upper 16 bits of MTFlags */ } + + public WFLAGS2_ENUM GetFlag(WFLAGS2_ENUM mask) { ... /* mask & MTFlags2*/ } + + private bool TestFlagWithMask(WFLAGS_LOW mask, WFLAGS_LOW flag) + { + if (IsStringOrArray) + { + return (WFLAGS_LOW.StringArrayValues & mask) == flag; + } + else + { + return (FlagsLow & mask) == flag; + } + } + + public ushort ComponentSizeBits => (ushort)(MTFlags & 0x0000ffff); // only meaningful if HasComponentSize is set + + public bool HasComponentSize => GetFlag(WFLAGS_HIGH.HasComponentSize) != 0; + public bool IsInterface => GetFlag(WFLAGS_HIGH.Category_Mask) == WFLAGS_HIGH.Category_Interface; + public bool IsString => HasComponentSize && !IsArray && ComponentSizeBits == 2; + public bool IsArray => GetFlag(WFLAGS_HIGH.Category_Array_Mask) == WFLAGS_HIGH.Category_Array; + public bool IsStringOrArray => HasComponentSize; + public ushort ComponentSize => HasComponentSize ? ComponentSizeBits : (ushort)0; + public bool HasInstantiation => !TestFlagWithMask(WFLAGS_LOW.GenericsMask, WFLAGS_LOW.GenericsMask_NonGeneric); + public bool ContainsGCPointers => GetFlag(WFLAGS_HIGH.ContainsGCPointers) != 0; + public bool IsDynamicStatics => GetFlag(WFLAGS2_ENUM.DynamicStatics) != 0; + } + + [Flags] + internal enum EEClassOrCanonMTBits + { + EEClass = 0, + CanonMT = 1, + Mask = 1, + } +} +``` + +Internally the contract has a `MethodTable_1` struct that depends on the `MethodTable` data descriptor + +```csharp +internal struct MethodTable_1 +{ + internal RuntimeTypeSystem_1.MethodTableFlags Flags { get; } + internal ushort NumInterfaces { get; } + internal ushort NumVirtuals { get; } + internal TargetPointer ParentMethodTable { get; } + internal TargetPointer Module { get; } + internal TargetPointer EEClassOrCanonMT { get; } + internal MethodTable_1(Data.MethodTable data) + { + Flags = new RuntimeTypeSystem_1.MethodTableFlags + { + MTFlags = data.MTFlags, + MTFlags2 = data.MTFlags2, + BaseSize = data.BaseSize, + }; + NumInterfaces = data.NumInterfaces; + NumVirtuals = data.NumVirtuals; + EEClassOrCanonMT = data.EEClassOrCanonMT; + Module = data.Module; + ParentMethodTable = data.ParentMethodTable; + } +} +``` + +The contract depends on the global pointer value `FreeObjectMethodTablePointer`. +The contract additionally depends on the `EEClass` data descriptor. + +```csharp + private readonly Dictionary _methodTables; + + internal TargetPointer FreeObjectMethodTablePointer {get; } + + public MethodTableHandle GetMethodTableHandle(TargetPointer methodTablePointer) + { + ... // validate that methodTablePointer points to something that looks like a MethodTable. + ... // read Data.MethodTable from methodTablePointer. + ... // create a MethodTable_1 and add it to _methodTables. + return MethodTableHandle { Address = methodTablePointer } + } + + internal static EEClassOrCanonMTBits GetEEClassOrCanonMTBits(TargetPointer eeClassOrCanonMTPtr) + { + return (EEClassOrCanonMTBits)(eeClassOrCanonMTPtr & (ulong)EEClassOrCanonMTBits.Mask); + } + + public uint GetBaseSize(MethodTableHandle methodTableHandle) => _methodTables[methodTableHandle.Address].Flags.BaseSize; + + public uint GetComponentSize(MethodTableHandle methodTableHandle) => GetComponentSize(_methodTables[methodTableHandle.Address]); + + private TargetPointer GetClassPointer(MethodTableHandle methodTableHandle) + { + ... // if the MethodTable stores a pointer to the EEClass, return it + // otherwise the MethodTable stores a pointer to the canonical MethodTable + // in that case, return the canonical MethodTable's EEClass. + // Canonical MethodTables always store an EEClass pointer. + } + + private Data.EEClass GetClassData(MethodTableHandle methodTableHandle) + { + TargetPointer eeClassPtr = GetClassPointer(methodTableHandle); + ... // read Data.EEClass data from eeClassPtr + } + + + public TargetPointer GetCanonicalMethodTable(MethodTableHandle methodTableHandle) => GetClassData(methodTableHandle).MethodTable; + + public TargetPointer GetModule(MethodTableHandle methodTableHandle) => _methodTables[methodTableHandle.Address].Module; + public TargetPointer GetParentMethodTable(MethodTableHandle methodTableHandle) => _methodTables[methodTableHandle.Address].ParentMethodTable; + + public bool IsFreeObjectMethodTable(MethodTableHandle methodTableHandle) => FreeObjectMethodTablePointer == methodTableHandle.Address; + + public bool IsString(MethodTableHandle methodTableHandle) => _methodTables[methodTableHandle.Address].Flags.IsString; + public bool ContainsGCPointers(MethodTableHandle methodTableHandle) => _methodTables[methodTableHandle.Address].Flags.ContainsGCPointers; + + public uint GetTypeDefToken(MethodTableHandle methodTableHandle) + { + MethodTable_1 methodTable = _methodTables[methodTableHandle.Address]; + return (uint)(methodTable.Flags.GetTypeDefRid() | ((int)TableIndex.TypeDef << 24)); + } + + public ushort GetNumMethods(MethodTableHandle methodTableHandle) => GetClassData(methodTableHandle).NumMethods; + + public ushort GetNumInterfaces(MethodTableHandle methodTableHandle) => _methodTables[methodTableHandle.Address].NumInterfaces; + + public uint GetTypeDefTypeAttributes(MethodTableHandle methodTableHandle) => GetClassData(methodTableHandle).CorTypeAttr; + + public bool IsDynamicStatics(MethodTableHandle methodTableHandle) => _methodTables[methodTableHandle.Address].Flags.IsDynamicStatics; +``` diff --git a/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.CoreCLR.cs index 052f6970a298e..488602f1e35bc 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.CoreCLR.cs @@ -654,7 +654,7 @@ internal unsafe struct MethodTable private const uint enum_flag_IsByRefLike = 0x00001000; // WFLAGS_HIGH_ENUM - private const uint enum_flag_ContainsPointers = 0x01000000; + private const uint enum_flag_ContainsGCPointers = 0x01000000; private const uint enum_flag_ContainsGenericVariables = 0x20000000; private const uint enum_flag_HasComponentSize = 0x80000000; private const uint enum_flag_HasTypeEquivalence = 0x02000000; @@ -707,7 +707,7 @@ internal unsafe struct MethodTable public bool HasComponentSize => (Flags & enum_flag_HasComponentSize) != 0; - public bool ContainsGCPointers => (Flags & enum_flag_ContainsPointers) != 0; + public bool ContainsGCPointers => (Flags & enum_flag_ContainsGCPointers) != 0; public bool NonTrivialInterfaceCast => (Flags & enum_flag_NonTrivialInterfaceCast) != 0; diff --git a/src/coreclr/debug/daccess/dacimpl.h b/src/coreclr/debug/daccess/dacimpl.h index f480eea6baaa5..57be1538e2bb7 100644 --- a/src/coreclr/debug/daccess/dacimpl.h +++ b/src/coreclr/debug/daccess/dacimpl.h @@ -1232,6 +1232,8 @@ class ClrDataAccess HRESULT GetThreadDataImpl(CLRDATA_ADDRESS threadAddr, struct DacpThreadData *threadData); HRESULT GetThreadStoreDataImpl(struct DacpThreadStoreData *data); HRESULT GetNestedExceptionDataImpl(CLRDATA_ADDRESS exception, CLRDATA_ADDRESS *exceptionObject, CLRDATA_ADDRESS *nextNestedException); + HRESULT GetMethodTableDataImpl(CLRDATA_ADDRESS mt, struct DacpMethodTableData *data); + HRESULT GetMethodTableForEEClassImpl (CLRDATA_ADDRESS eeClassReallyMT, CLRDATA_ADDRESS *value); BOOL IsExceptionFromManagedCode(EXCEPTION_RECORD * pExceptionRecord); #ifndef TARGET_UNIX diff --git a/src/coreclr/debug/daccess/request.cpp b/src/coreclr/debug/daccess/request.cpp index 445acc64b1019..1021fc3db128f 100644 --- a/src/coreclr/debug/daccess/request.cpp +++ b/src/coreclr/debug/daccess/request.cpp @@ -161,11 +161,6 @@ BOOL DacValidateMethodTable(PTR_MethodTable pMT, BOOL &bIsFree) // In rare cases, we've seen the standard check above pass when it shouldn't. // Insert additional/ad-hoc tests below. - // Metadata token should look valid for a class - mdTypeDef td = pMT->GetCl(); - if (td != mdTokenNil && TypeFromToken(td) != mdtTypeDef) - goto BadMethodTable; - // BaseSize should always be greater than 0 for valid objects (unless it's an interface) // For strings, baseSize is not ptr-aligned if (!pMT->IsInterface() && !pMT->IsString()) @@ -1785,42 +1780,87 @@ ClrDataAccess::GetMethodTableData(CLRDATA_ADDRESS mt, struct DacpMethodTableData return E_INVALIDARG; SOSDacEnter(); - - PTR_MethodTable pMT = PTR_MethodTable(TO_TADDR(mt)); - BOOL bIsFree = FALSE; - if (!DacValidateMethodTable(pMT, bIsFree)) + if (m_cdacSos != NULL) { - hr = E_INVALIDARG; + // Try the cDAC first - it will return E_NOTIMPL if it doesn't support this method yet. Fall back to the DAC. + hr = m_cdacSos->GetMethodTableData(mt, MTData); + if (FAILED(hr)) + { + hr = GetMethodTableDataImpl(mt, MTData); + } +#ifdef _DEBUG + else + { + // Assert that the data is the same as what we get from the DAC. + DacpMethodTableData mtDataLocal; + HRESULT hrLocal = GetMethodTableDataImpl(mt, &mtDataLocal); + _ASSERTE(hr == hrLocal); + _ASSERTE(MTData->BaseSize == mtDataLocal.BaseSize); + _ASSERTE(MTData->ComponentSize == mtDataLocal.ComponentSize); + _ASSERTE(MTData->bIsFree == mtDataLocal.bIsFree); + _ASSERTE(MTData->Module == mtDataLocal.Module); + _ASSERTE(MTData->Class == mtDataLocal.Class); + _ASSERTE(MTData->ParentMethodTable == mtDataLocal.ParentMethodTable); + _ASSERTE(MTData->wNumInterfaces == mtDataLocal.wNumInterfaces); + _ASSERTE(MTData->wNumMethods == mtDataLocal.wNumMethods); + _ASSERTE(MTData->wNumVtableSlots == mtDataLocal.wNumVtableSlots); + _ASSERTE(MTData->wNumVirtuals == mtDataLocal.wNumVirtuals); + _ASSERTE(MTData->cl == mtDataLocal.cl); + _ASSERTE(MTData->dwAttrClass = mtDataLocal.dwAttrClass); + _ASSERTE(MTData->bContainsPointers == mtDataLocal.bContainsPointers); + _ASSERTE(MTData->bIsShared == mtDataLocal.bIsShared); + _ASSERTE(MTData->bIsDynamic == mtDataLocal.bIsDynamic); + } +#endif } else { - ZeroMemory(MTData,sizeof(DacpMethodTableData)); - MTData->BaseSize = pMT->GetBaseSize(); - if(pMT->IsString()) - MTData->BaseSize -= sizeof(WCHAR); - MTData->ComponentSize = (DWORD)pMT->GetComponentSize(); - MTData->bIsFree = bIsFree; - if(!bIsFree) - { - MTData->Module = HOST_CDADDR(pMT->GetModule()); - MTData->Class = HOST_CDADDR(pMT->GetClass()); - MTData->ParentMethodTable = HOST_CDADDR(pMT->GetParentMethodTable());; - MTData->wNumInterfaces = (WORD)pMT->GetNumInterfaces(); - MTData->wNumMethods = pMT->GetNumMethods(); - MTData->wNumVtableSlots = pMT->GetNumVtableSlots(); - MTData->wNumVirtuals = pMT->GetNumVirtuals(); - MTData->cl = pMT->GetCl(); - MTData->dwAttrClass = pMT->GetAttrClass(); - MTData->bContainsPointers = pMT->ContainsPointers(); - MTData->bIsShared = FALSE; - MTData->bIsDynamic = pMT->IsDynamicStatics(); - } + hr = GetMethodTableDataImpl (mt, MTData); } - SOSDacLeave(); return hr; } +HRESULT +ClrDataAccess::GetMethodTableDataImpl(CLRDATA_ADDRESS mt, struct DacpMethodTableData *MTData) +{ + PTR_MethodTable pMT = PTR_MethodTable(TO_TADDR(mt)); + BOOL bIsFree = FALSE; + if (!DacValidateMethodTable(pMT, bIsFree)) + { + return E_INVALIDARG; + } + + ZeroMemory(MTData,sizeof(DacpMethodTableData)); + MTData->BaseSize = pMT->GetBaseSize(); + // [compat] SOS DAC APIs added this base size adjustment for strings + // due to: "2008/09/25 Title: New implementation of StringBuilder and improvements in String class" + // which changed StringBuilder not to use a String as an internal buffer and in the process + // changed the String internals so that StringObject::GetBaseSize() now includes the nul terminator character, + // which is apparently not expected by SOS. + if(pMT->IsString()) + MTData->BaseSize -= sizeof(WCHAR); + MTData->ComponentSize = (DWORD)pMT->GetComponentSize(); + MTData->bIsFree = bIsFree; + if(!bIsFree) + { + MTData->Module = HOST_CDADDR(pMT->GetModule()); + // Note: DacpMethodTableData::Class is really a pointer to the canonical method table + MTData->Class = HOST_CDADDR(pMT->GetClass()->GetMethodTable()); + MTData->ParentMethodTable = HOST_CDADDR(pMT->GetParentMethodTable());; + MTData->wNumInterfaces = (WORD)pMT->GetNumInterfaces(); + MTData->wNumMethods = pMT->GetNumMethods(); // printed as "number of vtable slots" and used to iterate over method slots + MTData->wNumVtableSlots = 0; // always return 0 since .NET 9 + MTData->wNumVirtuals = 0; // always return 0 since .NET 9 + MTData->cl = pMT->GetCl(); + MTData->dwAttrClass = pMT->GetAttrClass(); + MTData->bContainsPointers = pMT->ContainsGCPointers(); + MTData->bIsShared = FALSE; + MTData->bIsDynamic = pMT->IsDynamicStatics(); + } + return S_OK; +} + HRESULT ClrDataAccess::GetMethodTableName(CLRDATA_ADDRESS mt, unsigned int count, _Inout_updates_z_(count) WCHAR *mtName, unsigned int *pNeeded) { @@ -2063,27 +2103,53 @@ ClrDataAccess::GetMethodTableTransparencyData(CLRDATA_ADDRESS mt, struct DacpMet } HRESULT -ClrDataAccess::GetMethodTableForEEClass(CLRDATA_ADDRESS eeClass, CLRDATA_ADDRESS *value) +ClrDataAccess::GetMethodTableForEEClass(CLRDATA_ADDRESS eeClassReallyCanonMT, CLRDATA_ADDRESS *value) { - if (eeClass == 0 || value == NULL) + if (eeClassReallyCanonMT == 0 || value == NULL) return E_INVALIDARG; SOSDacEnter(); - - PTR_EEClass pClass = PTR_EEClass(TO_TADDR(eeClass)); - if (!DacValidateEEClass(pClass)) + if (m_cdacSos != NULL) { - hr = E_INVALIDARG; + // Try the cDAC first - it will return E_NOTIMPL if it doesn't support this method yet. Fall back to the DAC. + hr = m_cdacSos->GetMethodTableForEEClass(eeClassReallyCanonMT, value); + if (FAILED(hr)) + { + hr = GetMethodTableForEEClassImpl(eeClassReallyCanonMT, value); + } +#ifdef _DEBUG + else + { + // Assert that the data is the same as what we get from the DAC. + CLRDATA_ADDRESS valueLocal; + HRESULT hrLocal = GetMethodTableForEEClassImpl(eeClassReallyCanonMT, &valueLocal); + _ASSERTE(hr == hrLocal); + _ASSERTE(*value == valueLocal); + } +#endif } else { - *value = HOST_CDADDR(pClass->GetMethodTable()); + hr = GetMethodTableForEEClassImpl (eeClassReallyCanonMT, value); } - SOSDacLeave(); return hr; } +HRESULT +ClrDataAccess::GetMethodTableForEEClassImpl(CLRDATA_ADDRESS eeClassReallyCanonMT, CLRDATA_ADDRESS *value) +{ + PTR_MethodTable pCanonMT = PTR_MethodTable(TO_TADDR(eeClassReallyCanonMT)); + BOOL bIsFree; + if (!DacValidateMethodTable(pCanonMT, bIsFree)) + { + return E_INVALIDARG; + } + + *value = HOST_CDADDR(pCanonMT); + return S_OK; +} + HRESULT ClrDataAccess::GetFrameName(CLRDATA_ADDRESS vtable, unsigned int count, _Inout_updates_z_(count) WCHAR *frameName, unsigned int *pNeeded) { diff --git a/src/coreclr/debug/runtimeinfo/contractpointerdata.cpp b/src/coreclr/debug/runtimeinfo/contractpointerdata.cpp index ae1440af4219a..cf7d914e728d7 100644 --- a/src/coreclr/debug/runtimeinfo/contractpointerdata.cpp +++ b/src/coreclr/debug/runtimeinfo/contractpointerdata.cpp @@ -7,10 +7,10 @@ #include #include "threads.h" +#include "vars.hpp" extern "C" { - // without an extern declaration, clang does not emit this global into the object file extern const uintptr_t contractDescriptorPointerData[]; diff --git a/src/coreclr/debug/runtimeinfo/contracts.jsonc b/src/coreclr/debug/runtimeinfo/contracts.jsonc index 0c94cf58c2322..44e7f914f9b07 100644 --- a/src/coreclr/debug/runtimeinfo/contracts.jsonc +++ b/src/coreclr/debug/runtimeinfo/contracts.jsonc @@ -10,6 +10,6 @@ // so to conditionally include contracts, put additional contracts in a separate file { "Exception": 1, - "Thread": 1, - "SOSBreakingChangeVersion": 1 // example contract: "runtime exports an SOS breaking change version global" + "RuntimeTypeSystem": 1, + "Thread": 1 } diff --git a/src/coreclr/debug/runtimeinfo/datadescriptor.cpp b/src/coreclr/debug/runtimeinfo/datadescriptor.cpp index 99fe1cca7eeca..bea29213783eb 100644 --- a/src/coreclr/debug/runtimeinfo/datadescriptor.cpp +++ b/src/coreclr/debug/runtimeinfo/datadescriptor.cpp @@ -9,6 +9,7 @@ #include "static_assert.h" #include +#include "methodtable.h" #include "threads.h" // begin blob definition diff --git a/src/coreclr/debug/runtimeinfo/datadescriptor.h b/src/coreclr/debug/runtimeinfo/datadescriptor.h index 0c7e3f70b4d33..0e0aa0c8a819d 100644 --- a/src/coreclr/debug/runtimeinfo/datadescriptor.h +++ b/src/coreclr/debug/runtimeinfo/datadescriptor.h @@ -156,6 +156,27 @@ CDAC_TYPE_BEGIN(GCHandle) CDAC_TYPE_SIZE(sizeof(OBJECTHANDLE)) CDAC_TYPE_END(GCHandle) +// Metadata + +CDAC_TYPE_BEGIN(MethodTable) +CDAC_TYPE_INDETERMINATE(MethodTable) +CDAC_TYPE_FIELD(MethodTable, /*uint32*/, MTFlags, cdac_offsets::MTFlags) +CDAC_TYPE_FIELD(MethodTable, /*uint32*/, BaseSize, cdac_offsets::BaseSize) +CDAC_TYPE_FIELD(MethodTable, /*uint32*/, MTFlags2, cdac_offsets::MTFlags2) +CDAC_TYPE_FIELD(MethodTable, /*nuint*/, EEClassOrCanonMT, cdac_offsets::EEClassOrCanonMT) +CDAC_TYPE_FIELD(MethodTable, /*pointer*/, Module, cdac_offsets::Module) +CDAC_TYPE_FIELD(MethodTable, /*pointer*/, ParentMethodTable, cdac_offsets::ParentMethodTable) +CDAC_TYPE_FIELD(MethodTable, /*uint16*/, NumInterfaces, cdac_offsets::NumInterfaces) +CDAC_TYPE_FIELD(MethodTable, /*uint16*/, NumVirtuals, cdac_offsets::NumVirtuals) +CDAC_TYPE_END(MethodTable) + +CDAC_TYPE_BEGIN(EEClass) +CDAC_TYPE_INDETERMINATE(EEClass) +CDAC_TYPE_FIELD(EEClass, /*pointer*/, MethodTable, cdac_offsets::MethodTable) +CDAC_TYPE_FIELD(EEClass, /*uint16*/, NumMethods, cdac_offsets::NumMethods) +CDAC_TYPE_FIELD(EEClass, /*uint32*/, CorTypeAttr, cdac_offsets::CorTypeAttr) +CDAC_TYPE_END(EEClass) + CDAC_TYPES_END() CDAC_GLOBALS_BEGIN() @@ -169,6 +190,7 @@ CDAC_GLOBAL(FeatureEHFunclets, uint8, 1) CDAC_GLOBAL(FeatureEHFunclets, uint8, 0) #endif CDAC_GLOBAL(SOSBreakingChangeVersion, uint8, SOS_BREAKING_CHANGE_VERSION) +CDAC_GLOBAL_POINTER(FreeObjectMethodTable, &::g_pFreeObjectMethodTable) CDAC_GLOBALS_END() #undef CDAC_BASELINE diff --git a/src/coreclr/gc/env/gcenv.object.h b/src/coreclr/gc/env/gcenv.object.h index ff0dbb343ed1d..f515536f6a66f 100644 --- a/src/coreclr/gc/env/gcenv.object.h +++ b/src/coreclr/gc/env/gcenv.object.h @@ -49,7 +49,7 @@ static_assert(sizeof(ObjHeader) == sizeof(uintptr_t), "this assumption is made b #define MTFlag_RequiresAlign8 0x00001000 // enum_flag_RequiresAlign8 #define MTFlag_Category_ValueType 0x00040000 // enum_flag_Category_ValueType #define MTFlag_Category_ValueType_Mask 0x000C0000 // enum_flag_Category_ValueType_Mask -#define MTFlag_ContainsPointers 0x01000000 // enum_flag_ContainsPointers +#define MTFlag_ContainsGCPointers 0x01000000 // enum_flag_ContainsGCPointers #define MTFlag_HasCriticalFinalizer 0x00000002 // enum_flag_HasCriticalFinalizer #define MTFlag_HasFinalizer 0x00100000 // enum_flag_HasFinalizer #define MTFlag_IsArray 0x00080000 // enum_flag_Category_Array @@ -100,14 +100,14 @@ class MethodTable return (m_flags & MTFlag_Collectible) != 0; } - bool ContainsPointers() + bool ContainsGCPointers() { - return (m_flags & MTFlag_ContainsPointers) != 0; + return (m_flags & MTFlag_ContainsGCPointers) != 0; } - bool ContainsPointersOrCollectible() + bool ContainsGCPointersOrCollectible() { - return ContainsPointers() || Collectible(); + return ContainsGCPointers() || Collectible(); } bool RequiresAlign8() diff --git a/src/coreclr/gc/gc.cpp b/src/coreclr/gc/gc.cpp index a9242a804555e..fe4bfeb522c2d 100644 --- a/src/coreclr/gc/gc.cpp +++ b/src/coreclr/gc/gc.cpp @@ -4829,7 +4829,7 @@ class CObjectHeader : public Object CGCDesc *GetSlotMap () { - assert (GetMethodTable()->ContainsPointers()); + assert (GetMethodTable()->ContainsGCPointers()); return CGCDesc::GetCGCDescFromMT(GetMethodTable()); } @@ -4893,9 +4893,9 @@ class CObjectHeader : public Object } #endif // FEATURE_STRUCTALIGN - BOOL ContainsPointers() const + BOOL ContainsGCPointers() const { - return GetMethodTable()->ContainsPointers(); + return GetMethodTable()->ContainsGCPointers(); } #ifdef COLLECTIBLE_CLASS @@ -4904,10 +4904,10 @@ class CObjectHeader : public Object return GetMethodTable()->Collectible(); } - FORCEINLINE BOOL ContainsPointersOrCollectible() const + FORCEINLINE BOOL ContainsGCPointersOrCollectible() const { MethodTable *pMethodTable = GetMethodTable(); - return (pMethodTable->ContainsPointers() || pMethodTable->Collectible()); + return (pMethodTable->ContainsGCPointers() || pMethodTable->Collectible()); } #endif //COLLECTIBLE_CLASS @@ -6066,7 +6066,7 @@ void gc_heap::release_segment (heap_segment* sg) FIRE_EVENT(GCFreeSegment_V1, heap_segment_mem(sg)); size_t reserved_size = (uint8_t*)heap_segment_reserved (sg) - (uint8_t*)sg; reduce_committed_bytes ( - sg, + sg, ((uint8_t*)heap_segment_committed (sg) - (uint8_t*)sg), (int) heap_segment_oh (sg) #ifdef MULTIPLE_HEAPS @@ -9083,7 +9083,7 @@ void destroy_card_table (uint32_t* c_table) void gc_heap::destroy_card_table_helper (uint32_t* c_table) { uint8_t* lowest = card_table_lowest_address (c_table); - uint8_t* highest = card_table_highest_address (c_table); + uint8_t* highest = card_table_highest_address (c_table); get_card_table_element_layout(lowest, highest, card_table_element_layout); size_t result = card_table_element_layout[seg_mapping_table_element + 1]; gc_heap::reduce_committed_bytes (&card_table_refcount(c_table), result, recorded_committed_bookkeeping_bucket, -1, true); @@ -11555,14 +11555,14 @@ inline size_t my_get_size (Object* ob) #define size(i) my_get_size (header(i)) -#define contain_pointers(i) header(i)->ContainsPointers() +#define contain_pointers(i) header(i)->ContainsGCPointers() #ifdef COLLECTIBLE_CLASS -#define contain_pointers_or_collectible(i) header(i)->ContainsPointersOrCollectible() +#define contain_pointers_or_collectible(i) header(i)->ContainsGCPointersOrCollectible() #define get_class_object(i) GCToEEInterface::GetLoaderAllocatorObjectForGC((Object *)i) #define is_collectible(i) method_table(i)->Collectible() #else //COLLECTIBLE_CLASS -#define contain_pointers_or_collectible(i) header(i)->ContainsPointers() +#define contain_pointers_or_collectible(i) header(i)->ContainsGCPointers() #endif //COLLECTIBLE_CLASS #ifdef BACKGROUND_GC @@ -26683,7 +26683,7 @@ BOOL gc_heap::background_mark (uint8_t* o, uint8_t* low, uint8_t* high) #ifndef COLLECTIBLE_CLASS #define go_through_object_cl(mt,o,size,parm,exp) \ { \ - if (header(o)->ContainsPointers()) \ + if (header(o)->ContainsGCPointers()) \ { \ go_through_object_nostart(mt,o,size,parm,exp); \ } \ @@ -26697,7 +26697,7 @@ BOOL gc_heap::background_mark (uint8_t* o, uint8_t* low, uint8_t* high) uint8_t** parm = &class_obj; \ do {exp} while (false); \ } \ - if (header(o)->ContainsPointers()) \ + if (header(o)->ContainsGCPointers()) \ { \ go_through_object_nostart(mt,o,size,parm,exp); \ } \ diff --git a/src/coreclr/gc/gcdesc.h b/src/coreclr/gc/gcdesc.h index 54a13dfdb8cdf..8d91e776ac428 100644 --- a/src/coreclr/gc/gcdesc.h +++ b/src/coreclr/gc/gcdesc.h @@ -161,7 +161,7 @@ class CGCDesc // If it doesn't contain pointers, there isn't a GCDesc PTR_MethodTable mt(pMT); - _ASSERTE(mt->ContainsPointers()); + _ASSERTE(mt->ContainsGCPointers()); return PTR_CGCDesc(mt); } @@ -195,7 +195,7 @@ class CGCDesc { size_t NumOfPointers = 0; - if (pMT->ContainsPointers()) + if (pMT->ContainsGCPointers()) { CGCDesc* map = GetCGCDescFromMT(pMT); CGCDescSeries* cur = map->GetHighestSeries(); diff --git a/src/coreclr/gc/sample/GCSample.cpp b/src/coreclr/gc/sample/GCSample.cpp index 0f2afc7c20a71..3b9bf63103dc4 100644 --- a/src/coreclr/gc/sample/GCSample.cpp +++ b/src/coreclr/gc/sample/GCSample.cpp @@ -179,7 +179,7 @@ int __cdecl main(int argc, char* argv[]) My_MethodTable.m_MT.m_baseSize = max(baseSize, (uint32_t)MIN_OBJECT_SIZE); My_MethodTable.m_MT.m_componentSize = 0; // Array component size - My_MethodTable.m_MT.m_flags = MTFlag_ContainsPointers; + My_MethodTable.m_MT.m_flags = MTFlag_ContainsGCPointers; My_MethodTable.m_numSeries = 2; diff --git a/src/coreclr/inc/dacprivate.h b/src/coreclr/inc/dacprivate.h index 3e96334430da5..305029634406c 100644 --- a/src/coreclr/inc/dacprivate.h +++ b/src/coreclr/inc/dacprivate.h @@ -274,11 +274,14 @@ struct MSLAYOUT DacpMethodTableData { BOOL bIsFree = FALSE; // everything else is NULL if this is true. CLRDATA_ADDRESS Module = 0; + // Note: DacpMethodTableData::Class is really a pointer to the canonical method table CLRDATA_ADDRESS Class = 0; CLRDATA_ADDRESS ParentMethodTable = 0; WORD wNumInterfaces = 0; WORD wNumMethods = 0; + // Note: Always 0, since .NET 9 WORD wNumVtableSlots = 0; + // Note: Always 0, since .NET 9 WORD wNumVirtuals = 0; DWORD BaseSize = 0; DWORD ComponentSize = 0; diff --git a/src/coreclr/nativeaot/Runtime/GCHelpers.cpp b/src/coreclr/nativeaot/Runtime/GCHelpers.cpp index 6fecd5ac04768..b038d9d33541b 100644 --- a/src/coreclr/nativeaot/Runtime/GCHelpers.cpp +++ b/src/coreclr/nativeaot/Runtime/GCHelpers.cpp @@ -476,7 +476,7 @@ static Object* GcAllocInternal(MethodTable* pEEType, uint32_t uFlags, uintptr_t ASSERT(!pThread->IsDoNotTriggerGcSet()); ASSERT(pThread->IsCurrentThreadInCooperativeMode()); - if (pEEType->ContainsPointers()) + if (pEEType->ContainsGCPointers()) { uFlags |= GC_ALLOC_CONTAINS_REF; uFlags &= ~GC_ALLOC_ZEROING_OPTIONAL; @@ -693,7 +693,7 @@ EXTERN_C void QCALLTYPE RhUnregisterFrozenSegment(void* pSegmentHandle) FCIMPL1(uint32_t, RhGetGCDescSize, MethodTable* pMT) { - if (!pMT->ContainsPointersOrCollectible()) + if (!pMT->ContainsGCPointersOrCollectible()) return 0; return (uint32_t)CGCDesc::GetCGCDescFromMT(pMT)->GetSize(); diff --git a/src/coreclr/nativeaot/Runtime/inc/MethodTable.h b/src/coreclr/nativeaot/Runtime/inc/MethodTable.h index b5c41b14d92bf..f33a5d066dc3e 100644 --- a/src/coreclr/nativeaot/Runtime/inc/MethodTable.h +++ b/src/coreclr/nativeaot/Runtime/inc/MethodTable.h @@ -289,8 +289,8 @@ class MethodTable public: // Methods expected by the GC - uint32_t ContainsPointers() { return HasReferenceFields(); } - uint32_t ContainsPointersOrCollectible() { return HasReferenceFields(); } + uint32_t ContainsGCPointers() { return HasReferenceFields(); } + uint32_t ContainsGCPointersOrCollectible() { return HasReferenceFields(); } UInt32_BOOL SanityCheck() { return Validate(); } }; diff --git a/src/coreclr/nativeaot/Runtime/profheapwalkhelper.cpp b/src/coreclr/nativeaot/Runtime/profheapwalkhelper.cpp index 3cecf152f3a85..6d9d7edc6fea4 100644 --- a/src/coreclr/nativeaot/Runtime/profheapwalkhelper.cpp +++ b/src/coreclr/nativeaot/Runtime/profheapwalkhelper.cpp @@ -138,7 +138,7 @@ bool HeapWalkHelper(Object * pBO, void * pvContext) ProfilerWalkHeapContext * pProfilerWalkHeapContext = (ProfilerWalkHeapContext *) pvContext; - //if (pMT->ContainsPointersOrCollectible()) + //if (pMT->ContainsGCPointersOrCollectible()) { // First round through calculates the number of object refs for this class GCHeapUtilities::GetGCHeap()->DiagWalkObject(pBO, &CountContainedObjectRef, (void *)&cNumRefs); diff --git a/src/coreclr/vm/amd64/JitHelpers_Slow.asm b/src/coreclr/vm/amd64/JitHelpers_Slow.asm index e2f58ac6618db..6d322248cdeee 100644 --- a/src/coreclr/vm/amd64/JitHelpers_Slow.asm +++ b/src/coreclr/vm/amd64/JitHelpers_Slow.asm @@ -224,7 +224,7 @@ NESTED_ENTRY JIT_BoxFastUP, _TEXT mov [g_global_alloc_lock], -1 ; Check whether the object contains pointers - test dword ptr [rcx + OFFSETOF__MethodTable__m_dwFlags], MethodTable__enum_flag_ContainsPointers + test dword ptr [rcx + OFFSETOF__MethodTable__m_dwFlags], MethodTable__enum_flag_ContainsGCPointers jnz ContainsPointers ; We have no pointers - emit a simple inline copy loop diff --git a/src/coreclr/vm/amd64/asmconstants.h b/src/coreclr/vm/amd64/asmconstants.h index c629192da5cb9..524e1fd40b7ae 100644 --- a/src/coreclr/vm/amd64/asmconstants.h +++ b/src/coreclr/vm/amd64/asmconstants.h @@ -176,9 +176,9 @@ ASMCONSTANTS_C_ASSERT(METHODTABLE_EQUIVALENCE_FLAGS #define METHODTABLE_EQUIVALENCE_FLAGS 0x0 #endif -#define MethodTable__enum_flag_ContainsPointers 0x01000000 -ASMCONSTANTS_C_ASSERT(MethodTable__enum_flag_ContainsPointers - == MethodTable::enum_flag_ContainsPointers); +#define MethodTable__enum_flag_ContainsGCPointers 0x01000000 +ASMCONSTANTS_C_ASSERT(MethodTable__enum_flag_ContainsGCPointers + == MethodTable::enum_flag_ContainsGCPointers); #define OFFSETOF__InterfaceInfo_t__m_pMethodTable 0 ASMCONSTANTS_C_ASSERT(OFFSETOF__InterfaceInfo_t__m_pMethodTable diff --git a/src/coreclr/vm/arm/asmconstants.h b/src/coreclr/vm/arm/asmconstants.h index 9995068d85279..1a65e1e45351d 100644 --- a/src/coreclr/vm/arm/asmconstants.h +++ b/src/coreclr/vm/arm/asmconstants.h @@ -76,8 +76,8 @@ ASMCONSTANTS_C_ASSERT(MethodTable__m_BaseSize == offsetof(MethodTable, m_BaseSiz #define MethodTable__m_dwFlags 0x0 ASMCONSTANTS_C_ASSERT(MethodTable__m_dwFlags == offsetof(MethodTable, m_dwFlags)); -#define MethodTable__enum_flag_ContainsPointers 0x01000000 -ASMCONSTANTS_C_ASSERT(MethodTable__enum_flag_ContainsPointers == MethodTable::enum_flag_ContainsPointers); +#define MethodTable__enum_flag_ContainsGCPointers 0x01000000 +ASMCONSTANTS_C_ASSERT(MethodTable__enum_flag_ContainsGCPointers == MethodTable::enum_flag_ContainsGCPointers); #define MethodTable__m_ElementType DBG_FRE(0x24, 0x20) ASMCONSTANTS_C_ASSERT(MethodTable__m_ElementType == offsetof(MethodTable, m_ElementTypeHnd)); diff --git a/src/coreclr/vm/array.cpp b/src/coreclr/vm/array.cpp index 3b2b778f4c50f..546b2292b3527 100644 --- a/src/coreclr/vm/array.cpp +++ b/src/coreclr/vm/array.cpp @@ -238,7 +238,7 @@ MethodTable* Module::CreateArrayMethodTable(TypeHandle elemTypeHnd, CorElementTy } BOOL containsPointers = CorTypeInfo::IsObjRef(elemType); - if (elemType == ELEMENT_TYPE_VALUETYPE && pElemMT->ContainsPointers()) + if (elemType == ELEMENT_TYPE_VALUETYPE && pElemMT->ContainsGCPointers()) containsPointers = TRUE; // this is the base for every array type @@ -520,7 +520,7 @@ MethodTable* Module::CreateArrayMethodTable(TypeHandle elemTypeHnd, CorElementTy if (CorTypeInfo::IsObjRef(elemType) || ((elemType == ELEMENT_TYPE_VALUETYPE) && pElemMT->IsAllGCPointers())) { - pMT->SetContainsPointers(); + pMT->SetContainsGCPointers(); // This array is all GC Pointers CGCDesc::GetCGCDescFromMT(pMT)->Init( pMT, 1 ); @@ -536,9 +536,9 @@ MethodTable* Module::CreateArrayMethodTable(TypeHandle elemTypeHnd, CorElementTy else if (elemType == ELEMENT_TYPE_VALUETYPE) { // If it's an array of value classes, there is a different format for the GCDesc if it contains pointers - if (pElemMT->ContainsPointers()) + if (pElemMT->ContainsGCPointers()) { - pMT->SetContainsPointers(); + pMT->SetContainsGCPointers(); CGCDescSeries* pElemSeries = CGCDesc::GetCGCDescFromMT(pElemMT)->GetHighestSeries(); diff --git a/src/coreclr/vm/cdacoffsets.h b/src/coreclr/vm/cdacoffsets.h index 317ef41f73603..38c5b316c4244 100644 --- a/src/coreclr/vm/cdacoffsets.h +++ b/src/coreclr/vm/cdacoffsets.h @@ -8,8 +8,8 @@ // // If the offset of some field F in class C must be provided to cDAC, but the field is private, the // class C should declare cdac_offsets as a friend: -// -// friend template struct cdac_offsets; +// +// template friend struct ::cdac_offsets; // // and provide a specialization cdac_offsets with a constexpr size_t member providing the offset: // diff --git a/src/coreclr/vm/class.cpp b/src/coreclr/vm/class.cpp index 6c0052636f6ae..9d85bc141e115 100644 --- a/src/coreclr/vm/class.cpp +++ b/src/coreclr/vm/class.cpp @@ -2690,7 +2690,7 @@ MethodTable::DebugDumpGCDesc( LOG((LF_ALWAYS, LL_ALWAYS, "GC description for '%s':\n\n", pszClassName)); } - if (ContainsPointers()) + if (ContainsGCPointers()) { CGCDescSeries *pSeries; CGCDescSeries *pHighest; diff --git a/src/coreclr/vm/class.h b/src/coreclr/vm/class.h index e20b29b9e5f12..74c66714555f3 100644 --- a/src/coreclr/vm/class.h +++ b/src/coreclr/vm/class.h @@ -1798,6 +1798,14 @@ class EEClass // DO NOT CREATE A NEW EEClass USING NEW! } #endif // !DACCESS_COMPILE + template friend struct ::cdac_offsets; +}; + +template<> struct cdac_offsets +{ + static constexpr size_t MethodTable = offsetof(EEClass, m_pMethodTable); + static constexpr size_t NumMethods = offsetof(EEClass, m_NumMethods); + static constexpr size_t CorTypeAttr = offsetof(EEClass, m_dwAttrClass); }; // -------------------------------------------------------------------------------------------- diff --git a/src/coreclr/vm/classlayoutinfo.cpp b/src/coreclr/vm/classlayoutinfo.cpp index 8336f89066032..b7290c5a5c3b5 100644 --- a/src/coreclr/vm/classlayoutinfo.cpp +++ b/src/coreclr/vm/classlayoutinfo.cpp @@ -285,7 +285,7 @@ namespace } else #endif // FEATURE_64BIT_ALIGNMENT - if (pNestedType.GetMethodTable()->ContainsPointers()) + if (pNestedType.GetMethodTable()->ContainsGCPointers()) { // this field type has GC pointers in it, which need to be pointer-size aligned placementInfo.m_alignment = TARGET_POINTER_SIZE; @@ -310,7 +310,7 @@ namespace if (corElemType == ELEMENT_TYPE_VALUETYPE) { _ASSERTE(!pNestedType.IsNull()); - return pNestedType.GetMethodTable()->ContainsPointers() != FALSE; + return pNestedType.GetMethodTable()->ContainsGCPointers() != FALSE; } return TRUE; } diff --git a/src/coreclr/vm/comutilnative.cpp b/src/coreclr/vm/comutilnative.cpp index 73547a59e4df3..548e56cda251f 100644 --- a/src/coreclr/vm/comutilnative.cpp +++ b/src/coreclr/vm/comutilnative.cpp @@ -1623,7 +1623,7 @@ BOOL CanCompareBitsOrUseFastGetHashCode(MethodTable* mt) return mt->CanCompareBitsOrUseFastGetHashCode(); } - if (mt->ContainsPointers() + if (mt->ContainsGCPointers() || mt->IsNotTightlyPacked() || mt->GetClass()->IsInlineArray()) { diff --git a/src/coreclr/vm/dllimport.cpp b/src/coreclr/vm/dllimport.cpp index a1e64dae2d285..41fa1842c5c13 100644 --- a/src/coreclr/vm/dllimport.cpp +++ b/src/coreclr/vm/dllimport.cpp @@ -3368,7 +3368,7 @@ BOOL NDirect::MarshalingRequired( // as long as they aren't auto-layout and don't have any auto-layout fields. if (!runtimeMarshallingEnabled && !hndArgType.IsEnum() && - (hndArgType.GetMethodTable()->ContainsPointers() + (hndArgType.GetMethodTable()->ContainsGCPointers() || hndArgType.GetMethodTable()->IsAutoLayoutOrHasAutoLayoutField())) { return TRUE; diff --git a/src/coreclr/vm/gchelpers.cpp b/src/coreclr/vm/gchelpers.cpp index 06db3076ef4eb..335bd3cb25cab 100644 --- a/src/coreclr/vm/gchelpers.cpp +++ b/src/coreclr/vm/gchelpers.cpp @@ -413,7 +413,7 @@ OBJECTREF AllocateSzArray(MethodTable* pArrayMT, INT32 cElements, GC_ALLOC_FLAGS if (totalSize >= LARGE_OBJECT_SIZE && totalSize >= GCHeapUtilities::GetGCHeap()->GetLOHThreshold()) flags |= GC_ALLOC_LARGE_OBJECT_HEAP; - if (pArrayMT->ContainsPointers()) + if (pArrayMT->ContainsGCPointers()) flags |= GC_ALLOC_CONTAINS_REF; ArrayBase* orArray = NULL; @@ -513,7 +513,7 @@ OBJECTREF TryAllocateFrozenSzArray(MethodTable* pArrayMT, INT32 cElements) // The initial validation is copied from AllocateSzArray impl - if (pArrayMT->ContainsPointers() && cElements > 0) + if (pArrayMT->ContainsGCPointers() && cElements > 0) { // For arrays with GC pointers we can only work with empty arrays return NULL; @@ -720,7 +720,7 @@ OBJECTREF AllocateArrayEx(MethodTable *pArrayMT, INT32 *pArgs, DWORD dwNumArgs, if (totalSize >= LARGE_OBJECT_SIZE && totalSize >= GCHeapUtilities::GetGCHeap()->GetLOHThreshold()) flags |= GC_ALLOC_LARGE_OBJECT_HEAP; - if (pArrayMT->ContainsPointers()) + if (pArrayMT->ContainsGCPointers()) flags |= GC_ALLOC_CONTAINS_REF; ArrayBase* orArray = NULL; @@ -1066,7 +1066,7 @@ OBJECTREF AllocateObject(MethodTable *pMT #endif // FEATURE_COMINTEROP else { - if (pMT->ContainsPointers()) + if (pMT->ContainsGCPointers()) flags |= GC_ALLOC_CONTAINS_REF; if (pMT->HasFinalizer()) @@ -1122,7 +1122,7 @@ OBJECTREF TryAllocateFrozenObject(MethodTable* pObjMT) SetTypeHandleOnThreadForAlloc(TypeHandle(pObjMT)); - if (pObjMT->ContainsPointers() || pObjMT->IsComObjectType()) + if (pObjMT->ContainsGCPointers() || pObjMT->IsComObjectType()) { return NULL; } diff --git a/src/coreclr/vm/generics.cpp b/src/coreclr/vm/generics.cpp index 988229d8009a8..384b1007cf8bd 100644 --- a/src/coreclr/vm/generics.cpp +++ b/src/coreclr/vm/generics.cpp @@ -217,7 +217,7 @@ ClassLoader::CreateTypeHandleForNonCanonicalGenericInstantiation( #endif // FEATURE_COMINTEROP // The number of bytes used for GC info - size_t cbGC = pOldMT->ContainsPointers() ? ((CGCDesc*) pOldMT)->GetSize() : 0; + size_t cbGC = pOldMT->ContainsGCPointers() ? ((CGCDesc*) pOldMT)->GetSize() : 0; // Bytes are required for the vtable itself S_SIZE_T safe_cbMT = S_SIZE_T( cbGC ) + S_SIZE_T( sizeof(MethodTable) ); diff --git a/src/coreclr/vm/i386/jitinterfacex86.cpp b/src/coreclr/vm/i386/jitinterfacex86.cpp index 73603f2752969..3807b00a8ca6e 100644 --- a/src/coreclr/vm/i386/jitinterfacex86.cpp +++ b/src/coreclr/vm/i386/jitinterfacex86.cpp @@ -421,9 +421,9 @@ void *JIT_TrialAlloc::GenBox(Flags flags) // Here we are at the end of the success case // Check whether the object contains pointers - // test [ecx]MethodTable.m_dwFlags,MethodTable::enum_flag_ContainsPointers + // test [ecx]MethodTable.m_dwFlags,MethodTable::enum_flag_ContainsGCPointers sl.X86EmitOffsetModRM(0xf7, (X86Reg)0x0, kECX, offsetof(MethodTable, m_dwFlags)); - sl.Emit32(MethodTable::enum_flag_ContainsPointers); + sl.Emit32(MethodTable::enum_flag_ContainsGCPointers); CodeLabel *pointerLabel = sl.NewCodeLabel(); diff --git a/src/coreclr/vm/ilmarshalers.cpp b/src/coreclr/vm/ilmarshalers.cpp index b0fcef12d6b8d..75d979076bd3a 100644 --- a/src/coreclr/vm/ilmarshalers.cpp +++ b/src/coreclr/vm/ilmarshalers.cpp @@ -4383,7 +4383,7 @@ extern "C" void QCALLTYPE MngdNativeArrayMarshaler_ConvertContentsToNative(MngdN if ( (!ClrSafeInt::multiply(cElements, OleVariant::GetElementSizeForVarType(pThis->m_vt, pThis->m_pElementMT), cElements)) || cElements > MAX_SIZE_FOR_INTEROP) COMPlusThrow(kArgumentException, IDS_EE_STRUCTARRAYTOOLARGE); - _ASSERTE(!GetTypeHandleForCVType(OleVariant::GetCVTypeForVarType(pThis->m_vt)).GetMethodTable()->ContainsPointers()); + _ASSERTE(!GetTypeHandleForCVType(OleVariant::GetCVTypeForVarType(pThis->m_vt)).GetMethodTable()->ContainsGCPointers()); memcpyNoGCRefs(*pNativeHome, arrayRef->GetDataPtr(), cElements); } else @@ -4452,7 +4452,7 @@ extern "C" void QCALLTYPE MngdNativeArrayMarshaler_ConvertContentsToManaged(Mngd COMPlusThrow(kArgumentException, IDS_EE_STRUCTARRAYTOOLARGE); // If we are copying variants, strings, etc, we need to use write barrier - _ASSERTE(!GetTypeHandleForCVType(OleVariant::GetCVTypeForVarType(pThis->m_vt)).GetMethodTable()->ContainsPointers()); + _ASSERTE(!GetTypeHandleForCVType(OleVariant::GetCVTypeForVarType(pThis->m_vt)).GetMethodTable()->ContainsGCPointers()); memcpyNoGCRefs(arrayRef->GetDataPtr(), *pNativeHome, cElements); } else @@ -4567,7 +4567,7 @@ extern "C" void QCALLTYPE MngdFixedArrayMarshaler_ConvertContentsToNative(MngdFi SIZE_T cElements = arrayRef->GetNumComponents(); if (pMarshaler == NULL || pMarshaler->ComToOleArray == NULL) { - _ASSERTE(!GetTypeHandleForCVType(OleVariant::GetCVTypeForVarType(pThis->m_vt)).GetMethodTable()->ContainsPointers()); + _ASSERTE(!GetTypeHandleForCVType(OleVariant::GetCVTypeForVarType(pThis->m_vt)).GetMethodTable()->ContainsGCPointers()); memcpyNoGCRefs(pNativeHome, arrayRef->GetDataPtr(), nativeSize); } else @@ -4641,7 +4641,7 @@ extern "C" void QCALLTYPE MngdFixedArrayMarshaler_ConvertContentsToManaged(MngdF if (pMarshaler == NULL || pMarshaler->OleToComArray == NULL) { // If we are copying variants, strings, etc, we need to use write barrier - _ASSERTE(!GetTypeHandleForCVType(OleVariant::GetCVTypeForVarType(pThis->m_vt)).GetMethodTable()->ContainsPointers()); + _ASSERTE(!GetTypeHandleForCVType(OleVariant::GetCVTypeForVarType(pThis->m_vt)).GetMethodTable()->ContainsGCPointers()); memcpyNoGCRefs(arrayRef->GetDataPtr(), pNativeHome, nativeSize); } else diff --git a/src/coreclr/vm/interpreter.cpp b/src/coreclr/vm/interpreter.cpp index 908077f7ba389..181dc83cbea83 100644 --- a/src/coreclr/vm/interpreter.cpp +++ b/src/coreclr/vm/interpreter.cpp @@ -10959,7 +10959,7 @@ void Interpreter::DoIsReferenceOrContainsReferences(CORINFO_METHOD_HANDLE method MethodTable* typeArg = GetMethodTableFromClsHnd(sigInfoFull.sigInst.methInst[0]); - bool containsGcPtrs = typeArg->ContainsPointers(); + bool containsGcPtrs = typeArg->ContainsGCPointers(); // Return true for byref-like structs with ref fields (they might not have them) if (!containsGcPtrs && typeArg->IsByRefLike()) @@ -10981,7 +10981,7 @@ bool Interpreter::DoInterlockedCompareExchange(CorInfoType retType) } CONTRACTL_END; // These CompareExchange are must-expand: - // + // // long CompareExchange(ref long location1, long value, long comparand) // int CompareExchange(ref int location1, int value, int comparand) // ushort CompareExchange(ref ushort location1, ushort value, ushort comparand) @@ -11033,7 +11033,7 @@ bool Interpreter::DoInterlockedExchange(CorInfoType retType) } CONTRACTL_END; // These Exchange are must-expand: - // + // // long Exchange(ref long location1, long value) // int Exchange(ref int location1, int value) // ushort Exchange(ref ushort location1, ushort value) @@ -11082,7 +11082,7 @@ bool Interpreter::DoInterlockedExchangeAdd(CorInfoType retType) } CONTRACTL_END; // These ExchangeAdd are must-expand: - // + // // long ExchangeAdd(ref long location1, long value) // int ExchangeAdd(ref int location1, int value) // diff --git a/src/coreclr/vm/jitinterface.cpp b/src/coreclr/vm/jitinterface.cpp index ca66bba77f49d..935f1ccde8202 100644 --- a/src/coreclr/vm/jitinterface.cpp +++ b/src/coreclr/vm/jitinterface.cpp @@ -1182,7 +1182,7 @@ size_t CEEInfo::getClassThreadStaticDynamicInfo(CORINFO_CLASS_HANDLE cls) EE_TO_JIT_TRANSITION_LEAF(); - return result; + return result; } size_t CEEInfo::getClassStaticDynamicInfo(CORINFO_CLASS_HANDLE cls) @@ -1203,7 +1203,7 @@ size_t CEEInfo::getClassStaticDynamicInfo(CORINFO_CLASS_HANDLE cls) EE_TO_JIT_TRANSITION_LEAF(); - return result; + return result; } CorInfoHelpFunc CEEInfo::getSharedStaticsHelper(FieldDesc * pField, MethodTable * pFieldMT) @@ -1218,7 +1218,7 @@ CorInfoHelpFunc CEEInfo::getSharedStaticsHelper(FieldDesc * pField, MethodTable bool isCollectible = pFieldMT->Collectible(); _ASSERTE(!isInexactMT); CorInfoHelpFunc helper; - + if (threadStatic) { if (GCStatic) @@ -1551,9 +1551,9 @@ void CEEInfo::getFieldInfo (CORINFO_RESOLVED_TOKEN * pResolvedToken, Object* frozenObj = VolatileLoad((Object**)pResult->fieldLookup.addr); _ASSERT(frozenObj != nullptr); - // ContainsPointers here is unnecessary but it's cheaper than IsInFrozenSegment + // ContainsGCPointers here is unnecessary but it's cheaper than IsInFrozenSegment // for structs containing gc handles - if (!frozenObj->GetMethodTable()->ContainsPointers() && + if (!frozenObj->GetMethodTable()->ContainsGCPointers() && GCHeapUtilities::GetGCHeap()->IsInFrozenSegment(frozenObj)) { pResult->fieldLookup.addr = frozenObj->GetData(); @@ -2014,7 +2014,7 @@ unsigned CEEInfo::getClassAlignmentRequirementStatic(TypeHandle clsHnd) } else if (pInfo->IsManagedSequential() || pInfo->IsBlittable()) { - _ASSERTE(!pMT->ContainsPointers()); + _ASSERTE(!pMT->ContainsGCPointers()); // if it's managed sequential, we use the managed alignment requirement result = pInfo->m_ManagedLargestAlignmentRequirementOfAllMembers; @@ -2425,7 +2425,7 @@ unsigned CEEInfo::getClassGClayoutStatic(TypeHandle VMClsHnd, BYTE* gcPtrs) (size + TARGET_POINTER_SIZE - 1) / TARGET_POINTER_SIZE); // walk the GC descriptors, turning on the correct bits - if (pMT->ContainsPointers()) + if (pMT->ContainsGCPointers()) { CGCDesc* map = CGCDesc::GetCGCDescFromMT(pMT); CGCDescSeries * pByValueSeries = map->GetLowestSeries(); @@ -3829,7 +3829,7 @@ uint32_t CEEInfo::getClassAttribsInternal (CORINFO_CLASS_HANDLE clsHnd) if (VMClsHnd.IsCanonicalSubtype()) ret |= CORINFO_FLG_SHAREDINST; - if (pMT->ContainsPointers() || pMT == g_TypedReferenceMT) + if (pMT->ContainsGCPointers() || pMT == g_TypedReferenceMT) ret |= CORINFO_FLG_CONTAINS_GC_PTR; if (pMT->IsDelegate()) @@ -11750,7 +11750,7 @@ bool CEEInfo::getStaticFieldContent(CORINFO_FIELD_HANDLE fieldHnd, uint8_t* buff { TypeHandle structType = field->GetFieldTypeHandleThrowing(); PTR_MethodTable structTypeMT = structType.AsMethodTable(); - if (!structTypeMT->ContainsPointers()) + if (!structTypeMT->ContainsGCPointers()) { // Fast-path: no GC pointers in the struct, we can use memcpy useMemcpy = true; @@ -11849,7 +11849,7 @@ bool CEEInfo::getObjectContent(CORINFO_OBJECT_HANDLE handle, uint8_t* buffer, in { Object* obj = OBJECTREFToObject(objRef); PTR_MethodTable type = obj->GetMethodTable(); - if (type->ContainsPointers()) + if (type->ContainsGCPointers()) { // RuntimeType has a gc field (object m_keepAlive), but if the object is in a frozen segment // it means that field is always nullptr so we can read any part of the object: @@ -13155,7 +13155,7 @@ void ComputeGCRefMap(MethodTable * pMT, BYTE * pGCRefMap, size_t cbGCRefMap) ZeroMemory(pGCRefMap, cbGCRefMap); - if (!pMT->ContainsPointers()) + if (!pMT->ContainsGCPointers()) return; CGCDesc* map = CGCDesc::GetCGCDescFromMT(pMT); @@ -13304,7 +13304,7 @@ BOOL TypeLayoutCheck(MethodTable * pMT, PCCOR_SIGNATURE pBlob, BOOL printDiff) { if (dwFlags & READYTORUN_LAYOUT_GCLayout_Empty) { - if (pMT->ContainsPointers()) + if (pMT->ContainsGCPointers()) { if (printDiff) { diff --git a/src/coreclr/vm/method.cpp b/src/coreclr/vm/method.cpp index 4a8dd9efbb57c..628b8c6f6e45d 100644 --- a/src/coreclr/vm/method.cpp +++ b/src/coreclr/vm/method.cpp @@ -1192,7 +1192,7 @@ ReturnKind MethodDesc::ParseReturnKindFromSig(INDEBUG(bool supportStringConstruc } #endif // UNIX_AMD64_ABI - if (pReturnTypeMT->ContainsPointers() || pReturnTypeMT->IsByRefLike()) + if (pReturnTypeMT->ContainsGCPointers() || pReturnTypeMT->IsByRefLike()) { if (pReturnTypeMT->GetNumInstanceFields() == 1) { diff --git a/src/coreclr/vm/methodtable.cpp b/src/coreclr/vm/methodtable.cpp index bb8a1646e511b..8a0632e768b88 100644 --- a/src/coreclr/vm/methodtable.cpp +++ b/src/coreclr/vm/methodtable.cpp @@ -658,7 +658,7 @@ void MethodTable::AllocateAuxiliaryData(LoaderAllocator *pAllocator, Module *pLo } prependedAllocationSpace = prependedAllocationSpace + sizeofStaticsStructure; - + cbAuxiliaryData = cbAuxiliaryData + S_SIZE_T(prependedAllocationSpace) + extraAllocation; if (cbAuxiliaryData.IsOverflow()) ThrowHR(COR_E_OVERFLOW); @@ -1569,7 +1569,7 @@ MethodTable::IsExternallyVisible() BOOL MethodTable::IsAllGCPointers() { - if (this->ContainsPointers()) + if (this->ContainsGCPointers()) { // check for canonical GC encoding for all-pointer types CGCDesc* pDesc = CGCDesc::GetCGCDescFromMT(this); @@ -3368,7 +3368,7 @@ void MethodTable::AllocateRegularStaticBox(FieldDesc* pField, Object** boxedStat bool hasFixedAddr = HasFixedAddressVTStatics(); LOG((LF_CLASSLOADER, LL_INFO10000, "\tInstantiating static of type %s\n", pFieldMT->GetDebugClassName())); - const bool canBeFrozen = !pFieldMT->ContainsPointers() && !Collectible(); + const bool canBeFrozen = !pFieldMT->ContainsGCPointers() && !Collectible(); OBJECTREF obj = AllocateStaticBox(pFieldMT, hasFixedAddr, canBeFrozen); SetObjectReference((OBJECTREF*)(boxedStaticHandle), obj); GCPROTECT_END(); @@ -3394,7 +3394,7 @@ OBJECTREF MethodTable::AllocateStaticBox(MethodTable* pFieldMT, BOOL fPinned, bo if (canBeFrozen) { // In case if we don't plan to collect this handle we may try to allocate it on FOH - _ASSERT(!pFieldMT->ContainsPointers()); + _ASSERT(!pFieldMT->ContainsGCPointers()); FrozenObjectHeapManager* foh = SystemDomain::GetFrozenObjectHeapManager(); obj = ObjectToOBJECTREF(foh->TryAllocateObject(pFieldMT, pFieldMT->GetBaseSize())); // obj can be null in case if struct is huge (>64kb) @@ -3837,7 +3837,7 @@ bool MethodTable::IsInitedIfStaticDataAllocated() // If there is a class constructor, then the class cannot be preinitted. return false; } - + if (GetClass()->GetNonGCRegularStaticFieldBytes() == 0 && GetClass()->GetNumHandleRegularStatics() == 0) { // If there aren't static fields that are not thread statics, then the class is preinitted. @@ -7455,7 +7455,7 @@ MethodTable::EnumMemoryRegions(CLRDataEnumMemoryFlags flags) DacEnumMemoryRegion(dac_cast(this), size); // Make sure the GCDescs are added to the dump - if (ContainsPointers()) + if (ContainsGCPointers()) { PTR_CGCDesc gcdesc = CGCDesc::GetCGCDescFromMT(this); size_t size = gcdesc->GetSize(); diff --git a/src/coreclr/vm/methodtable.h b/src/coreclr/vm/methodtable.h index 23fcb92e5291a..80abb784df1fa 100644 --- a/src/coreclr/vm/methodtable.h +++ b/src/coreclr/vm/methodtable.h @@ -601,6 +601,7 @@ struct DynamicStaticsInfo { oldValFromInterlockedOp = InterlockedCompareExchangeT(pAddr, newVal | oldVal, oldVal); } + } while(oldValFromInterlockedOp != oldVal); return true; } @@ -1784,10 +1785,10 @@ class MethodTable inline WORD GetNumIntroducedInstanceFields(); - BOOL ContainsPointers() + BOOL ContainsGCPointers() { LIMITED_METHOD_CONTRACT; - return !!GetFlag(enum_flag_ContainsPointers); + return !!GetFlag(enum_flag_ContainsGCPointers); } BOOL Collectible() @@ -1800,10 +1801,10 @@ class MethodTable #endif } - BOOL ContainsPointersOrCollectible() + BOOL ContainsGCPointersOrCollectible() { LIMITED_METHOD_CONTRACT; - return GetFlag(enum_flag_ContainsPointers) || GetFlag(enum_flag_Collectible); + return GetFlag(enum_flag_ContainsGCPointers) || GetFlag(enum_flag_Collectible); } OBJECTHANDLE GetLoaderAllocatorObjectHandle(); @@ -1813,10 +1814,10 @@ class MethodTable BOOL IsAllGCPointers(); - void SetContainsPointers() + void SetContainsGCPointers() { LIMITED_METHOD_CONTRACT; - SetFlag(enum_flag_ContainsPointers); + SetFlag(enum_flag_ContainsGCPointers); } #ifdef FEATURE_64BIT_ALIGNMENT @@ -3603,7 +3604,7 @@ public : enum_flag_RequiresAlign8 = 0x00800000, // Type requires 8-byte alignment (only set on platforms that require this and don't get it implicitly) #endif - enum_flag_ContainsPointers = 0x01000000, // Contains object references + enum_flag_ContainsGCPointers = 0x01000000, // Contains object references enum_flag_HasTypeEquivalence = 0x02000000, // can be equivalent to another type enum_flag_IsTrackedReferenceWithFinalizer = 0x04000000, // unused = 0x08000000, @@ -3888,8 +3889,23 @@ public : BOOL Validate (); static void GetStaticsOffsets(StaticsOffsetType staticsOffsetType, bool fGenericsStatics, uint32_t *dwGCOffset, uint32_t *dwNonGCOffset); + + template friend struct ::cdac_offsets; }; // class MethodTable +template<> struct cdac_offsets +{ + static constexpr size_t MTFlags = offsetof(MethodTable, m_dwFlags); + static constexpr size_t BaseSize = offsetof(MethodTable, m_BaseSize); + static constexpr size_t MTFlags2 = offsetof(MethodTable, m_dwFlags2); + static constexpr size_t EEClassOrCanonMT = offsetof(MethodTable, m_pEEClass); + static constexpr size_t Module = offsetof(MethodTable, m_pModule); + static constexpr size_t AuxiliaryData = offsetof(MethodTable, m_pAuxiliaryData); + static constexpr size_t ParentMethodTable = offsetof(MethodTable, m_pParentMethodTable); + static constexpr size_t NumInterfaces = offsetof(MethodTable, m_wNumInterfaces); + static constexpr size_t NumVirtuals = offsetof(MethodTable, m_wNumVirtuals); +}; + #ifndef CROSSBITNESS_COMPILE static_assert_no_msg(sizeof(MethodTable) == SIZEOF__MethodTable_); #endif diff --git a/src/coreclr/vm/methodtablebuilder.cpp b/src/coreclr/vm/methodtablebuilder.cpp index 3e684a4bab3a1..549031c53ba05 100644 --- a/src/coreclr/vm/methodtablebuilder.cpp +++ b/src/coreclr/vm/methodtablebuilder.cpp @@ -1878,7 +1878,7 @@ MethodTableBuilder::BuildMethodTableThrowing( // GC reqires the series to be sorted. // TODO: fix it so that we emit them in the correct order in the first place. - if (pMT->ContainsPointers()) + if (pMT->ContainsGCPointers()) { CGCDesc* gcDesc = CGCDesc::GetCGCDescFromMT(pMT); qsort(gcDesc->GetLowestSeries(), (int)gcDesc->GetNumSeries(), sizeof(CGCDescSeries), compareCGCDescSeries); @@ -1907,7 +1907,7 @@ MethodTableBuilder::BuildMethodTableThrowing( // // structs with GC pointers MUST be pointer sized aligned because the GC assumes it - if (IsValueClass() && pMT->ContainsPointers() && (bmtFP->NumInstanceFieldBytes % TARGET_POINTER_SIZE != 0)) + if (IsValueClass() && pMT->ContainsGCPointers() && (bmtFP->NumInstanceFieldBytes % TARGET_POINTER_SIZE != 0)) { BuildMethodTableThrowException(IDS_CLASSLOAD_BADFORMAT); } @@ -2098,7 +2098,7 @@ MethodTableBuilder::ResolveInterfaces( MethodTable * pParentClass = GetParentMethodTable(); PREFIX_ASSUME(pParentClass != NULL); - bmtParent->NumParentPointerSeries = pParentClass->ContainsPointers() ? + bmtParent->NumParentPointerSeries = pParentClass->ContainsGCPointers() ? (DWORD)CGCDesc::GetCGCDescFromMT(pParentClass)->GetNumSeries() : 0; if (pParentClass->HasFieldsWhichMustBeInited()) @@ -8317,7 +8317,7 @@ VOID MethodTableBuilder::PlaceInstanceFields(MethodTable ** pByValueClassCach } else #endif // FEATURE_64BIT_ALIGNMENT - if (pByValueMT->ContainsPointers()) + if (pByValueMT->ContainsGCPointers()) { // this field type has GC pointers in it, which need to be pointer-size aligned // so do this if it has not been done already @@ -8335,13 +8335,13 @@ VOID MethodTableBuilder::PlaceInstanceFields(MethodTable ** pByValueClassCach pFieldDescList[i].SetOffset(dwCumulativeInstanceFieldPos - dwOffsetBias); dwCumulativeInstanceFieldPos += pByValueMT->GetNumInstanceFieldBytes(); - if (pByValueMT->ContainsPointers()) + if (pByValueMT->ContainsGCPointers()) { // Add pointer series for by-value classes dwNumGCPointerSeries += (DWORD)CGCDesc::GetCGCDescFromMT(pByValueMT)->GetNumSeries(); } - if (!pByValueMT->ContainsPointers() || !pByValueMT->IsAllGCPointers()) + if (!pByValueMT->ContainsGCPointers() || !pByValueMT->IsAllGCPointers()) { isAllGCPointers = false; } @@ -8684,7 +8684,7 @@ MethodTableBuilder::HandleExplicitLayout( else { MethodTable *pByValueMT = pByValueClassCache[valueClassCacheIndex]; - if (pByValueMT->IsByRefLike() || pByValueMT->ContainsPointers()) + if (pByValueMT->IsByRefLike() || pByValueMT->ContainsGCPointers()) { if ((pFD->GetOffset() & ((ULONG)TARGET_POINTER_SIZE - 1)) != 0) { @@ -8881,7 +8881,7 @@ MethodTableBuilder::HandleExplicitLayout( memset((void*)vcLayout, nonoref, fieldSize); // If the type contains pointers fill it out from the GC data - if (pMT->ContainsPointers()) + if (pMT->ContainsGCPointers()) { // use pointer series to locate the orefs CGCDesc* map = CGCDesc::GetCGCDescFromMT(pMT); @@ -9097,7 +9097,7 @@ MethodTableBuilder::HandleGCForExplicitLayout() if (bmtFP->NumGCPointerSeries != 0) { - pMT->SetContainsPointers(); + pMT->SetContainsGCPointers(); // Copy the pointer series map from the parent CGCDesc::Init( (PVOID) pMT, bmtFP->NumGCPointerSeries ); @@ -10643,7 +10643,7 @@ MethodTableBuilder::SetupMethodTable2( pMT->SetHasClassConstructor(); CONSISTENCY_CHECK(pMT->GetClassConstructorSlot() == bmtVT->pCCtor->GetSlotIndex()); } - + if (bmtVT->pDefaultCtor != NULL) { pMT->SetHasDefaultConstructor(); @@ -11569,7 +11569,7 @@ VOID MethodTableBuilder::HandleGCForValueClasses(MethodTable ** pByValueClassCac CGCDescSeries *pSeries; CGCDescSeries *pHighest; - pMT->SetContainsPointers(); + pMT->SetContainsGCPointers(); CGCDesc::Init( (PVOID) pMT, bmtFP->NumGCPointerSeries ); @@ -11625,7 +11625,7 @@ VOID MethodTableBuilder::HandleGCForValueClasses(MethodTable ** pByValueClassCac { MethodTable* pByValueMT = pByValueClassCache[i]; - if (pByValueMT->ContainsPointers()) + if (pByValueMT->ContainsGCPointers()) { // Offset of the by value class in the class we are building, does NOT include Object DWORD dwCurrentOffset = pFieldDescList[i].GetOffset(); diff --git a/src/coreclr/vm/mlinfo.cpp b/src/coreclr/vm/mlinfo.cpp index f3401ae2c8fd7..8131e4fd3053a 100644 --- a/src/coreclr/vm/mlinfo.cpp +++ b/src/coreclr/vm/mlinfo.cpp @@ -1174,7 +1174,7 @@ namespace TypeHandle sigTH = sig.GetTypeHandleThrowing(pModule, pTypeContext); MethodTable* pMT = sigTH.GetMethodTable(); - if (!pMT->IsValueType() || pMT->ContainsPointers()) + if (!pMT->IsValueType() || pMT->ContainsGCPointers()) { *errorResIDOut = IDS_EE_BADMARSHAL_MARSHAL_DISABLED; return MarshalInfo::MARSHAL_TYPE_UNKNOWN; diff --git a/src/coreclr/vm/object.cpp b/src/coreclr/vm/object.cpp index 2a05ca470bff1..d74d368b0bafd 100644 --- a/src/coreclr/vm/object.cpp +++ b/src/coreclr/vm/object.cpp @@ -364,7 +364,7 @@ void STDCALL CopyValueClassUnchecked(void* dest, void* src, MethodTable *pMT) _ASSERTE(!pMT->IsArray()); // bunch of assumptions about arrays wrong. - if (pMT->ContainsPointers()) + if (pMT->ContainsGCPointers()) { memmoveGCRefs(dest, src, pMT->GetNumInstanceFieldBytes()); } diff --git a/src/coreclr/vm/proftoeeinterfaceimpl.cpp b/src/coreclr/vm/proftoeeinterfaceimpl.cpp index 7c90d9e1753ec..3cffa14b12f4a 100644 --- a/src/coreclr/vm/proftoeeinterfaceimpl.cpp +++ b/src/coreclr/vm/proftoeeinterfaceimpl.cpp @@ -1166,7 +1166,7 @@ bool HeapWalkHelper(Object * pBO, void * pvContext) ProfilerWalkHeapContext * pProfilerWalkHeapContext = (ProfilerWalkHeapContext *) pvContext; - if (pMT->ContainsPointersOrCollectible()) + if (pMT->ContainsGCPointersOrCollectible()) { // First round through calculates the number of object refs for this class GCHeapUtilities::GetGCHeap()->DiagWalkObject(pBO, &CountContainedObjectRef, (void *)&cNumRefs); @@ -6747,7 +6747,7 @@ HRESULT ProfToEEInterfaceImpl::EnumerateObjectReferences(ObjectID objectId, Obje Object* pBO = (Object*)objectId; MethodTable *pMT = pBO->GetMethodTable(); - if (pMT->ContainsPointersOrCollectible()) + if (pMT->ContainsGCPointersOrCollectible()) { GCHeapUtilities::GetGCHeap()->DiagWalkObject2(pBO, (walk_fn2)callback, clientData); return S_OK; diff --git a/src/coreclr/vm/reflectioninvocation.cpp b/src/coreclr/vm/reflectioninvocation.cpp index 115e0f85ac96c..aec38501adf60 100644 --- a/src/coreclr/vm/reflectioninvocation.cpp +++ b/src/coreclr/vm/reflectioninvocation.cpp @@ -647,7 +647,7 @@ FCIMPL4(Object*, RuntimeMethodHandle::InvokeMethod, *(PVOID *)pArgDst = pStackCopy; // save the info into ValueClassInfo - if (pMT->ContainsPointers()) + if (pMT->ContainsGCPointers()) { pValueClasses = new (_alloca(sizeof(ValueClassInfo))) ValueClassInfo(pStackCopy, pMT, pValueClasses); } diff --git a/src/coreclr/vm/siginfo.cpp b/src/coreclr/vm/siginfo.cpp index 108e95cc39e53..facb809cd4841 100644 --- a/src/coreclr/vm/siginfo.cpp +++ b/src/coreclr/vm/siginfo.cpp @@ -5062,7 +5062,7 @@ void ReportPointersFromValueType(promote_func *fn, ScanContext *sc, PTR_MethodTa reporter.Find(pMT, 0 /* baseOffset */); } - if (!pMT->ContainsPointers()) + if (!pMT->ContainsGCPointers()) return; CGCDesc* map = CGCDesc::GetCGCDescFromMT(pMT); @@ -5091,7 +5091,7 @@ void ReportPointersFromValueTypeArg(promote_func *fn, ScanContext *sc, PTR_Metho { WRAPPER_NO_CONTRACT; - if (!pMT->ContainsPointers() && !pMT->IsByRefLike()) + if (!pMT->ContainsGCPointers() && !pMT->IsByRefLike()) { return; } diff --git a/src/coreclr/vm/stubgen.cpp b/src/coreclr/vm/stubgen.cpp index 29595b9414d51..283b6d3fa2d88 100644 --- a/src/coreclr/vm/stubgen.cpp +++ b/src/coreclr/vm/stubgen.cpp @@ -2657,7 +2657,7 @@ void ILStubLinker::TransformArgForJIT(LocalDesc *pLoc) // JIT will handle structures if (pLoc->InternalToken.IsValueType()) { - _ASSERTE(pLoc->InternalToken.IsNativeValueType() || !pLoc->InternalToken.GetMethodTable()->ContainsPointers()); + _ASSERTE(pLoc->InternalToken.IsNativeValueType() || !pLoc->InternalToken.GetMethodTable()->ContainsGCPointers()); break; } FALLTHROUGH; diff --git a/src/coreclr/vm/tailcallhelp.cpp b/src/coreclr/vm/tailcallhelp.cpp index e9fb3a75852ea..4d9c60838b54f 100644 --- a/src/coreclr/vm/tailcallhelp.cpp +++ b/src/coreclr/vm/tailcallhelp.cpp @@ -281,7 +281,7 @@ bool TailCallHelp::GenerateGCDescriptor( TypeHandle tyHnd = val.TyHnd; if (tyHnd.IsValueType()) { - if (!tyHnd.GetMethodTable()->ContainsPointers()) + if (!tyHnd.GetMethodTable()->ContainsGCPointers()) { #ifndef TARGET_X86 // The generic instantiation arg is right after this pointer diff --git a/src/native/managed/cdacreader/src/Constants.cs b/src/native/managed/cdacreader/src/Constants.cs index c4003c9630656..5c95db68d227f 100644 --- a/src/native/managed/cdacreader/src/Constants.cs +++ b/src/native/managed/cdacreader/src/Constants.cs @@ -15,5 +15,7 @@ internal static class Globals internal const string FeatureEHFunclets = nameof(FeatureEHFunclets); internal const string SOSBreakingChangeVersion = nameof(SOSBreakingChangeVersion); + + internal const string FreeObjectMethodTable = nameof(FreeObjectMethodTable); } } diff --git a/src/native/managed/cdacreader/src/Contracts/Registry.cs b/src/native/managed/cdacreader/src/Contracts/Registry.cs index 98f2a28d7b564..9f7151adec432 100644 --- a/src/native/managed/cdacreader/src/Contracts/Registry.cs +++ b/src/native/managed/cdacreader/src/Contracts/Registry.cs @@ -20,6 +20,7 @@ public Registry(Target target) public IException Exception => GetContract(); public IThread Thread => GetContract(); + public IRuntimeTypeSystem RuntimeTypeSystem => GetContract(); private T GetContract() where T : IContract { diff --git a/src/native/managed/cdacreader/src/Contracts/RuntimeTypeSystem.cs b/src/native/managed/cdacreader/src/Contracts/RuntimeTypeSystem.cs new file mode 100644 index 0000000000000..8d37ca5441fcd --- /dev/null +++ b/src/native/managed/cdacreader/src/Contracts/RuntimeTypeSystem.cs @@ -0,0 +1,67 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Reflection.Metadata.Ecma335; + +namespace Microsoft.Diagnostics.DataContractReader.Contracts; + +// an opaque handle to a method table. See IMetadata.GetMethodTableData +internal readonly struct MethodTableHandle +{ + internal MethodTableHandle(TargetPointer address) + { + Address = address; + } + + internal TargetPointer Address { get; } +} + +internal interface IRuntimeTypeSystem : IContract +{ + static string IContract.Name => nameof(RuntimeTypeSystem); + static IContract IContract.Create(Target target, int version) + { + TargetPointer targetPointer = target.ReadGlobalPointer(Constants.Globals.FreeObjectMethodTable); + TargetPointer freeObjectMethodTable = target.ReadPointer(targetPointer); + return version switch + { + 1 => new RuntimeTypeSystem_1(target, freeObjectMethodTable), + _ => default(RuntimeTypeSystem), + }; + } + + #region MethodTable inspection APIs + public virtual MethodTableHandle GetMethodTableHandle(TargetPointer targetPointer) => throw new NotImplementedException(); + + public virtual TargetPointer GetModule(MethodTableHandle methodTable) => throw new NotImplementedException(); + // A canonical method table is either the MethodTable itself, or in the case of a generic instantiation, it is the + // MethodTable of the prototypical instance. + public virtual TargetPointer GetCanonicalMethodTable(MethodTableHandle methodTable) => throw new NotImplementedException(); + public virtual TargetPointer GetParentMethodTable(MethodTableHandle methodTable) => throw new NotImplementedException(); + + public virtual uint GetBaseSize(MethodTableHandle methodTable) => throw new NotImplementedException(); + // The component size is only available for strings and arrays. It is the size of the element type of the array, or the size of an ECMA 335 character (2 bytes) + public virtual uint GetComponentSize(MethodTableHandle methodTable) => throw new NotImplementedException(); + + // True if the MethodTable is the sentinel value associated with unallocated space in the managed heap + public virtual bool IsFreeObjectMethodTable(MethodTableHandle methodTable) => throw new NotImplementedException(); + public virtual bool IsString(MethodTableHandle methodTable) => throw new NotImplementedException(); + // True if the MethodTable represents a type that contains managed references + public virtual bool ContainsGCPointers(MethodTableHandle methodTable) => throw new NotImplementedException(); + public virtual bool IsDynamicStatics(MethodTableHandle methodTable) => throw new NotImplementedException(); + public virtual ushort GetNumMethods(MethodTableHandle methodTable) => throw new NotImplementedException(); + public virtual ushort GetNumInterfaces(MethodTableHandle methodTable) => throw new NotImplementedException(); + + // Returns an ECMA-335 TypeDef table token for this type, or for its generic type definition if it is a generic instantiation + public virtual uint GetTypeDefToken(MethodTableHandle methodTable) => throw new NotImplementedException(); + // Returns the ECMA 335 TypeDef table Flags value (a bitmask of TypeAttributes) for this type, + // or for its generic type definition if it is a generic instantiation + public virtual uint GetTypeDefTypeAttributes(MethodTableHandle methodTable) => throw new NotImplementedException(); + #endregion MethodTable inspection APIs +} + +internal struct RuntimeTypeSystem : IRuntimeTypeSystem +{ + // Everything throws NotImplementedException +} diff --git a/src/native/managed/cdacreader/src/Contracts/RuntimeTypeSystem_1.MethodTableFlags.cs b/src/native/managed/cdacreader/src/Contracts/RuntimeTypeSystem_1.MethodTableFlags.cs new file mode 100644 index 0000000000000..e6f98f0d8e2f0 --- /dev/null +++ b/src/native/managed/cdacreader/src/Contracts/RuntimeTypeSystem_1.MethodTableFlags.cs @@ -0,0 +1,87 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; + +namespace Microsoft.Diagnostics.DataContractReader.Contracts; + +internal partial struct RuntimeTypeSystem_1 +{ + // The lower 16-bits of the MTFlags field are used for these flags, + // if WFLAGS_HIGH.HasComponentSize is unset + [Flags] + internal enum WFLAGS_LOW : uint + { + GenericsMask = 0x00000030, + GenericsMask_NonGeneric = 0x00000000, // no instantiation + + StringArrayValues = + GenericsMask_NonGeneric | + 0, + } + + // Upper bits of MTFlags + [Flags] + internal enum WFLAGS_HIGH : uint + { + Category_Mask = 0x000F0000, + Category_Array = 0x00080000, + Category_Array_Mask = 0x000C0000, + Category_Interface = 0x000C0000, + ContainsGCPointers = 0x01000000, + HasComponentSize = 0x80000000, // This is set if lower 16 bits is used for the component size, + // otherwise the lower bits are used for WFLAGS_LOW + } + + [Flags] + internal enum WFLAGS2_ENUM : uint + { + DynamicStatics = 0x0002, + } + + internal struct MethodTableFlags + { + public uint MTFlags { get; init; } + public uint MTFlags2 { get; init; } + public uint BaseSize { get; init; } + + private const int MTFlags2TypeDefRidShift = 8; + private WFLAGS_HIGH FlagsHigh => (WFLAGS_HIGH)MTFlags; + private WFLAGS_LOW FlagsLow => (WFLAGS_LOW)MTFlags; + public int GetTypeDefRid() => (int)(MTFlags2 >> MTFlags2TypeDefRidShift); + + public WFLAGS_LOW GetFlag(WFLAGS_LOW mask) => throw new NotImplementedException("TODO"); + public WFLAGS_HIGH GetFlag(WFLAGS_HIGH mask) => FlagsHigh & mask; + + public WFLAGS2_ENUM GetFlag(WFLAGS2_ENUM mask) => (WFLAGS2_ENUM)MTFlags2 & mask; + + private ushort ComponentSizeBits => (ushort)(MTFlags & 0x0000ffff); // note: caller should check HasComponentSize + + private bool TestFlagWithMask(WFLAGS_LOW mask, WFLAGS_LOW flag) + { + if (IsStringOrArray) + { + return (WFLAGS_LOW.StringArrayValues & mask) == flag; + } + else + { + return (FlagsLow & mask) == flag; + } + } + + private bool TestFlagWithMask(WFLAGS2_ENUM mask, WFLAGS2_ENUM flag) + { + return ((WFLAGS2_ENUM)MTFlags2 & mask) == flag; + } + + public bool HasComponentSize => GetFlag(WFLAGS_HIGH.HasComponentSize) != 0; + public bool IsInterface => GetFlag(WFLAGS_HIGH.Category_Mask) == WFLAGS_HIGH.Category_Interface; + public bool IsString => HasComponentSize && !IsArray && ComponentSizeBits == 2; + public bool IsArray => GetFlag(WFLAGS_HIGH.Category_Array_Mask) == WFLAGS_HIGH.Category_Array; + public bool IsStringOrArray => HasComponentSize; + public ushort ComponentSize => HasComponentSize ? ComponentSizeBits : (ushort)0; + public bool HasInstantiation => !TestFlagWithMask(WFLAGS_LOW.GenericsMask, WFLAGS_LOW.GenericsMask_NonGeneric); + public bool ContainsGCPointers => GetFlag(WFLAGS_HIGH.ContainsGCPointers) != 0; + public bool IsDynamicStatics => GetFlag(WFLAGS2_ENUM.DynamicStatics) != 0; + } +} diff --git a/src/native/managed/cdacreader/src/Contracts/RuntimeTypeSystem_1.NonValidated.cs b/src/native/managed/cdacreader/src/Contracts/RuntimeTypeSystem_1.NonValidated.cs new file mode 100644 index 0000000000000..ef392130e6231 --- /dev/null +++ b/src/native/managed/cdacreader/src/Contracts/RuntimeTypeSystem_1.NonValidated.cs @@ -0,0 +1,198 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; + +namespace Microsoft.Diagnostics.DataContractReader.Contracts; + +internal partial struct RuntimeTypeSystem_1 : IRuntimeTypeSystem +{ + // GC Heap corruption may create situations where a pointer value may point to garbage or even + // to an unmapped memory region. + // All types here have not been validated as actually representing a MethodTable, EEClass, etc. + // All checks are unsafe and may throw if we access an invalid address in target memory. + internal static class NonValidated + { + + // This doesn't need as many properties as MethodTable because we don't want to be operating on + // a NonValidatedMethodTable for too long + internal struct MethodTable + { + private readonly Target _target; + private readonly Target.TypeInfo _type; + internal TargetPointer Address { get; init; } + + private MethodTableFlags? _methodTableFlags; + + internal MethodTable(Target target, TargetPointer methodTablePointer) + { + _target = target; + _type = target.GetTypeInfo(DataType.MethodTable); + Address = methodTablePointer; + _methodTableFlags = null; + } + + private MethodTableFlags GetOrCreateFlags() + { + if (_methodTableFlags == null) + { + // note: may throw if the method table Address is corrupted + MethodTableFlags flags = new MethodTableFlags + { + MTFlags = _target.Read(Address + (ulong)_type.Fields[nameof(MethodTableFlags.MTFlags)].Offset), + MTFlags2 = _target.Read(Address + (ulong)_type.Fields[nameof(MethodTableFlags.MTFlags2)].Offset), + BaseSize = _target.Read(Address + (ulong)_type.Fields[nameof(MethodTableFlags.BaseSize)].Offset), + }; + _methodTableFlags = flags; + } + return _methodTableFlags.Value; + } + + internal MethodTableFlags Flags => GetOrCreateFlags(); + + internal TargetPointer EEClassOrCanonMT => _target.ReadPointer(Address + (ulong)_type.Fields[nameof(EEClassOrCanonMT)].Offset); + internal TargetPointer EEClass => GetEEClassOrCanonMTBits(EEClassOrCanonMT) == EEClassOrCanonMTBits.EEClass ? EEClassOrCanonMT : throw new InvalidOperationException("not an EEClass"); + internal TargetPointer CanonMT + { + get + { + if (GetEEClassOrCanonMTBits(EEClassOrCanonMT) == EEClassOrCanonMTBits.CanonMT) + { + return new TargetPointer((ulong)EEClassOrCanonMT & ~(ulong)EEClassOrCanonMTBits.Mask); + } + else + { + throw new InvalidOperationException("not a canonical method table"); + } + } + } + } + + internal struct EEClass + { + public readonly Target _target; + private readonly Target.TypeInfo _type; + + internal TargetPointer Address { get; init; } + + internal EEClass(Target target, TargetPointer eeClassPointer) + { + _target = target; + Address = eeClassPointer; + _type = target.GetTypeInfo(DataType.EEClass); + } + + internal TargetPointer MethodTable => _target.ReadPointer(Address + (ulong)_type.Fields[nameof(MethodTable)].Offset); + } + + internal static MethodTable GetMethodTableData(Target target, TargetPointer methodTablePointer) + { + return new MethodTable(target, methodTablePointer); + } + + internal static EEClass GetEEClassData(Target target, TargetPointer eeClassPointer) + { + return new EEClass(target, eeClassPointer); + } + + } + + /// + /// Validates that the given address is a valid MethodTable. + /// + /// + /// If the target process has memory corruption, we may see pointers that are not valid method tables. + /// We validate by looking at the MethodTable -> EEClass -> MethodTable relationship (which may throw if we access invalid memory). + /// And then we do some ad-hoc checks on the method table flags. + private bool ValidateMethodTablePointer(NonValidated.MethodTable umt) + { + try + { + if (!ValidateThrowing(umt)) + { + return false; + } + if (!ValidateMethodTableAdHoc(umt)) + { + return false; + } + } + catch (System.Exception) + { + // TODO(cdac): maybe don't swallow all exceptions? We could consider a richer contract that + // helps to track down what sort of memory corruption caused the validation to fail. + // TODO(cdac): we could also consider a more fine-grained exception type so we don't mask + // programmer mistakes in cdacreader. + return false; + } + return true; + } + + // This portion of validation may throw if we are trying to read an invalid address in the target process + private bool ValidateThrowing(NonValidated.MethodTable methodTable) + { + // For non-generic classes, we can rely on comparing + // object->methodtable->class->methodtable + // to + // object->methodtable + // + // However, for generic instantiation this does not work. There we must + // compare + // + // object->methodtable->class->methodtable->class + // to + // object->methodtable->class + TargetPointer eeClassPtr = GetClassThrowing(methodTable); + if (eeClassPtr != TargetPointer.Null) + { + NonValidated.EEClass eeClass = NonValidated.GetEEClassData(_target, eeClassPtr); + TargetPointer methodTablePtrFromClass = eeClass.MethodTable; + if (methodTable.Address == methodTablePtrFromClass) + { + return true; + } + if (methodTable.Flags.HasInstantiation || methodTable.Flags.IsArray) + { + NonValidated.MethodTable methodTableFromClass = NonValidated.GetMethodTableData(_target, methodTablePtrFromClass); + TargetPointer classFromMethodTable = GetClassThrowing(methodTableFromClass); + return classFromMethodTable == eeClassPtr; + } + } + return false; + } + + private bool ValidateMethodTableAdHoc(NonValidated.MethodTable methodTable) + { + // ad-hoc checks; add more here as needed + if (!methodTable.Flags.IsInterface && !methodTable.Flags.IsString) + { + if (methodTable.Flags.BaseSize == 0 || !_target.IsAlignedToPointerSize(methodTable.Flags.BaseSize)) + { + return false; + } + } + return true; + } + + internal static EEClassOrCanonMTBits GetEEClassOrCanonMTBits(TargetPointer eeClassOrCanonMTPtr) + { + return (EEClassOrCanonMTBits)(eeClassOrCanonMTPtr & (ulong)EEClassOrCanonMTBits.Mask); + } + private TargetPointer GetClassThrowing(NonValidated.MethodTable methodTable) + { + TargetPointer eeClassOrCanonMT = methodTable.EEClassOrCanonMT; + + if (GetEEClassOrCanonMTBits(eeClassOrCanonMT) == EEClassOrCanonMTBits.EEClass) + { + return methodTable.EEClass; + } + else + { + TargetPointer canonicalMethodTablePtr = methodTable.CanonMT; + NonValidated.MethodTable umt = NonValidated.GetMethodTableData(_target, canonicalMethodTablePtr); + return umt.EEClass; + } + } + + +} diff --git a/src/native/managed/cdacreader/src/Contracts/RuntimeTypeSystem_1.cs b/src/native/managed/cdacreader/src/Contracts/RuntimeTypeSystem_1.cs new file mode 100644 index 0000000000000..da2ea6c8b01b3 --- /dev/null +++ b/src/native/managed/cdacreader/src/Contracts/RuntimeTypeSystem_1.cs @@ -0,0 +1,157 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using System.Reflection.Metadata.Ecma335; + +namespace Microsoft.Diagnostics.DataContractReader.Contracts; + + + +internal partial struct RuntimeTypeSystem_1 : IRuntimeTypeSystem +{ + private readonly Target _target; + private readonly TargetPointer _freeObjectMethodTablePointer; + + // TODO(cdac): we mutate this dictionary - copies of the RuntimeTypeSystem_1 struct share this instance. + // If we need to invalidate our view of memory, we should clear this dictionary. + private readonly Dictionary _methodTables = new(); + + + internal struct MethodTable + { + internal MethodTableFlags Flags { get; } + internal ushort NumInterfaces { get; } + internal ushort NumVirtuals { get; } + internal TargetPointer ParentMethodTable { get; } + internal TargetPointer Module { get; } + internal TargetPointer EEClassOrCanonMT { get; } + internal MethodTable(Data.MethodTable data) + { + Flags = new MethodTableFlags + { + MTFlags = data.MTFlags, + MTFlags2 = data.MTFlags2, + BaseSize = data.BaseSize, + }; + NumInterfaces = data.NumInterfaces; + NumVirtuals = data.NumVirtuals; + EEClassOrCanonMT = data.EEClassOrCanonMT; + Module = data.Module; + ParentMethodTable = data.ParentMethodTable; + } + } + + // Low order bit of EEClassOrCanonMT. + // See MethodTable::LowBits UNION_EECLASS / UNION_METHODABLE + [Flags] + internal enum EEClassOrCanonMTBits + { + EEClass = 0, + CanonMT = 1, + Mask = 1, + } + + internal RuntimeTypeSystem_1(Target target, TargetPointer freeObjectMethodTablePointer) + { + _target = target; + _freeObjectMethodTablePointer = freeObjectMethodTablePointer; + } + + internal TargetPointer FreeObjectMethodTablePointer => _freeObjectMethodTablePointer; + + + public MethodTableHandle GetMethodTableHandle(TargetPointer methodTablePointer) + { + // if we already validated this address, return a handle + if (_methodTables.ContainsKey(methodTablePointer)) + { + return new MethodTableHandle(methodTablePointer); + } + // Check if we cached the underlying data already + if (_target.ProcessedData.TryGet(methodTablePointer, out Data.MethodTable? methodTableData)) + { + // we already cached the data, we must have validated the address, create the representation struct for our use + MethodTable trustedMethodTable = new MethodTable(methodTableData); + _ = _methodTables.TryAdd(methodTablePointer, trustedMethodTable); + return new MethodTableHandle(methodTablePointer); + } + + // If it's the free object method table, we trust it to be valid + if (methodTablePointer == FreeObjectMethodTablePointer) + { + Data.MethodTable freeObjectMethodTableData = _target.ProcessedData.GetOrAdd(methodTablePointer); + MethodTable trustedMethodTable = new MethodTable(freeObjectMethodTableData); + _ = _methodTables.TryAdd(methodTablePointer, trustedMethodTable); + return new MethodTableHandle(methodTablePointer); + } + + // Otherwse, get ready to validate + NonValidated.MethodTable nonvalidatedMethodTable = NonValidated.GetMethodTableData(_target, methodTablePointer); + + if (!ValidateMethodTablePointer(nonvalidatedMethodTable)) + { + throw new InvalidOperationException("Invalid method table pointer"); + } + // ok, we validated it, cache the data and add the MethodTable_1 struct to the dictionary + Data.MethodTable trustedMethodTableData = _target.ProcessedData.GetOrAdd(methodTablePointer); + MethodTable trustedMethodTableF = new MethodTable(trustedMethodTableData); + _ = _methodTables.TryAdd(methodTablePointer, trustedMethodTableF); + return new MethodTableHandle(methodTablePointer); + } + + + public uint GetBaseSize(MethodTableHandle methodTableHandle) => _methodTables[methodTableHandle.Address].Flags.BaseSize; + + public uint GetComponentSize(MethodTableHandle methodTableHandle) => _methodTables[methodTableHandle.Address].Flags.ComponentSize; + + private TargetPointer GetClassPointer(MethodTableHandle methodTableHandle) + { + MethodTable methodTable = _methodTables[methodTableHandle.Address]; + switch (GetEEClassOrCanonMTBits(methodTable.EEClassOrCanonMT)) + { + case EEClassOrCanonMTBits.EEClass: + return methodTable.EEClassOrCanonMT; + case EEClassOrCanonMTBits.CanonMT: + TargetPointer canonMTPtr = new TargetPointer((ulong)methodTable.EEClassOrCanonMT & ~(ulong)RuntimeTypeSystem_1.EEClassOrCanonMTBits.Mask); + MethodTableHandle canonMTHandle = GetMethodTableHandle(canonMTPtr); + MethodTable canonMT = _methodTables[canonMTHandle.Address]; + return canonMT.EEClassOrCanonMT; // canonical method table EEClassOrCanonMT is always EEClass + default: + throw new InvalidOperationException(); + } + } + + // only called on validated method tables, so we don't need to re-validate the EEClass + private Data.EEClass GetClassData(MethodTableHandle methodTableHandle) + { + TargetPointer clsPtr = GetClassPointer(methodTableHandle); + return _target.ProcessedData.GetOrAdd(clsPtr); + } + + public TargetPointer GetCanonicalMethodTable(MethodTableHandle methodTableHandle) => GetClassData(methodTableHandle).MethodTable; + + public TargetPointer GetModule(MethodTableHandle methodTableHandle) => _methodTables[methodTableHandle.Address].Module; + public TargetPointer GetParentMethodTable(MethodTableHandle methodTableHandle) => _methodTables[methodTableHandle.Address].ParentMethodTable; + + public bool IsFreeObjectMethodTable(MethodTableHandle methodTableHandle) => FreeObjectMethodTablePointer == methodTableHandle.Address; + + public bool IsString(MethodTableHandle methodTableHandle) => _methodTables[methodTableHandle.Address].Flags.IsString; + public bool ContainsGCPointers(MethodTableHandle methodTableHandle) => _methodTables[methodTableHandle.Address].Flags.ContainsGCPointers; + + public uint GetTypeDefToken(MethodTableHandle methodTableHandle) + { + MethodTable methodTable = _methodTables[methodTableHandle.Address]; + return (uint)(methodTable.Flags.GetTypeDefRid() | ((int)TableIndex.TypeDef << 24)); + } + + public ushort GetNumMethods(MethodTableHandle methodTableHandle) => GetClassData(methodTableHandle).NumMethods; + + public ushort GetNumInterfaces(MethodTableHandle methodTableHandle) => _methodTables[methodTableHandle.Address].NumInterfaces; + + public uint GetTypeDefTypeAttributes(MethodTableHandle methodTableHandle) => GetClassData(methodTableHandle).CorTypeAttr; + + public bool IsDynamicStatics(MethodTableHandle methodTableHandle) => _methodTables[methodTableHandle.Address].Flags.IsDynamicStatics; + +} diff --git a/src/native/managed/cdacreader/src/Data/EEClass.cs b/src/native/managed/cdacreader/src/Data/EEClass.cs new file mode 100644 index 0000000000000..e697b3f40756c --- /dev/null +++ b/src/native/managed/cdacreader/src/Data/EEClass.cs @@ -0,0 +1,23 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; + +namespace Microsoft.Diagnostics.DataContractReader.Data; + +public sealed class EEClass : IData +{ + static EEClass IData.Create(Target target, TargetPointer address) => new EEClass(target, address); + public EEClass(Target target, TargetPointer address) + { + Target.TypeInfo type = target.GetTypeInfo(DataType.EEClass); + + MethodTable = target.ReadPointer(address + (ulong)type.Fields[nameof(MethodTable)].Offset); + NumMethods = target.Read(address + (ulong)type.Fields[nameof(NumMethods)].Offset); + CorTypeAttr = target.Read(address + (ulong)type.Fields[nameof(CorTypeAttr)].Offset); + } + + public TargetPointer MethodTable { get; init; } + public ushort NumMethods { get; init; } + public uint CorTypeAttr { get; init; } +} diff --git a/src/native/managed/cdacreader/src/Data/MethodTable.cs b/src/native/managed/cdacreader/src/Data/MethodTable.cs new file mode 100644 index 0000000000000..3319c6547f056 --- /dev/null +++ b/src/native/managed/cdacreader/src/Data/MethodTable.cs @@ -0,0 +1,33 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; + +namespace Microsoft.Diagnostics.DataContractReader.Data; + +internal sealed class MethodTable : IData +{ + static MethodTable IData.Create(Target target, TargetPointer address) => new MethodTable(target, address); + public MethodTable(Target target, TargetPointer address) + { + Target.TypeInfo type = target.GetTypeInfo(DataType.MethodTable); + + MTFlags = target.Read(address + (ulong)type.Fields[nameof(MTFlags)].Offset); + BaseSize = target.Read(address + (ulong)type.Fields[nameof(BaseSize)].Offset); + MTFlags2 = target.Read(address + (ulong)type.Fields[nameof(MTFlags2)].Offset); + EEClassOrCanonMT = target.ReadPointer(address + (ulong)type.Fields[nameof(EEClassOrCanonMT)].Offset); + Module = target.ReadPointer(address + (ulong)type.Fields[nameof(Module)].Offset); + ParentMethodTable = target.ReadPointer(address + (ulong)type.Fields[nameof(ParentMethodTable)].Offset); + NumInterfaces = target.Read(address + (ulong)type.Fields[nameof(NumInterfaces)].Offset); + NumVirtuals = target.Read(address + (ulong)type.Fields[nameof(NumVirtuals)].Offset); + } + + public uint MTFlags { get; init; } + public uint BaseSize { get; init; } + public uint MTFlags2 { get; init; } + public TargetPointer EEClassOrCanonMT { get; init; } + public TargetPointer Module { get; init; } + public TargetPointer ParentMethodTable { get; init; } + public ushort NumInterfaces { get; init; } + public ushort NumVirtuals { get; init; } +} diff --git a/src/native/managed/cdacreader/src/Data/MethodTableAuxiliaryData.cs b/src/native/managed/cdacreader/src/Data/MethodTableAuxiliaryData.cs new file mode 100644 index 0000000000000..bdc711a510473 --- /dev/null +++ b/src/native/managed/cdacreader/src/Data/MethodTableAuxiliaryData.cs @@ -0,0 +1,21 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; + +namespace Microsoft.Diagnostics.DataContractReader.Data; + +internal sealed class MethodTableAuxiliaryData : IData +{ + static MethodTableAuxiliaryData IData.Create(Target target, TargetPointer address) => new MethodTableAuxiliaryData(target, address); + + private MethodTableAuxiliaryData(Target target, TargetPointer address) + { + Target.TypeInfo type = target.GetTypeInfo(DataType.MethodTableAuxiliaryData); + + AuxFlags = target.Read(address + (ulong)type.Fields[nameof(AuxFlags)].Offset); + + } + + public uint AuxFlags { get; init; } +} diff --git a/src/native/managed/cdacreader/src/DataType.cs b/src/native/managed/cdacreader/src/DataType.cs index 94274d5f47a8b..338a544ab488c 100644 --- a/src/native/managed/cdacreader/src/DataType.cs +++ b/src/native/managed/cdacreader/src/DataType.cs @@ -24,5 +24,9 @@ public enum DataType ThreadStore, GCAllocContext, ExceptionInfo, - RuntimeThreadLocals + RuntimeThreadLocals, + + MethodTable, + EEClass, + MethodTableAuxiliaryData, } diff --git a/src/native/managed/cdacreader/src/Legacy/ISOSDacInterface.cs b/src/native/managed/cdacreader/src/Legacy/ISOSDacInterface.cs index bd0d20e65b77c..6a806e631731c 100644 --- a/src/native/managed/cdacreader/src/Legacy/ISOSDacInterface.cs +++ b/src/native/managed/cdacreader/src/Legacy/ISOSDacInterface.cs @@ -42,6 +42,25 @@ internal struct DacpThreadData public ulong lastThrownObjectHandle; public ulong nextThread; } + +internal struct DacpMethodTableData +{ + public int bIsFree; // everything else is NULL if this is true. + public ulong module; + public ulong klass; + public ulong parentMethodTable; + public ushort wNumInterfaces; + public ushort wNumMethods; + public ushort wNumVtableSlots; + public ushort wNumVirtuals; + public uint baseSize; + public uint componentSize; + public uint /*mdTypeDef*/ cl; // Metadata token + public uint dwAttrClass; // cached metadata + public int bIsShared; // Always false, preserved for backward compatibility + public int bIsDynamic; + public int bContainsGCPointers; +} #pragma warning restore CS0649 // Field is never assigned to, and will always have its default value [GeneratedComInterface] @@ -139,7 +158,7 @@ internal unsafe partial interface ISOSDacInterface [PreserveSig] int GetMethodTableName(ulong mt, uint count, char* mtName, uint* pNeeded); [PreserveSig] - int GetMethodTableData(ulong mt, /*struct DacpMethodTableData*/ void* data); + int GetMethodTableData(ulong mt, DacpMethodTableData* data); [PreserveSig] int GetMethodTableSlot(ulong mt, uint slot, ulong* value); [PreserveSig] diff --git a/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs b/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs index 8dc36985a9add..240d98bf7d295 100644 --- a/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs +++ b/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; +using System.Diagnostics.Contracts; using System.Runtime.InteropServices; using System.Runtime.InteropServices.Marshalling; @@ -81,9 +82,71 @@ public int GetBreakingChangeVersion() public unsafe int GetMethodDescPtrFromFrame(ulong frameAddr, ulong* ppMD) => HResults.E_NOTIMPL; public unsafe int GetMethodDescPtrFromIP(ulong ip, ulong* ppMD) => HResults.E_NOTIMPL; public unsafe int GetMethodDescTransparencyData(ulong methodDesc, void* data) => HResults.E_NOTIMPL; - public unsafe int GetMethodTableData(ulong mt, void* data) => HResults.E_NOTIMPL; + public unsafe int GetMethodTableData(ulong mt, DacpMethodTableData* data) + { + if (mt == 0 || data == null) + return HResults.E_INVALIDARG; + + try + { + Contracts.IRuntimeTypeSystem contract = _target.Contracts.RuntimeTypeSystem; + Contracts.MethodTableHandle methodTable = contract.GetMethodTableHandle(mt); + + DacpMethodTableData result = default; + result.baseSize = contract.GetBaseSize(methodTable); + // [compat] SOS DAC APIs added this base size adjustment for strings + // due to: "2008/09/25 Title: New implementation of StringBuilder and improvements in String class" + // which changed StringBuilder not to use a String as an internal buffer and in the process + // changed the String internals so that StringObject::GetBaseSize() now includes the nul terminator character, + // which is apparently not expected by SOS. + if (contract.IsString(methodTable)) + result.baseSize -= sizeof(char); + + result.componentSize = contract.GetComponentSize(methodTable); + bool isFreeObjectMT = contract.IsFreeObjectMethodTable(methodTable); + result.bIsFree = isFreeObjectMT ? 1 : 0; + if (!isFreeObjectMT) + { + result.module = contract.GetModule(methodTable); + // Note: really the canonical method table, not the EEClass, which we don't expose + result.klass = contract.GetCanonicalMethodTable(methodTable); + result.parentMethodTable = contract.GetParentMethodTable(methodTable); + result.wNumInterfaces = contract.GetNumInterfaces(methodTable); + result.wNumMethods = contract.GetNumMethods(methodTable); + result.wNumVtableSlots = 0; // always return 0 since .NET 9 + result.wNumVirtuals = 0; // always return 0 since .NET 9 + result.cl = contract.GetTypeDefToken(methodTable); + result.dwAttrClass = contract.GetTypeDefTypeAttributes(methodTable); + result.bContainsGCPointers = contract.ContainsGCPointers(methodTable) ? 1 : 0; + result.bIsShared = 0; + result.bIsDynamic = contract.IsDynamicStatics(methodTable) ? 1 : 0; + } + *data = result; + return HResults.S_OK; + } + catch (Exception ex) + { + return ex.HResult; + } + } public unsafe int GetMethodTableFieldData(ulong mt, void* data) => HResults.E_NOTIMPL; - public unsafe int GetMethodTableForEEClass(ulong eeClass, ulong* value) => HResults.E_NOTIMPL; + public unsafe int GetMethodTableForEEClass(ulong eeClassReallyCanonMT, ulong* value) + { + if (eeClassReallyCanonMT == 0 || value == null) + return HResults.E_INVALIDARG; + + try + { + Contracts.IRuntimeTypeSystem contract = _target.Contracts.RuntimeTypeSystem; + Contracts.MethodTableHandle methodTableHandle = contract.GetMethodTableHandle(eeClassReallyCanonMT); + *value = methodTableHandle.Address; + return HResults.S_OK; + } + catch (Exception ex) + { + return ex.HResult; + } + } public unsafe int GetMethodTableName(ulong mt, uint count, char* mtName, uint* pNeeded) => HResults.E_NOTIMPL; public unsafe int GetMethodTableSlot(ulong mt, uint slot, ulong* value) => HResults.E_NOTIMPL; public unsafe int GetMethodTableTransparencyData(ulong mt, void* data) => HResults.E_NOTIMPL; diff --git a/src/native/managed/cdacreader/src/Target.cs b/src/native/managed/cdacreader/src/Target.cs index d42f581581884..06b201d3957d5 100644 --- a/src/native/managed/cdacreader/src/Target.cs +++ b/src/native/managed/cdacreader/src/Target.cs @@ -11,7 +11,7 @@ namespace Microsoft.Diagnostics.DataContractReader; -public readonly struct TargetPointer +public readonly struct TargetPointer : IEquatable { public static TargetPointer Null = new(0); @@ -20,6 +20,14 @@ public readonly struct TargetPointer public static implicit operator ulong(TargetPointer p) => p.Value; public static implicit operator TargetPointer(ulong v) => new TargetPointer(v); + + public static bool operator ==(TargetPointer left, TargetPointer right) => left.Value == right.Value; + public static bool operator !=(TargetPointer left, TargetPointer right) => left.Value != right.Value; + + public override bool Equals(object? obj) => obj is TargetPointer pointer && Equals(pointer); + public bool Equals(TargetPointer other) => Value == other.Value; + + public override int GetHashCode() => Value.GetHashCode(); } public readonly struct TargetNUInt @@ -309,6 +317,16 @@ private static bool TryReadNUInt(ulong address, Configuration config, Reader rea return false; } + public static bool IsAligned(ulong value, int alignment) + => (value & (ulong)(alignment - 1)) == 0; + + public bool IsAlignedToPointerSize(uint value) + => IsAligned(value, _config.PointerSize); + public bool IsAlignedToPointerSize(ulong value) + => IsAligned(value, _config.PointerSize); + public bool IsAlignedToPointerSize(TargetPointer pointer) + => IsAligned(pointer.Value, _config.PointerSize); + public T ReadGlobal(string name) where T : struct, INumber => ReadGlobal(name, out _); @@ -384,7 +402,7 @@ public T GetOrAdd(TargetPointer address) where T : IData return result!; } - private bool TryGet(ulong address, [NotNullWhen(true)] out T? data) + public bool TryGet(ulong address, [NotNullWhen(true)] out T? data) { data = default; if (!_readDataByAddress.TryGetValue((address, typeof(T)), out object? dataObj)) diff --git a/src/native/managed/cdacreader/src/cdacreader.csproj b/src/native/managed/cdacreader/src/cdacreader.csproj index 20ecd197c7046..1c973cac51a57 100644 --- a/src/native/managed/cdacreader/src/cdacreader.csproj +++ b/src/native/managed/cdacreader/src/cdacreader.csproj @@ -15,6 +15,9 @@ + + + diff --git a/src/native/managed/cdacreader/tests/MethodTableTests.cs b/src/native/managed/cdacreader/tests/MethodTableTests.cs new file mode 100644 index 0000000000000..dd451fa075f6a --- /dev/null +++ b/src/native/managed/cdacreader/tests/MethodTableTests.cs @@ -0,0 +1,376 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Text; +using Microsoft.Diagnostics.DataContractReader.Contracts; +using Xunit; + +namespace Microsoft.Diagnostics.DataContractReader.UnitTests; + +public unsafe class MethodTableTests +{ + const ulong TestFreeObjectMethodTableGlobalAddress = 0x00000000_7a0000a0; + const ulong TestFreeObjectMethodTableAddress = 0x00000000_7a0000a8; + + private static readonly Target.TypeInfo MethodTableTypeInfo = new() + { + Fields = { + { nameof(Data.MethodTable.MTFlags), new() { Offset = 4, Type = DataType.uint32}}, + { nameof(Data.MethodTable.BaseSize), new() { Offset = 8, Type = DataType.uint32}}, + { nameof(Data.MethodTable.MTFlags2), new() { Offset = 12, Type = DataType.uint32}}, + { nameof(Data.MethodTable.EEClassOrCanonMT), new () { Offset = 16, Type = DataType.nuint}}, + { nameof(Data.MethodTable.Module), new () { Offset = 24, Type = DataType.pointer}}, + { nameof(Data.MethodTable.ParentMethodTable), new () { Offset = 40, Type = DataType.pointer}}, + { nameof(Data.MethodTable.NumInterfaces), new () { Offset = 48, Type = DataType.uint16}}, + { nameof(Data.MethodTable.NumVirtuals), new () { Offset = 50, Type = DataType.uint16}}, + } + }; + + private static readonly Target.TypeInfo EEClassTypeInfo = new Target.TypeInfo() + { + Fields = { + { nameof (Data.EEClass.MethodTable), new () { Offset = 8, Type = DataType.pointer}}, + { nameof (Data.EEClass.CorTypeAttr), new () { Offset = 16, Type = DataType.uint32}}, + { nameof (Data.EEClass.NumMethods), new () { Offset = 20, Type = DataType.uint16}}, + } + }; + + private static readonly (DataType Type, Target.TypeInfo Info)[] RTSTypes = + [ + (DataType.MethodTable, MethodTableTypeInfo), + (DataType.EEClass, EEClassTypeInfo), + ]; + + + private static readonly (string Name, ulong Value, string? Type)[] RTSGlobals = + [ + (nameof(Constants.Globals.FreeObjectMethodTable), TestFreeObjectMethodTableGlobalAddress, null), + ]; + + private static MockMemorySpace.Builder AddFreeObjectMethodTable(TargetTestHelpers targetTestHelpers, MockMemorySpace.Builder builder) + { + MockMemorySpace.HeapFragment globalAddr = new() { Name = "Address of Free Object Method Table", Address = TestFreeObjectMethodTableGlobalAddress, Data = new byte[targetTestHelpers.PointerSize] }; + targetTestHelpers.WritePointer(globalAddr.Data, TestFreeObjectMethodTableAddress); + return builder.AddHeapFragments([ + globalAddr, + new () { Name = "Free Object Method Table", Address = TestFreeObjectMethodTableAddress, Data = new byte[targetTestHelpers.SizeOfTypeInfo(MethodTableTypeInfo)] } + ]); + } + + private static MockMemorySpace.Builder AddEEClass(TargetTestHelpers targetTestHelpers, MockMemorySpace.Builder builder, TargetPointer eeClassPtr, string name, TargetPointer canonMTPtr, uint attr, ushort numMethods) + { + MockMemorySpace.HeapFragment eeClassFragment = new() { Name = $"EEClass '{name}'", Address = eeClassPtr, Data = new byte[targetTestHelpers.SizeOfTypeInfo(EEClassTypeInfo)] }; + Span dest = eeClassFragment.Data; + targetTestHelpers.WritePointer(dest.Slice(EEClassTypeInfo.Fields[nameof(Data.EEClass.MethodTable)].Offset), canonMTPtr); + targetTestHelpers.Write(dest.Slice(EEClassTypeInfo.Fields[nameof(Data.EEClass.CorTypeAttr)].Offset), attr); + targetTestHelpers.Write(dest.Slice(EEClassTypeInfo.Fields[nameof(Data.EEClass.NumMethods)].Offset), numMethods); + return builder.AddHeapFragment(eeClassFragment); + + } + + private static MockMemorySpace.Builder AddMethodTable(TargetTestHelpers targetTestHelpers, MockMemorySpace.Builder builder, TargetPointer methodTablePtr, string name, TargetPointer eeClassOrCanonMT, uint mtflags, uint mtflags2, uint baseSize, + TargetPointer module, TargetPointer parentMethodTable, ushort numInterfaces, ushort numVirtuals) + { + MockMemorySpace.HeapFragment methodTableFragment = new() { Name = $"MethodTable '{name}'", Address = methodTablePtr, Data = new byte[targetTestHelpers.SizeOfTypeInfo(MethodTableTypeInfo)] }; + Span dest = methodTableFragment.Data; + targetTestHelpers.WritePointer(dest.Slice(MethodTableTypeInfo.Fields[nameof(Data.MethodTable.EEClassOrCanonMT)].Offset), eeClassOrCanonMT); + targetTestHelpers.Write(dest.Slice(MethodTableTypeInfo.Fields[nameof(Data.MethodTable.MTFlags)].Offset), mtflags); + targetTestHelpers.Write(dest.Slice(MethodTableTypeInfo.Fields[nameof(Data.MethodTable.MTFlags2)].Offset), mtflags2); + targetTestHelpers.Write(dest.Slice(MethodTableTypeInfo.Fields[nameof(Data.MethodTable.BaseSize)].Offset), baseSize); + targetTestHelpers.WritePointer(dest.Slice(MethodTableTypeInfo.Fields[nameof(Data.MethodTable.Module)].Offset), module); + targetTestHelpers.WritePointer(dest.Slice(MethodTableTypeInfo.Fields[nameof(Data.MethodTable.ParentMethodTable)].Offset), parentMethodTable); + targetTestHelpers.Write(dest.Slice(MethodTableTypeInfo.Fields[nameof(Data.MethodTable.NumInterfaces)].Offset), numInterfaces); + targetTestHelpers.Write(dest.Slice(MethodTableTypeInfo.Fields[nameof(Data.MethodTable.NumVirtuals)].Offset), numVirtuals); + + // TODO fill in the rest of the fields + return builder.AddHeapFragment(methodTableFragment); + } + + // a delegate for adding more heap fragments to the context builder + private delegate MockMemorySpace.Builder ConfigureContextBuilder(MockMemorySpace.Builder builder); + + private static void RTSContractHelper(MockTarget.Architecture arch, ConfigureContextBuilder configure, Action testCase) + { + TargetTestHelpers targetTestHelpers = new(arch); + string metadataTypesJson = TargetTestHelpers.MakeTypesJson(RTSTypes); + string metadataGlobalsJson = TargetTestHelpers.MakeGlobalsJson(RTSGlobals); + byte[] json = Encoding.UTF8.GetBytes($$""" + { + "version": 0, + "baseline": "empty", + "contracts": { + "{{nameof(Contracts.RuntimeTypeSystem)}}": 1 + }, + "types": { {{metadataTypesJson}} }, + "globals": { {{metadataGlobalsJson}} } + } + """); + Span descriptor = stackalloc byte[targetTestHelpers.ContractDescriptorSize]; + targetTestHelpers.ContractDescriptorFill(descriptor, json.Length, RTSGlobals.Length); + + int pointerSize = targetTestHelpers.PointerSize; + Span pointerData = stackalloc byte[RTSGlobals.Length * pointerSize]; + for (int i = 0; i < RTSGlobals.Length; i++) + { + var (_, value, _) = RTSGlobals[i]; + targetTestHelpers.WritePointer(pointerData.Slice(i * pointerSize), value); + } + + fixed (byte* jsonPtr = json) + { + MockMemorySpace.Builder builder = new(); + + builder = builder.SetDescriptor(descriptor) + .SetJson(json) + .SetPointerData(pointerData); + + builder = AddFreeObjectMethodTable(targetTestHelpers, builder); + + if (configure != null) + { + builder = configure(builder); + } + + using MockMemorySpace.ReadContext context = builder.Create(); + + bool success = MockMemorySpace.TryCreateTarget(&context, out Target? target); + Assert.True(success); + + testCase(target); + } + GC.KeepAlive(json); + } + + [Theory] + [ClassData(typeof(MockTarget.StdArch))] + public void HasRuntimeTypeSystemContract(MockTarget.Architecture arch) + { + RTSContractHelper(arch, default, (target) => + { + Contracts.IRuntimeTypeSystem metadataContract = target.Contracts.RuntimeTypeSystem; + Assert.NotNull(metadataContract); + Contracts.MethodTableHandle handle = metadataContract.GetMethodTableHandle(TestFreeObjectMethodTableAddress); + Assert.NotEqual(TargetPointer.Null, handle.Address); + Assert.True(metadataContract.IsFreeObjectMethodTable(handle)); + }); + } + + private static MockMemorySpace.Builder AddSystemObject(TargetTestHelpers targetTestHelpers, MockMemorySpace.Builder builder, TargetPointer systemObjectMethodTablePtr, TargetPointer systemObjectEEClassPtr) + { + System.Reflection.TypeAttributes typeAttributes = System.Reflection.TypeAttributes.Public | System.Reflection.TypeAttributes.Class; + const int numMethods = 8; // System.Object has 8 methods + const int numVirtuals = 3; // System.Object has 3 virtual methods + builder = AddEEClass(targetTestHelpers, builder, systemObjectEEClassPtr, "System.Object", systemObjectMethodTablePtr, attr: (uint)typeAttributes, numMethods: numMethods); + builder = AddMethodTable(targetTestHelpers, builder, systemObjectMethodTablePtr, "System.Object", systemObjectEEClassPtr, + mtflags: default, mtflags2: default, baseSize: targetTestHelpers.ObjectBaseSize, + module: TargetPointer.Null, parentMethodTable: TargetPointer.Null, numInterfaces: 0, numVirtuals: numVirtuals); + return builder; + } + + [Theory] + [ClassData(typeof(MockTarget.StdArch))] + public void ValidateSystemObjectMethodTable(MockTarget.Architecture arch) + { + const ulong SystemObjectMethodTableAddress = 0x00000000_7c000010; + const ulong SystemObjectEEClassAddress = 0x00000000_7c0000d0; + TargetPointer systemObjectMethodTablePtr = new TargetPointer(SystemObjectMethodTableAddress); + TargetPointer systemObjectEEClassPtr = new TargetPointer(SystemObjectEEClassAddress); + TargetTestHelpers targetTestHelpers = new(arch); + RTSContractHelper(arch, + (builder) => + { + builder = AddSystemObject(targetTestHelpers, builder, systemObjectMethodTablePtr, systemObjectEEClassPtr); + return builder; + }, + (target) => + { + Contracts.IRuntimeTypeSystem metadataContract = target.Contracts.RuntimeTypeSystem; + Assert.NotNull(metadataContract); + Contracts.MethodTableHandle systemObjectMethodTableHandle = metadataContract.GetMethodTableHandle(systemObjectMethodTablePtr); + Assert.Equal(systemObjectMethodTablePtr.Value, systemObjectMethodTableHandle.Address.Value); + Assert.False(metadataContract.IsFreeObjectMethodTable(systemObjectMethodTableHandle)); + }); + } + + [Theory] + [ClassData(typeof(MockTarget.StdArch))] + public void ValidateSystemStringMethodTable(MockTarget.Architecture arch) + { + const ulong SystemObjectMethodTableAddress = 0x00000000_7c000010; + const ulong SystemObjectEEClassAddress = 0x00000000_7c0000d0; + TargetPointer systemObjectMethodTablePtr = new TargetPointer(SystemObjectMethodTableAddress); + TargetPointer systemObjectEEClassPtr = new TargetPointer(SystemObjectEEClassAddress); + + const ulong SystemStringMethodTableAddress = 0x00000000_7c002010; + const ulong SystemStringEEClassAddress = 0x00000000_7c0020d0; + TargetPointer systemStringMethodTablePtr = new TargetPointer(SystemStringMethodTableAddress); + TargetPointer systemStringEEClassPtr = new TargetPointer(SystemStringEEClassAddress); + TargetTestHelpers targetTestHelpers = new(arch); + RTSContractHelper(arch, + (builder) => + { + builder = AddSystemObject(targetTestHelpers, builder, systemObjectMethodTablePtr, systemObjectEEClassPtr); + System.Reflection.TypeAttributes typeAttributes = System.Reflection.TypeAttributes.Public | System.Reflection.TypeAttributes.Class | System.Reflection.TypeAttributes.Sealed; + const int numMethods = 37; // Arbitrary. Not trying to exactly match the real System.String + const int numInterfaces = 8; // Arbitrary + const int numVirtuals = 3; // at least as many as System.Object + uint mtflags = (uint)RuntimeTypeSystem_1.WFLAGS_HIGH.HasComponentSize | /*componentSize: */2; + builder = AddEEClass(targetTestHelpers, builder, systemStringEEClassPtr, "System.String", systemStringMethodTablePtr, attr: (uint)typeAttributes, numMethods: numMethods); + builder = AddMethodTable(targetTestHelpers, builder, systemStringMethodTablePtr, "System.String", systemStringEEClassPtr, + mtflags: mtflags, mtflags2: default, baseSize: targetTestHelpers.StringBaseSize, + module: TargetPointer.Null, parentMethodTable: systemObjectMethodTablePtr, numInterfaces: numInterfaces, numVirtuals: numVirtuals); + return builder; + }, + (target) => + { + Contracts.IRuntimeTypeSystem metadataContract = target.Contracts.RuntimeTypeSystem; + Assert.NotNull(metadataContract); + Contracts.MethodTableHandle systemStringMethodTableHandle = metadataContract.GetMethodTableHandle(systemStringMethodTablePtr); + Assert.Equal(systemStringMethodTablePtr.Value, systemStringMethodTableHandle.Address.Value); + Assert.False(metadataContract.IsFreeObjectMethodTable(systemStringMethodTableHandle)); + Assert.True(metadataContract.IsString(systemStringMethodTableHandle)); + }); + } + + [Theory] + [ClassData(typeof(MockTarget.StdArch))] + public void MethodTableEEClassInvalidThrows(MockTarget.Architecture arch) + { + TargetTestHelpers targetTestHelpers = new(arch); + const ulong SystemObjectMethodTableAddress = 0x00000000_7c000010; + const ulong SystemObjectEEClassAddress = 0x00000000_7c0000d0; + TargetPointer systemObjectMethodTablePtr = new TargetPointer(SystemObjectMethodTableAddress); + TargetPointer systemObjectEEClassPtr = new TargetPointer(SystemObjectEEClassAddress); + + const ulong badMethodTableAddress = 0x00000000_4a000100; // place a normal-looking MethodTable here + const ulong badMethodTableEEClassAddress = 0x00000010_afafafafa0; // bad address + TargetPointer badMethodTablePtr = new TargetPointer(badMethodTableAddress); + TargetPointer badMethodTableEEClassPtr = new TargetPointer(badMethodTableEEClassAddress); + RTSContractHelper(arch, + (builder) => + { + builder = AddSystemObject(targetTestHelpers, builder, systemObjectMethodTablePtr, systemObjectEEClassPtr); + builder = AddMethodTable(targetTestHelpers, builder, badMethodTablePtr, "Bad MethodTable", badMethodTableEEClassPtr, mtflags: default, mtflags2: default, baseSize: targetTestHelpers.ObjectBaseSize, module: TargetPointer.Null, parentMethodTable: systemObjectMethodTablePtr, numInterfaces: 0, numVirtuals: 3); + return builder; + }, + (target) => + { + Contracts.IRuntimeTypeSystem metadataContract = target.Contracts.RuntimeTypeSystem; + Assert.NotNull(metadataContract); + Assert.Throws(() => metadataContract.GetMethodTableHandle(badMethodTablePtr)); + }); + } + + [Theory] + [ClassData(typeof(MockTarget.StdArch))] + public void ValidateGenericInstMethodTable(MockTarget.Architecture arch) + { + TargetTestHelpers targetTestHelpers = new(arch); + const ulong SystemObjectMethodTableAddress = 0x00000000_7c000010; + const ulong SystemObjectEEClassAddress = 0x00000000_7c0000d0; + TargetPointer systemObjectMethodTablePtr = new TargetPointer(SystemObjectMethodTableAddress); + TargetPointer systemObjectEEClassPtr = new TargetPointer(SystemObjectEEClassAddress); + + const ulong genericDefinitionMethodTableAddress = 0x00000000_5d004040; + const ulong genericDefinitionEEClassAddress = 0x00000000_5d0040c0; + TargetPointer genericDefinitionMethodTablePtr = new TargetPointer(genericDefinitionMethodTableAddress); + TargetPointer genericDefinitionEEClassPtr = new TargetPointer(genericDefinitionEEClassAddress); + + const ulong genericInstanceMethodTableAddress = 0x00000000_330000a0; + TargetPointer genericInstanceMethodTablePtr = new TargetPointer(genericInstanceMethodTableAddress); + + const int numMethods = 17; + + RTSContractHelper(arch, + (builder) => + { + builder = AddSystemObject(targetTestHelpers, builder, systemObjectMethodTablePtr, systemObjectEEClassPtr); + + System.Reflection.TypeAttributes typeAttributes = System.Reflection.TypeAttributes.Public | System.Reflection.TypeAttributes.Class; + const int numInterfaces = 0; + const int numVirtuals = 3; + const uint gtd_mtflags = 0x00000030; // TODO: GenericsMask_TypicalInst + builder = AddEEClass(targetTestHelpers, builder, genericDefinitionEEClassPtr, "EEClass GenericDefinition", genericDefinitionMethodTablePtr, attr: (uint)typeAttributes, numMethods: numMethods); + builder = AddMethodTable(targetTestHelpers, builder, genericDefinitionMethodTablePtr, "MethodTable GenericDefinition", genericDefinitionEEClassPtr, + mtflags: gtd_mtflags, mtflags2: default, baseSize: targetTestHelpers.ObjectBaseSize, + module: TargetPointer.Null, parentMethodTable: systemObjectMethodTablePtr, numInterfaces: numInterfaces, numVirtuals: numVirtuals); + + const uint ginst_mtflags = 0x00000010; // TODO: GenericsMask_GenericInst + TargetPointer ginstCanonMT = new TargetPointer(genericDefinitionMethodTablePtr.Value | (ulong)1); + builder = AddMethodTable(targetTestHelpers, builder, genericInstanceMethodTablePtr, "MethodTable GenericInstance", eeClassOrCanonMT: ginstCanonMT, + mtflags: ginst_mtflags, mtflags2: default, baseSize: targetTestHelpers.ObjectBaseSize, + module: TargetPointer.Null, parentMethodTable: genericDefinitionMethodTablePtr, numInterfaces: numInterfaces, numVirtuals: numVirtuals); + + return builder; + }, + (target) => + { + Contracts.IRuntimeTypeSystem metadataContract = target.Contracts.RuntimeTypeSystem; + Assert.NotNull(metadataContract); + Contracts.MethodTableHandle genericInstanceMethodTableHandle = metadataContract.GetMethodTableHandle(genericInstanceMethodTablePtr); + Assert.Equal(genericInstanceMethodTablePtr.Value, genericInstanceMethodTableHandle.Address.Value); + Assert.False(metadataContract.IsFreeObjectMethodTable(genericInstanceMethodTableHandle)); + Assert.False(metadataContract.IsString(genericInstanceMethodTableHandle)); + Assert.Equal(numMethods, metadataContract.GetNumMethods(genericInstanceMethodTableHandle)); + }); + } + + [Theory] + [ClassData(typeof(MockTarget.StdArch))] + public void ValidateArrayInstMethodTable(MockTarget.Architecture arch) + { + TargetTestHelpers targetTestHelpers = new(arch); + const ulong SystemObjectMethodTableAddress = 0x00000000_7c000010; + const ulong SystemObjectEEClassAddress = 0x00000000_7c0000d0; + TargetPointer systemObjectMethodTablePtr = new TargetPointer(SystemObjectMethodTableAddress); + TargetPointer systemObjectEEClassPtr = new TargetPointer(SystemObjectEEClassAddress); + + const ulong SystemArrayMethodTableAddress = 0x00000000_7c00a010; + const ulong SystemArrayEEClassAddress = 0x00000000_7c00a0d0; + TargetPointer systemArrayMethodTablePtr = new TargetPointer(SystemArrayMethodTableAddress); + TargetPointer systemArrayEEClassPtr = new TargetPointer(SystemArrayEEClassAddress); + + const ulong arrayInstanceMethodTableAddress = 0x00000000_330000a0; + const ulong arrayInstanceEEClassAddress = 0x00000000_330001d0; + TargetPointer arrayInstanceMethodTablePtr = new TargetPointer(arrayInstanceMethodTableAddress); + TargetPointer arrayInstanceEEClassPtr = new TargetPointer(arrayInstanceEEClassAddress); + + const uint arrayInstanceComponentSize = 392; + + RTSContractHelper(arch, + (builder) => + { + builder = AddSystemObject(targetTestHelpers, builder, systemObjectMethodTablePtr, systemObjectEEClassPtr); + const ushort systemArrayNumInterfaces = 4; + const ushort systemArrayNumMethods = 37; // Arbitrary. Not trying to exactly match the real System.Array + const uint systemArrayCorTypeAttr = (uint)(System.Reflection.TypeAttributes.Public | System.Reflection.TypeAttributes.Class); + + builder = AddEEClass(targetTestHelpers, builder, systemArrayEEClassPtr, "EEClass System.Array", systemArrayMethodTablePtr, attr: systemArrayCorTypeAttr, numMethods: systemArrayNumMethods); + builder = AddMethodTable(targetTestHelpers, builder, systemArrayMethodTablePtr, "MethodTable System.Array", systemArrayEEClassPtr, + mtflags: default, mtflags2: default, baseSize: targetTestHelpers.ObjectBaseSize, + module: TargetPointer.Null, parentMethodTable: systemObjectMethodTablePtr, numInterfaces: systemArrayNumInterfaces, numVirtuals: 3); + + const uint arrayInst_mtflags = (uint)(RuntimeTypeSystem_1.WFLAGS_HIGH.HasComponentSize | RuntimeTypeSystem_1.WFLAGS_HIGH.Category_Array) | arrayInstanceComponentSize; + const uint arrayInstCorTypeAttr = (uint)(System.Reflection.TypeAttributes.Public | System.Reflection.TypeAttributes.Class | System.Reflection.TypeAttributes.Sealed); + + builder = AddEEClass(targetTestHelpers, builder, arrayInstanceEEClassPtr, "EEClass ArrayInstance", arrayInstanceMethodTablePtr, attr: arrayInstCorTypeAttr, numMethods: systemArrayNumMethods); + builder = AddMethodTable(targetTestHelpers, builder, arrayInstanceMethodTablePtr, "MethodTable ArrayInstance", arrayInstanceEEClassPtr, + mtflags: arrayInst_mtflags, mtflags2: default, baseSize: targetTestHelpers.ObjectBaseSize, + module: TargetPointer.Null, parentMethodTable: systemArrayMethodTablePtr, numInterfaces: systemArrayNumInterfaces, numVirtuals: 3); + + return builder; + }, + (target) => + { + Contracts.IRuntimeTypeSystem metadataContract = target.Contracts.RuntimeTypeSystem; + Assert.NotNull(metadataContract); + Contracts.MethodTableHandle arrayInstanceMethodTableHandle = metadataContract.GetMethodTableHandle(arrayInstanceMethodTablePtr); + Assert.Equal(arrayInstanceMethodTablePtr.Value, arrayInstanceMethodTableHandle.Address.Value); + Assert.False(metadataContract.IsFreeObjectMethodTable(arrayInstanceMethodTableHandle)); + Assert.False(metadataContract.IsString(arrayInstanceMethodTableHandle)); + Assert.Equal(arrayInstanceComponentSize, metadataContract.GetComponentSize(arrayInstanceMethodTableHandle)); + }); + + } +} diff --git a/src/native/managed/cdacreader/tests/MockMemorySpace.cs b/src/native/managed/cdacreader/tests/MockMemorySpace.cs new file mode 100644 index 0000000000000..c74771a7f5b5c --- /dev/null +++ b/src/native/managed/cdacreader/tests/MockMemorySpace.cs @@ -0,0 +1,257 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace Microsoft.Diagnostics.DataContractReader.UnitTests; + +/// +/// Helper for creating a mock memory space for testing. +/// +/// +/// Use MockMemorySpace.CreateContext to create a mostly empty context for reading from the target. +/// Use MockMemorySpace.ContextBuilder to create a context with additional MockMemorySpace.HeapFragment data. +/// +/// +/// All the spans should be stackalloc or pinned while the context is being used. +/// +internal unsafe static class MockMemorySpace +{ + internal const ulong ContractDescriptorAddr = 0xaaaaaaaa; + internal const uint JsonDescriptorAddr = 0xdddddddd; + internal const uint ContractPointerDataAddr = 0xeeeeeeee; + + + internal struct HeapFragment + { + public ulong Address; + public byte[] Data; + public string? Name; + } + + /// + /// Helper to populate a virtual memory space for reading from a target. + /// + /// + /// All the spans should be stackalloc or pinned while the context is being used. + /// + internal unsafe ref struct Builder + { + private bool _created = false; + private byte* _descriptor = null; + private int _descriptorLength = 0; + private byte* _json = null; + private int _jsonLength = 0; + private byte* _pointerData = null; + private int _pointerDataLength = 0; + private List _heapFragments = new(); + + public Builder() + { + + } + + public Builder SetDescriptor(scoped ReadOnlySpan descriptor) + { + if (_created) + throw new InvalidOperationException("Context already created"); + _descriptor = (byte*)Unsafe.AsPointer(ref MemoryMarshal.GetReference(descriptor)); + _descriptorLength = descriptor.Length; + return this; + } + + public Builder SetJson(scoped ReadOnlySpan json) + { + if (_created) + throw new InvalidOperationException("Context already created"); + _json = (byte*)Unsafe.AsPointer(ref MemoryMarshal.GetReference(json)); + _jsonLength = json.Length; + return this; + } + + public Builder SetPointerData(scoped ReadOnlySpan pointerData) + { + if (_created) + throw new InvalidOperationException("Context already created"); + if (pointerData.Length >= 0) + { + _pointerData = (byte*)Unsafe.AsPointer(ref MemoryMarshal.GetReference(pointerData)); + _pointerDataLength = pointerData.Length; + } + return this; + } + + public Builder AddHeapFragment(HeapFragment fragment) + { + if (_created) + throw new InvalidOperationException("Context already created"); + if (fragment.Data is null || fragment.Data.Length == 0) + throw new InvalidOperationException($"Fragment '{fragment.Name}' data is empty"); + if (!FragmentFits(fragment)) + throw new InvalidOperationException($"Fragment '{fragment.Name}' does not fit in the address space"); + _heapFragments.Add(fragment); + return this; + } + + public Builder AddHeapFragments(IEnumerable fragments) + { + foreach (var f in fragments) + { + // add fragments one at a time to check for overlaps + AddHeapFragment(f); + } + return this; + } + + public ReadContext Create() + { + if (_created) + throw new InvalidOperationException("Context already created"); + GCHandle fragmentReaderHandle = default; ; + if (_heapFragments.Count > 0) + { + fragmentReaderHandle = GCHandle.Alloc(new HeapFragmentReader(_heapFragments)); + } + ReadContext context = new ReadContext + { + ContractDescriptor = _descriptor, + ContractDescriptorLength = _descriptorLength, + JsonDescriptor = _json, + JsonDescriptorLength = _jsonLength, + PointerData = _pointerData, + PointerDataLength = _pointerDataLength, + HeapFragmentReader = GCHandle.ToIntPtr(fragmentReaderHandle) + }; + _created = true; + return context; + } + + private bool FragmentFits(HeapFragment f) + { + foreach (var fragment in _heapFragments) + { + // f and fragment overlap if either: + // 1. f starts before fragment starts and ends after fragment starts + // 2. f starts before fragment ends + if ((f.Address <= fragment.Address && f.Address + (ulong)f.Data.Length > fragment.Address) || + (f.Address >= fragment.Address && f.Address < fragment.Address + (ulong)fragment.Data.Length)) + { + return false; + } + + } + return true; + } + } + + // Note: all the spans should be stackalloc or pinned. + public static ReadContext CreateContext(ReadOnlySpan descriptor, ReadOnlySpan json, ReadOnlySpan pointerData = default) + { + Builder builder = new Builder() + .SetJson(json) + .SetDescriptor(descriptor) + .SetPointerData(pointerData); + return builder.Create(); + } + + public static bool TryCreateTarget(ReadContext* context, out Target? target) + { + return Target.TryCreate(ContractDescriptorAddr, &ReadFromTarget, context, out target); + } + + [UnmanagedCallersOnly] + private static int ReadFromTarget(ulong address, byte* buffer, uint length, void* context) + { + ReadContext* readContext = (ReadContext*)context; + var span = new Span(buffer, (int)length); + + // Populate the span with the requested portion of the contract descriptor + if (address >= ContractDescriptorAddr && address <= ContractDescriptorAddr + (ulong)readContext->ContractDescriptorLength - length) + { + ulong offset = address - ContractDescriptorAddr; + new ReadOnlySpan(readContext->ContractDescriptor + offset, (int)length).CopyTo(span); + return 0; + } + + // Populate the span with the JSON descriptor - this assumes the product will read it all at once. + if (address == JsonDescriptorAddr) + { + new ReadOnlySpan(readContext->JsonDescriptor, readContext->JsonDescriptorLength).CopyTo(span); + return 0; + } + + // Populate the span with the requested portion of the pointer data + if (address >= ContractPointerDataAddr && address <= ContractPointerDataAddr + (ulong)readContext->PointerDataLength - length) + { + ulong offset = address - ContractPointerDataAddr; + new ReadOnlySpan(readContext->PointerData + offset, (int)length).CopyTo(span); + return 0; + } + + HeapFragmentReader? heapFragmentReader = GCHandle.FromIntPtr(readContext->HeapFragmentReader).Target as HeapFragmentReader; + if (heapFragmentReader is not null) + { + return heapFragmentReader.ReadFragment(address, span); + } + + return -1; + } + + // Used by ReadFromTarget to return the appropriate bytes + internal ref struct ReadContext : IDisposable + { + public byte* ContractDescriptor; + public int ContractDescriptorLength; + + public byte* JsonDescriptor; + public int JsonDescriptorLength; + + public byte* PointerData; + public int PointerDataLength; + + public IntPtr HeapFragmentReader; + + public void Dispose() + { + if (HeapFragmentReader != IntPtr.Zero) + { + GCHandle.FromIntPtr(HeapFragmentReader).Free(); + HeapFragmentReader = IntPtr.Zero; + } + } + } + + private class HeapFragmentReader + { + private readonly IReadOnlyList _fragments; + public HeapFragmentReader(IReadOnlyList fragments) + { + _fragments = fragments; + } + + public int ReadFragment(ulong address, Span buffer) + { + foreach (var fragment in _fragments) + { + if (address >= fragment.Address && address < fragment.Address + (ulong)fragment.Data.Length) + { + int offset = (int)(address - fragment.Address); + int availableLength = fragment.Data.Length - offset; + if (availableLength >= buffer.Length) + { + fragment.Data.AsSpan(offset, buffer.Length).CopyTo(buffer); + return 0; + } + else + { + throw new InvalidOperationException($"Not enough data in fragment at {fragment.Address:X} ('{fragment.Name}') to read {buffer.Length} bytes at {address:X} (only {availableLength} bytes available)"); + } + } + } + return -1; + } + } +} diff --git a/src/native/managed/cdacreader/tests/MockTarget.cs b/src/native/managed/cdacreader/tests/MockTarget.cs new file mode 100644 index 0000000000000..5ef528139b7d5 --- /dev/null +++ b/src/native/managed/cdacreader/tests/MockTarget.cs @@ -0,0 +1,41 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections.Generic; +using Xunit; + +namespace Microsoft.Diagnostics.DataContractReader.UnitTests; + +public class MockTarget +{ + public struct Architecture + { + public bool IsLittleEndian { get; init; } + public bool Is64Bit { get; init; } + } + + /// + /// Xunit enumeration of standard test architectures + /// + /// + /// [Theory] + /// [ClassData(typeof(MockTarget.StdArch))] + /// public void TestMethod(MockTarget.Architecture arch) + /// { + /// ... + /// } + /// + public class StdArch : IEnumerable + { + public IEnumerator GetEnumerator() + { + yield return [new Architecture { IsLittleEndian = true, Is64Bit = true }]; + yield return [new Architecture { IsLittleEndian = true, Is64Bit = false }]; + yield return [new Architecture { IsLittleEndian = false, Is64Bit = true }]; + yield return [new Architecture { IsLittleEndian = false, Is64Bit = false }]; + } + + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() => GetEnumerator(); + } + +} diff --git a/src/native/managed/cdacreader/tests/TargetTestHelpers.cs b/src/native/managed/cdacreader/tests/TargetTestHelpers.cs new file mode 100644 index 0000000000000..df95785d828c9 --- /dev/null +++ b/src/native/managed/cdacreader/tests/TargetTestHelpers.cs @@ -0,0 +1,251 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Buffers.Binary; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; + +namespace Microsoft.Diagnostics.DataContractReader.UnitTests; +internal unsafe class TargetTestHelpers +{ + public MockTarget.Architecture Arch { get; init; } + + public TargetTestHelpers(MockTarget.Architecture arch) + { + Arch = arch; + } + + public int PointerSize => Arch.Is64Bit ? sizeof(ulong) : sizeof(uint); + public int ContractDescriptorSize => ContractDescriptor.Size(Arch.Is64Bit); + + + #region Contract and data descriptor creation + + public void ContractDescriptorFill(Span dest, int jsonDescriptorSize, int pointerDataCount) + { + ContractDescriptor.Fill(dest, Arch, jsonDescriptorSize, pointerDataCount); + } + + internal static class ContractDescriptor + { + public static int Size(bool is64Bit) => is64Bit ? sizeof(ContractDescriptor64) : sizeof(ContractDescriptor32); + + public static void Fill(Span dest, MockTarget.Architecture arch, int jsonDescriptorSize, int pointerDataCount) + { + if (arch.Is64Bit) + { + ContractDescriptor64.Fill(dest, arch.IsLittleEndian, jsonDescriptorSize, pointerDataCount); + } + else + { + ContractDescriptor32.Fill(dest, arch.IsLittleEndian, jsonDescriptorSize, pointerDataCount); + } + } + + private struct ContractDescriptor32 + { + public ulong Magic = BitConverter.ToUInt64("DNCCDAC\0"u8); + public uint Flags = 0x2 /*32-bit*/ | 0x1; + public uint DescriptorSize; + public uint Descriptor = MockMemorySpace.JsonDescriptorAddr; + public uint PointerDataCount; + public uint Pad0 = 0; + public uint PointerData = MockMemorySpace.ContractPointerDataAddr; + + public ContractDescriptor32() { } + + public static void Fill(Span dest, bool isLittleEndian, int jsonDescriptorSize, int pointerDataCount) + { + ContractDescriptor32 descriptor = new() + { + DescriptorSize = (uint)jsonDescriptorSize, + PointerDataCount = (uint)pointerDataCount, + }; + if (BitConverter.IsLittleEndian != isLittleEndian) + descriptor.ReverseEndianness(); + + MemoryMarshal.AsBytes(MemoryMarshal.CreateSpan(ref descriptor, 1)).CopyTo(dest); + } + + private void ReverseEndianness() + { + Magic = BinaryPrimitives.ReverseEndianness(Magic); + Flags = BinaryPrimitives.ReverseEndianness(Flags); + DescriptorSize = BinaryPrimitives.ReverseEndianness(DescriptorSize); + Descriptor = BinaryPrimitives.ReverseEndianness(Descriptor); + PointerDataCount = BinaryPrimitives.ReverseEndianness(PointerDataCount); + Pad0 = BinaryPrimitives.ReverseEndianness(Pad0); + PointerData = BinaryPrimitives.ReverseEndianness(PointerData); + } + } + + private struct ContractDescriptor64 + { + public ulong Magic = BitConverter.ToUInt64("DNCCDAC\0"u8); + public uint Flags = 0x1; + public uint DescriptorSize; + public ulong Descriptor = MockMemorySpace.JsonDescriptorAddr; + public uint PointerDataCount; + public uint Pad0 = 0; + public ulong PointerData = MockMemorySpace.ContractPointerDataAddr; + + public ContractDescriptor64() { } + + public static void Fill(Span dest, bool isLittleEndian, int jsonDescriptorSize, int pointerDataCount) + { + ContractDescriptor64 descriptor = new() + { + DescriptorSize = (uint)jsonDescriptorSize, + PointerDataCount = (uint)pointerDataCount, + }; + if (BitConverter.IsLittleEndian != isLittleEndian) + descriptor.ReverseEndianness(); + + MemoryMarshal.AsBytes(MemoryMarshal.CreateSpan(ref descriptor, 1)).CopyTo(dest); + } + + private void ReverseEndianness() + { + Magic = BinaryPrimitives.ReverseEndianness(Magic); + Flags = BinaryPrimitives.ReverseEndianness(Flags); + DescriptorSize = BinaryPrimitives.ReverseEndianness(DescriptorSize); + Descriptor = BinaryPrimitives.ReverseEndianness(Descriptor); + PointerDataCount = BinaryPrimitives.ReverseEndianness(PointerDataCount); + Pad0 = BinaryPrimitives.ReverseEndianness(Pad0); + PointerData = BinaryPrimitives.ReverseEndianness(PointerData); + } + } + } + + #endregion Contract and data descriptor creation + + #region Data descriptor json formatting + private static string GetTypeJson(string name, Target.TypeInfo info) + { + string ret = string.Empty; + List fields = info.Size is null ? [] : [$"\"!\":{info.Size}"]; + fields.AddRange(info.Fields.Select(f => $"\"{f.Key}\":{(f.Value.TypeName is null ? f.Value.Offset : $"[{f.Value.Offset},\"{f.Value.TypeName}\"]")}")); + return $"\"{name}\":{{{string.Join(',', fields)}}}"; + } + + public static string MakeTypesJson(IEnumerable<(DataType Type, Target.TypeInfo Info)> types) + { + return string.Join(',', types.Select(t => GetTypeJson(t.Type.ToString(), t.Info))); + } + + public static string MakeGlobalsJson(IEnumerable<(string Name, ulong Value, string? Type)> globals) + { + return string.Join(',', globals.Select(i => $"\"{i.Name}\": {(i.Type is null ? i.Value.ToString() : $"[{i.Value}, \"{i.Type}\"]")}")); + } + + #endregion Data descriptor json formatting + + + + + #region Mock memory initialization + + internal uint ObjHeaderSize => (uint)(Arch.Is64Bit ? 2 * sizeof(uint) /*alignpad + syncblock*/: sizeof(uint) /* syncblock */); + internal uint ObjectSize => (uint)PointerSize /* methtab */; + + internal uint ObjectBaseSize => ObjHeaderSize + ObjectSize; + + internal uint ArrayBaseSize => Arch.Is64Bit ? ObjectSize + sizeof(uint) /* numComponents */ + sizeof(uint) /* pad*/ : ObjectSize + sizeof(uint) /* numComponents */; + + internal uint ArrayBaseBaseSize => ObjHeaderSize + ArrayBaseSize; + + internal uint StringBaseSize => ObjectBaseSize + sizeof(uint) /* length */ + sizeof(char) /* nul terminator */; + + internal void Write(Span dest, byte b) => dest[0] = b; + internal void Write(Span dest, ushort u) + { + if (Arch.IsLittleEndian) + { + BinaryPrimitives.WriteUInt16LittleEndian(dest, u); + } + else + { + BinaryPrimitives.WriteUInt16BigEndian(dest, u); + } + } + + internal void Write(Span dest, uint u) + { + if (Arch.IsLittleEndian) + { + BinaryPrimitives.WriteUInt32LittleEndian(dest, u); + } + else + { + BinaryPrimitives.WriteUInt32BigEndian(dest, u); + } + } + + internal void Write(Span dest, ulong u) + { + if (Arch.IsLittleEndian) + { + BinaryPrimitives.WriteUInt64LittleEndian(dest, u); + } + else + { + BinaryPrimitives.WriteUInt64BigEndian(dest, u); + } + } + + + internal void WritePointer(Span dest, ulong value) + { + if (Arch.Is64Bit) + { + if (Arch.IsLittleEndian) + { + BinaryPrimitives.WriteUInt64LittleEndian(dest, value); + } + else + { + BinaryPrimitives.WriteUInt64BigEndian(dest, value); + } + } + else + { + if (Arch.IsLittleEndian) + { + BinaryPrimitives.WriteUInt32LittleEndian(dest, (uint)value); + } + else + { + BinaryPrimitives.WriteUInt32BigEndian(dest, (uint)value); + } + } + } + + internal int SizeOfPrimitive(DataType type) + { + return type switch + { + DataType.uint8 or DataType.int8 => sizeof(byte), + DataType.uint16 or DataType.int16 => sizeof(ushort), + DataType.uint32 or DataType.int32 => sizeof(uint), + DataType.uint64 or DataType.int64 => sizeof(ulong), + DataType.pointer or DataType.nint or DataType.nuint => PointerSize, + _ => throw new InvalidOperationException($"Not a primitive: {type}"), + }; + } + + internal int SizeOfTypeInfo(Target.TypeInfo info) + { + int size = 0; + foreach (var (_, field) in info.Fields) + { + size = Math.Max(size, field.Offset + SizeOfPrimitive(field.Type)); + } + + return size; + } + + #endregion Mock memory initialization + +} diff --git a/src/native/managed/cdacreader/tests/TargetTests.cs b/src/native/managed/cdacreader/tests/TargetTests.cs index ec1899327b3fd..56e36ec7dc2e2 100644 --- a/src/native/managed/cdacreader/tests/TargetTests.cs +++ b/src/native/managed/cdacreader/tests/TargetTests.cs @@ -2,11 +2,8 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; -using System.Buffers.Binary; -using System.Collections.Generic; using System.Linq; using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; using System.Text; using Xunit; @@ -14,12 +11,9 @@ namespace Microsoft.Diagnostics.DataContractReader.UnitTests; public unsafe class TargetTests { - private const ulong ContractDescriptorAddr = 0xaaaaaaaa; - private const uint JsonDescriptorAddr = 0xdddddddd; - private const uint PointerDataAddr = 0xeeeeeeee; private static readonly (DataType Type, Target.TypeInfo Info)[] TestTypes = - [ + [ // Size and fields (DataType.Thread, new(){ Size = 56, @@ -41,35 +35,27 @@ private static readonly (DataType Type, Target.TypeInfo Info)[] TestTypes = ]; [Theory] - [InlineData(true, true)] - [InlineData(true, false)] - [InlineData(false, true)] - [InlineData(false, false)] - public void GetTypeInfo(bool isLittleEndian, bool is64Bit) + [ClassData(typeof(MockTarget.StdArch))] + public void GetTypeInfo(MockTarget.Architecture arch) { - string typesJson = string.Join(',', TestTypes.Select(t => GetTypeJson(t.Type.ToString(), t.Info))); + TargetTestHelpers targetTestHelpers = new(arch); + string typesJson = TargetTestHelpers.MakeTypesJson(TestTypes); byte[] json = Encoding.UTF8.GetBytes($$""" - { - "version": 0, - "baseline": "empty", - "contracts": {}, - "types": { {{typesJson}} }, - "globals": {} - } - """); - Span descriptor = stackalloc byte[ContractDescriptor.Size(is64Bit)]; - ContractDescriptor.Fill(descriptor, isLittleEndian, is64Bit, json.Length, 0); + { + "version": 0, + "baseline": "empty", + "contracts": {}, + "types": { {{typesJson}} }, + "globals": {} + } + """); + Span descriptor = stackalloc byte[targetTestHelpers.ContractDescriptorSize]; + targetTestHelpers.ContractDescriptorFill(descriptor, json.Length, 0); fixed (byte* jsonPtr = json) { - ReadContext context = new ReadContext - { - ContractDescriptor = (byte*)Unsafe.AsPointer(ref MemoryMarshal.GetReference(descriptor)), - ContractDescriptorLength = descriptor.Length, - JsonDescriptor = jsonPtr, - JsonDescriptorLength = json.Length, - }; + using MockMemorySpace.ReadContext context = MockMemorySpace.CreateContext(descriptor, json); - bool success = Target.TryCreate(ContractDescriptorAddr, &ReadFromTarget, &context, out Target? target); + bool success = MockMemorySpace.TryCreateTarget(&context, out Target? target); Assert.True(success); foreach ((DataType type, Target.TypeInfo info) in TestTypes) @@ -88,14 +74,6 @@ public void GetTypeInfo(bool isLittleEndian, bool is64Bit) } } } - - static string GetTypeJson(string name, Target.TypeInfo info) - { - string ret = string.Empty; - List fields = info.Size is null ? [] : [$"\"!\":{info.Size}"]; - fields.AddRange(info.Fields.Select(f => $"\"{f.Key}\":{(f.Value.TypeName is null ? f.Value.Offset : $"[{f.Value.Offset},\"{f.Value.TypeName}\"]")}")); - return $"\"{name}\":{{{string.Join(',', fields)}}}"; - } } private static readonly (string Name, ulong Value, string? Type)[] TestGlobals = @@ -115,13 +93,11 @@ private static readonly (string Name, ulong Value, string? Type)[] TestGlobals = ]; [Theory] - [InlineData(true, true)] - [InlineData(true, false)] - [InlineData(false, true)] - [InlineData(false, false)] - public void ReadGlobalValue(bool isLittleEndian, bool is64Bit) + [ClassData(typeof(MockTarget.StdArch))] + public void ReadGlobalValue(MockTarget.Architecture arch) { - string globalsJson = string.Join(',', TestGlobals.Select(i => $"\"{i.Name}\": {(i.Type is null ? i.Value.ToString() : $"[{i.Value}, \"{i.Type}\"]")}")); + TargetTestHelpers targetTestHelpers = new(arch); + string globalsJson = TargetTestHelpers.MakeGlobalsJson(TestGlobals); byte[] json = Encoding.UTF8.GetBytes($$""" { "version": 0, @@ -131,19 +107,13 @@ public void ReadGlobalValue(bool isLittleEndian, bool is64Bit) "globals": { {{globalsJson}} } } """); - Span descriptor = stackalloc byte[ContractDescriptor.Size(is64Bit)]; - ContractDescriptor.Fill(descriptor, isLittleEndian, is64Bit, json.Length, 0); + Span descriptor = stackalloc byte[targetTestHelpers.ContractDescriptorSize]; + targetTestHelpers.ContractDescriptorFill(descriptor, json.Length, 0); fixed (byte* jsonPtr = json) { - ReadContext context = new ReadContext - { - ContractDescriptor = (byte*)Unsafe.AsPointer(ref MemoryMarshal.GetReference(descriptor)), - ContractDescriptorLength = descriptor.Length, - JsonDescriptor = jsonPtr, - JsonDescriptorLength = json.Length, - }; + using MockMemorySpace.ReadContext context = MockMemorySpace.CreateContext(descriptor, json); - bool success = Target.TryCreate(ContractDescriptorAddr, &ReadFromTarget, &context, out Target? target); + bool success = MockMemorySpace.TryCreateTarget(&context, out Target? target); Assert.True(success); ValidateGlobals(target, TestGlobals); @@ -151,18 +121,16 @@ public void ReadGlobalValue(bool isLittleEndian, bool is64Bit) } [Theory] - [InlineData(true, true)] - [InlineData(true, false)] - [InlineData(false, true)] - [InlineData(false, false)] - public void ReadIndirectGlobalValue(bool isLittleEndian, bool is64Bit) + [ClassData(typeof(MockTarget.StdArch))] + public void ReadIndirectGlobalValue(MockTarget.Architecture arch) { - int pointerSize = is64Bit ? sizeof(ulong) : sizeof(uint); + TargetTestHelpers targetTestHelpers = new(arch); + int pointerSize = targetTestHelpers.PointerSize; Span pointerData = stackalloc byte[TestGlobals.Length * pointerSize]; for (int i = 0; i < TestGlobals.Length; i++) { var (_, value, _) = TestGlobals[i]; - WritePointer(pointerData.Slice(i * pointerSize), value, isLittleEndian, pointerSize); + targetTestHelpers.WritePointer(pointerData.Slice(i * pointerSize), value); } string globalsJson = string.Join(',', TestGlobals.Select((g, i) => $"\"{g.Name}\": {(g.Type is null ? $"[{i}]" : $"[[{i}], \"{g.Type}\"]")}")); @@ -175,25 +143,17 @@ public void ReadIndirectGlobalValue(bool isLittleEndian, bool is64Bit) "globals": { {{globalsJson}} } } """); - Span descriptor = stackalloc byte[ContractDescriptor.Size(is64Bit)]; - ContractDescriptor.Fill(descriptor, isLittleEndian, is64Bit, json.Length, pointerData.Length / pointerSize); + Span descriptor = stackalloc byte[targetTestHelpers.ContractDescriptorSize]; + targetTestHelpers.ContractDescriptorFill(descriptor, json.Length, pointerData.Length / pointerSize); fixed (byte* jsonPtr = json) { - ReadContext context = new ReadContext - { - ContractDescriptor = (byte*)Unsafe.AsPointer(ref MemoryMarshal.GetReference(descriptor)), - ContractDescriptorLength = descriptor.Length, - JsonDescriptor = jsonPtr, - JsonDescriptorLength = json.Length, - PointerData = (byte*)Unsafe.AsPointer(ref MemoryMarshal.GetReference(pointerData)), - PointerDataLength = pointerData.Length - }; + using MockMemorySpace.ReadContext context = MockMemorySpace.CreateContext(descriptor, json, pointerData); - bool success = Target.TryCreate(ContractDescriptorAddr, &ReadFromTarget, &context, out Target? target); + bool success = MockMemorySpace.TryCreateTarget(&context, out Target? target); Assert.True(success); // Indirect values are pointer-sized, so max 32-bits for a 32-bit target - var expected = is64Bit + var expected = arch.Is64Bit ? TestGlobals : TestGlobals.Select(g => (g.Name, g.Value & 0xffffffff, g.Type)).ToArray(); @@ -201,32 +161,6 @@ public void ReadIndirectGlobalValue(bool isLittleEndian, bool is64Bit) } } - private static void WritePointer(Span dest, ulong value, bool isLittleEndian, int pointerSize) - { - if (pointerSize == sizeof(ulong)) - { - if (isLittleEndian) - { - BinaryPrimitives.WriteUInt64LittleEndian(dest, value); - } - else - { - BinaryPrimitives.WriteUInt64BigEndian(dest, value); - } - } - else if (pointerSize == sizeof(uint)) - { - if (isLittleEndian) - { - BinaryPrimitives.WriteUInt32LittleEndian(dest, (uint)value); - } - else - { - BinaryPrimitives.WriteUInt32BigEndian(dest, (uint)value); - } - } - } - private static void ValidateGlobals( Target target, (string Name, ulong Value, string? Type)[] globals, @@ -302,146 +236,10 @@ private static void ValidateGlobals( } } - void AssertEqualsWithCallerInfo(T expected, T actual) + void AssertEqualsWithCallerInfo(T expected, T actual) { Assert.True((expected is null && actual is null) || expected.Equals(actual), $"Expected: {expected}. Actual: {actual}. [test case: {caller} in {filePath}:{lineNumber}]"); } } - [UnmanagedCallersOnly] - private static int ReadFromTarget(ulong address, byte* buffer, uint length, void* context) - { - ReadContext* readContext = (ReadContext*)context; - var span = new Span(buffer, (int)length); - - // Populate the span with the requested portion of the contract descriptor - if (address >= ContractDescriptorAddr && address <= ContractDescriptorAddr + (ulong)readContext->ContractDescriptorLength - length) - { - ulong offset = address - ContractDescriptorAddr; - new ReadOnlySpan(readContext->ContractDescriptor + offset, (int)length).CopyTo(span); - return 0; - } - - // Populate the span with the JSON descriptor - this assumes the product will read it all at once. - if (address == JsonDescriptorAddr) - { - new ReadOnlySpan(readContext->JsonDescriptor, readContext->JsonDescriptorLength).CopyTo(span); - return 0; - } - - // Populate the span with the requested portion of the pointer data - if (address >= PointerDataAddr && address <= PointerDataAddr + (ulong)readContext->PointerDataLength - length) - { - ulong offset = address - PointerDataAddr; - new ReadOnlySpan(readContext->PointerData + offset, (int)length).CopyTo(span); - return 0; - } - - return -1; - } - - // Used by ReadFromTarget to return the appropriate bytes - private struct ReadContext - { - public byte* ContractDescriptor; - public int ContractDescriptorLength; - - public byte* JsonDescriptor; - public int JsonDescriptorLength; - - public byte* PointerData; - public int PointerDataLength; - } - - private static class ContractDescriptor - { - public static int Size(bool is64Bit) => is64Bit ? sizeof(ContractDescriptor64) : sizeof(ContractDescriptor32); - - public static void Fill(Span dest, bool isLittleEndian, bool is64Bit, int jsonDescriptorSize, int pointerDataCount) - { - if (is64Bit) - { - ContractDescriptor64.Fill(dest, isLittleEndian, jsonDescriptorSize, pointerDataCount); - } - else - { - ContractDescriptor32.Fill(dest, isLittleEndian, jsonDescriptorSize, pointerDataCount); - } - } - - private struct ContractDescriptor32 - { - public ulong Magic = BitConverter.ToUInt64("DNCCDAC\0"u8); - public uint Flags = 0x2 /*32-bit*/ | 0x1; - public uint DescriptorSize; - public uint Descriptor = JsonDescriptorAddr; - public uint PointerDataCount; - public uint Pad0 = 0; - public uint PointerData = PointerDataAddr; - - public ContractDescriptor32() { } - - public static void Fill(Span dest, bool isLittleEndian, int jsonDescriptorSize, int pointerDataCount) - { - ContractDescriptor32 descriptor = new() - { - DescriptorSize = (uint)jsonDescriptorSize, - PointerDataCount = (uint)pointerDataCount, - }; - if (BitConverter.IsLittleEndian != isLittleEndian) - descriptor.ReverseEndianness(); - - MemoryMarshal.AsBytes(MemoryMarshal.CreateSpan(ref descriptor, 1)).CopyTo(dest); - } - - private void ReverseEndianness() - { - Magic = BinaryPrimitives.ReverseEndianness(Magic); - Flags = BinaryPrimitives.ReverseEndianness(Flags); - DescriptorSize = BinaryPrimitives.ReverseEndianness(DescriptorSize); - Descriptor = BinaryPrimitives.ReverseEndianness(Descriptor); - PointerDataCount = BinaryPrimitives.ReverseEndianness(PointerDataCount); - Pad0 = BinaryPrimitives.ReverseEndianness(Pad0); - PointerData = BinaryPrimitives.ReverseEndianness(PointerData); - } - } - - private struct ContractDescriptor64 - { - public ulong Magic = BitConverter.ToUInt64("DNCCDAC\0"u8); - public uint Flags = 0x1; - public uint DescriptorSize; - public ulong Descriptor = JsonDescriptorAddr; - public uint PointerDataCount; - public uint Pad0 = 0; - public ulong PointerData = PointerDataAddr; - - public ContractDescriptor64() { } - - public static void Fill(Span dest, bool isLittleEndian, int jsonDescriptorSize, int pointerDataCount) - { - ContractDescriptor64 descriptor = new() - { - DescriptorSize = (uint)jsonDescriptorSize, - PointerDataCount = (uint)pointerDataCount, - }; - if (BitConverter.IsLittleEndian != isLittleEndian) - descriptor.ReverseEndianness(); - - MemoryMarshal.AsBytes(MemoryMarshal.CreateSpan(ref descriptor, 1)).CopyTo(dest); - } - - private void ReverseEndianness() - { - Magic = BinaryPrimitives.ReverseEndianness(Magic); - Flags = BinaryPrimitives.ReverseEndianness(Flags); - DescriptorSize = BinaryPrimitives.ReverseEndianness(DescriptorSize); - Descriptor = BinaryPrimitives.ReverseEndianness(Descriptor); - PointerDataCount = BinaryPrimitives.ReverseEndianness(PointerDataCount); - Pad0 = BinaryPrimitives.ReverseEndianness(Pad0); - PointerData = BinaryPrimitives.ReverseEndianness(PointerData); - } - } - } - }