diff --git a/docs/design/datacontracts/EditAndContinue.md b/docs/design/datacontracts/EditAndContinue.md new file mode 100644 index 00000000000000..5826b69caf2c7b --- /dev/null +++ b/docs/design/datacontracts/EditAndContinue.md @@ -0,0 +1,64 @@ +# Contract EditAndContinue + +This contract exposes the runtime's Edit-and-Continue (EnC) bookkeeping. + +## APIs of contract + +```csharp +IEnumerable EnumerateAddedFieldDescs(TypeHandle typeHandle, bool staticFields); +``` + +## Version 1 + +### Data descriptors + +| Data Descriptor Name | Field | Meaning | +| --- | --- | --- | +| `Module` | `EnCClassList` | Address of the embedded `CUnorderedArrayBaseWithAllocator` holding the module's `EnCEEClassData*` entries. Optional: only present when EditAndContinue is configured. | +| `UnorderedArrayBase` | `Count` | Number of valid entries currently stored in the array. | +| `UnorderedArrayBase` | `Table` | Pointer to the backing storage holding the array's entries. | +| `EnCEEClassData` | `MethodTable` | Pointer to the `MethodTable` whose EnC data is held by this entry. | +| `EnCEEClassData` | `AddedInstanceFields` | Head of the linked list of `EnCAddedFieldElement` for added instance fields. | +| `EnCEEClassData` | `AddedStaticFields` | Head of the linked list of `EnCAddedFieldElement` for added static fields. | +| `EnCAddedFieldElement` | `Next` | Pointer to the next `EnCAddedFieldElement` in the linked list. | +| `EnCAddedFieldElement` | `FieldDesc` | Address of the embedded `EnCFieldDesc` (layout-compatible with `FieldDesc`). | + +### Required contracts + +| Contract | +| --- | +| `IRuntimeTypeSystem` | +| `ILoader` | + +```csharp +IEnumerable EnumerateAddedFieldDescs(TypeHandle typeHandle, bool staticFields) +{ + // get modulePtr and moduleHandle from typeHandle + // if there is no EnC data, yield break + TargetPointer classList = modulePtr + /* Module::EnCClassList offset */; + uint classListCount = target.Read(classList + /* UnorderedArrayBase::Count offset */); + TargetPointer classListTable = target.ReadPointer(classList + /* UnorderedArrayBase::Table offset */); + TargetPointer classDataPtr = TargetPointer.Null; + + for (uint i = 0; i < classListCount; i++) + { + // search on EnC data for data that matches the method table + TargetPointer entry = target.ReadPointer(classListTable + i * target.PointerSize); + TargetPointer mt = target.ReadPointer(entry + /* EnCEEClassData::MethodTable offset */); + if (mt == typeHandle.Address) + { + classDataPtr = entry; + break; + } + } + + // enumerate fields that have been added + TargetPointer node = staticFields ? target.ReadPointer(classDataPtr + /* EnCEEClassData::AddedStaticFields offset */) + : target.ReadPointer(classDataPtr + /* EnCEEClassData::AddedInstanceFields offset */); + while (node != TargetPointer.Null) + { + yield return node + /* EnCAddedFieldElement::FieldDesc offset */; + node = target.ReadPointer(node + /* EnCAddedFieldElement::Next offset */); + } +} +``` diff --git a/docs/design/datacontracts/RuntimeTypeSystem.md b/docs/design/datacontracts/RuntimeTypeSystem.md index a54f4cb37c18a7..30519ea9b416cd 100644 --- a/docs/design/datacontracts/RuntimeTypeSystem.md +++ b/docs/design/datacontracts/RuntimeTypeSystem.md @@ -51,6 +51,11 @@ partial interface IRuntimeTypeSystem : IContract public virtual TargetCodePointer GetSlot(TypeHandle typeHandle, uint slot); public virtual uint GetBaseSize(TypeHandle typeHandle); + // The number of bytes of instance fields stored in an object of this type on the GC heap. + // Equivalent to the native MethodTable::GetNumInstanceFieldBytes(), which is computed as + // GetBaseSize() minus the EEClass base-size padding (the trailing alignment/min-object-size + // bytes included in BaseSize but not occupied by actual instance fields). + public virtual uint GetNumInstanceFieldBytes(TypeHandle typeHandle); // 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(TypeHandle typeHandle); @@ -274,8 +279,10 @@ TargetPointer GetMTOfEnclosingClass(TargetPointer fieldDescPointer); uint GetFieldDescMemberDef(TargetPointer fieldDescPointer); bool IsFieldDescThreadStatic(TargetPointer fieldDescPointer); bool IsFieldDescStatic(TargetPointer fieldDescPointer); +bool IsFieldDescRVA(TargetPointer fieldDescPointer); +bool IsFieldDescEnCNew(TargetPointer fieldDescPointer); uint GetFieldDescType(TargetPointer fieldDescPointer); -uint GetFieldDescOffset(TargetPointer fieldDescPointer, FieldDefinition fieldDef); +uint GetFieldDescOffset(TargetPointer fieldDescPointer, FieldDefinition? fieldDef); TargetPointer GetFieldDescStaticAddress(TargetPointer fieldDescPointer, bool unboxValueTypes = true); TargetPointer GetFieldDescThreadStaticAddress(TargetPointer fieldDescPointer, TargetPointer thread, bool unboxValueTypes = true); ``` @@ -576,6 +583,8 @@ Contracts used: public uint GetBaseSize(TypeHandle TypeHandle) => !typeHandle.IsMethodTable() ? (uint)0 : _methodTables[TypeHandle.Address].Flags.BaseSize; + public uint GetNumInstanceFieldBytes(TypeHandle TypeHandle) => !typeHandle.IsMethodTable() ? (uint)0 : _methodTables[TypeHandle.Address].Flags.BaseSize - GetClassData(TypeHandle).BaseSizePadding; + public uint GetComponentSize(TypeHandle TypeHandle) =>!typeHandle.IsMethodTable() ? (uint)0 : GetComponentSize(_methodTables[TypeHandle.Address]); public TargetPointer GetClassPointer(TypeHandle TypeHandle) @@ -2023,6 +2032,13 @@ Getting a MethodDesc for a certain slot in a MethodTable ### FieldDesc +The version 1 FieldDesc APIs depend on the following globals: + +| Global name | Meaning | +| --- | --- | +| `FieldOffsetBigRVA` | Sentinel value of `FieldDesc::DWord2` indicating the field is an RVA static whose offset is too large to encode in the bitfield; the real offset must be read from the field's metadata (`FieldDefinition.GetRelativeVirtualAddress`). | +| `FieldOffsetNewEnc` | Sentinel value of `FieldDesc`'s offset bitfield indicating the field was added via Edit-and-Continue and does not yet have backing storage. | + The version 1 FieldDesc APIs depend on the following data descriptors: | Data Descriptor Name | Field | Meaning | | --- | --- | --- | @@ -2036,6 +2052,7 @@ internal enum FieldDescFlags1 : uint TokenMask = 0xffffff, IsStatic = 0x1000000, IsThreadStatic = 0x2000000, + IsRVA = 0x4000000, } internal enum FieldDescFlags2 : uint @@ -2067,18 +2084,31 @@ bool IsFieldDescStatic(TargetPointer fieldDescPointer) return (DWord1 & (uint)FieldDescFlags1.IsStatic) != 0; } +bool IsFieldDescRVA(TargetPointer fieldDescPointer) +{ + uint DWord1 = target.Read(fieldDescPointer + /* FieldDesc::DWord1 offset */); + return (DWord1 & (uint)FieldDescFlags1.IsRVA) != 0; +} + +bool IsFieldDescEnCNew(TargetPointer fieldDescPointer) +{ + uint DWord2 = target.Read(fieldDescPointer + /* FieldDesc::DWord2 offset */); + uint offset = DWord2 & (uint)FieldDescFlags2.OffsetMask; + return offset == _target.ReadGlobal("FieldOffsetNewEnc"); +} + uint GetFieldDescType(TargetPointer fieldDescPointer) { uint DWord2 = target.Read(fieldDescPointer + /* FieldDesc::DWord2 offset */); return (DWord2 & (uint)FieldDescFlags2.TypeMask) >> 27; } -uint GetFieldDescOffset(TargetPointer fieldDescPointer) +uint GetFieldDescOffset(TargetPointer fieldDescPointer, FieldDefinition? fieldDef) { uint DWord2 = target.Read(fieldDescPointer + /* FieldDesc::DWord2 offset */); - if (DWord2 == _target.ReadGlobal("FieldOffsetBigRVA")) + if (DWord2 == _target.ReadGlobal("FieldOffsetBigRVA") && fieldDef.HasValue) { - return (uint)fieldDef.GetRelativeVirtualAddress(); + return (uint)fieldDef.Value.GetRelativeVirtualAddress(); } return DWord2 & (uint)FieldDescFlags2.OffsetMask; } diff --git a/src/coreclr/debug/daccess/dacdbiimpl.cpp b/src/coreclr/debug/daccess/dacdbiimpl.cpp index d135943edf54b5..7de232c9c7406f 100644 --- a/src/coreclr/debug/daccess/dacdbiimpl.cpp +++ b/src/coreclr/debug/daccess/dacdbiimpl.cpp @@ -1404,69 +1404,6 @@ void DacDbiInterfaceImpl::GetTypeHandles(VMPTR_TypeHandle vmThExact, } } // DacDbiInterfaceImpl::GetTypeHandles -//----------------------------------------------------------------------------- -// DacDbiInterfaceImpl::GetTotalFieldCount -// Gets the total number of fields for a type. -// Input Argument: thApprox - type handle used to determine the number of fields -// Return Value: count of the total fields of the type. -//----------------------------------------------------------------------------- -unsigned int DacDbiInterfaceImpl::GetTotalFieldCount(TypeHandle thApprox) -{ - MethodTable *pMT = thApprox.GetMethodTable(); - - // Count the instance and static fields for this class (not including parent). - // This will not include any newly added EnC fields. - unsigned int IFCount = pMT->GetNumIntroducedInstanceFields(); - unsigned int SFCount = pMT->GetNumStaticFields(); - -#ifdef FEATURE_METADATA_UPDATER - PTR_Module pModule = pMT->GetModule(); - - // Stats above don't include EnC fields. So add them now. - if (pModule->IsEditAndContinueEnabled()) - { - PTR_EnCEEClassData pEncData = - (dac_cast(pModule))->GetEnCEEClassData(pMT, TRUE); - - if (pEncData != NULL) - { - _ASSERTE(pEncData->GetMethodTable() == pMT); - - // EnC only adds fields, never removes them. - IFCount += pEncData->GetAddedInstanceFields(); - SFCount += pEncData->GetAddedStaticFields(); - } - } -#endif - return IFCount + SFCount; -} // DacDbiInterfaceImpl::GetTotalFieldCount - -//----------------------------------------------------------------------------- -// DacDbiInterfaceImpl::InitClassData -// initializes various values of the ClassInfo data structure, including the -// field count, generic args count, size and value class flag -// Arguments: -// input: thApprox - used to get access to all the necessary values -// fIsInstantiatedType - used to determine how to compute the size -// output: pData - contains fields to be initialized -//----------------------------------------------------------------------------- -void DacDbiInterfaceImpl::InitClassData(TypeHandle thApprox, - BOOL fIsInstantiatedType, - ClassInfo * pData) -{ - pData->m_fieldList.Alloc(GetTotalFieldCount(thApprox)); - - // For Generic classes you must get the object size via the type handle, which - // will get you to the right information for the particular instantiation - // you're working with... - pData->m_objectSize = 0; - if ((!thApprox.GetNumGenericArgs()) || fIsInstantiatedType) - { - pData->m_objectSize = thApprox.GetMethodTable()->GetNumInstanceFieldBytes(); - } - -} // DacDbiInterfaceImpl::InitClassData - //----------------------------------------------------------------------------- // DacDbiInterfaceImpl::GetStaticsBases // Gets the base table addresses for both GC and non-GC statics @@ -1541,7 +1478,7 @@ void DacDbiInterfaceImpl::ComputeFieldData(PTR_FieldDesc pFD, PTR_VOID addr = pFD->GetStaticAddressHandle(NULL); if (pCurrentFieldData->OkToGetOrSetStaticAddress()) { - pCurrentFieldData->SetStaticAddress(PTR_TO_TADDR(addr)); + pCurrentFieldData->SetStaticAddress(PTR_TO_CORDB_ADDRESS(dac_cast(addr))); } } else if (pFD->IsThreadStatic() || @@ -1561,7 +1498,7 @@ void DacDbiInterfaceImpl::ComputeFieldData(PTR_FieldDesc pFD, if (pCurrentFieldData->OkToGetOrSetStaticAddress()) { - pCurrentFieldData->SetStaticAddress((TADDR)NULL); + pCurrentFieldData->SetStaticAddress((CORDB_ADDRESS)NULL); } } else @@ -1569,7 +1506,7 @@ void DacDbiInterfaceImpl::ComputeFieldData(PTR_FieldDesc pFD, if (pCurrentFieldData->OkToGetOrSetStaticAddress()) { // calculate the absolute address using the base and the offset from the base - pCurrentFieldData->SetStaticAddress(PTR_TO_TADDR(base) + pFD->GetOffset()); + pCurrentFieldData->SetStaticAddress(PTR_TO_CORDB_ADDRESS(dac_cast(base)) + pFD->GetOffset()); } } } @@ -1588,18 +1525,17 @@ void DacDbiInterfaceImpl::ComputeFieldData(PTR_FieldDesc pFD, //----------------------------------------------------------------------------- // DacDbiInterfaceImpl::CollectFields -// Gets information for all the fields for a given type +// Reports per-field FieldData entries for a given type to the supplied callback. // Arguments: -// input: thExact - used to determine whether we need to get statics base tables -// thApprox - used to get the field desc iterator -// output: -// pFieldList - contains fields to be initialized -// Note: the caller must ensure that *ppFields is NULL (i.e., any previously allocated memory -// must have been deallocated. +// input: thExact - used to determine whether we need to get statics base tables +// thApprox - used to get the field desc iterator +// fpCallback - invoked once per field. Must not throw. +// pUserData - opaque user data passed back to the callback. //----------------------------------------------------------------------------- void DacDbiInterfaceImpl::CollectFields(TypeHandle thExact, TypeHandle thApprox, - DacDbiArrayList * pFieldList) + FP_FIELDDATA_CALLBACK fpCallback, + CALLBACK_DATA pUserData) { PTR_BYTE pGCStaticsBase = NULL; PTR_BYTE pNonGCStaticsBase = NULL; @@ -1609,8 +1545,6 @@ void DacDbiInterfaceImpl::CollectFields(TypeHandle thExact, GetStaticsBases(thExact, &pGCStaticsBase, &pNonGCStaticsBase); } - unsigned int fieldCount = 0; - // we are losing exact type information for static fields in generic types. We have // field desc iterators only for approximate types, but statics are per instantiation, so we // need an exact type to be able to handle these correctly. We need to use @@ -1620,17 +1554,12 @@ void DacDbiInterfaceImpl::CollectFields(TypeHandle thExact, ApproxFieldDescIterator::ALL_FIELDS); // don't fixup EnC (we can't, we're stopped) PTR_FieldDesc pCurrentFD; - unsigned int index = 0; - while (((pCurrentFD = fdIterator.Next()) != NULL) && (index < pFieldList->Count())) + while ((pCurrentFD = fdIterator.Next()) != NULL) { - // fill in the pCurrentEntry structure - ComputeFieldData(pCurrentFD, pGCStaticsBase, pNonGCStaticsBase, &((*pFieldList)[index])); - - // Bump our counts and pointers. - fieldCount++; - index++; + FieldData currentFieldData; + ComputeFieldData(pCurrentFD, pGCStaticsBase, pNonGCStaticsBase, ¤tFieldData); + fpCallback(¤tFieldData, pUserData); } - _ASSERTE(fieldCount == (unsigned int)pFieldList->Count()); } // DacDbiInterfaceImpl::CollectFields @@ -1667,8 +1596,11 @@ HRESULT STDMETHODCALLTYPE DacDbiInterfaceImpl::HasTypeParams(VMPTR_TypeHandle th return hr; } -// DacDbi API: Get type information for a class -HRESULT STDMETHODCALLTYPE DacDbiInterfaceImpl::GetClassInfo(VMPTR_TypeHandle thExact, ClassInfo * pData) +// DacDbi API: Enumerate the FieldData entries for a class. +HRESULT STDMETHODCALLTYPE DacDbiInterfaceImpl::EnumerateClassFields(VMPTR_TypeHandle thExact, + OUT SIZE_T *pObjectSize, + FP_FIELDDATA_CALLBACK fpCallback, + CALLBACK_DATA pUserData) { DD_ENTER_MAY_THROW; @@ -1681,17 +1613,29 @@ HRESULT STDMETHODCALLTYPE DacDbiInterfaceImpl::GetClassInfo(VMPTR_TypeHandle thE GetTypeHandles(thExact, thExact, &typeHandleExact, &thApprox); - // initialize field count, generic args count, size and value class flag - InitClassData(thApprox, false, pData); + // For Generic classes you must get the object size via the type handle, which + // will get you to the right information for the particular instantiation + // you're working with... + *pObjectSize = 0; + if (!thApprox.GetNumGenericArgs()) + { + *pObjectSize = thApprox.GetMethodTable()->GetNumInstanceFieldBytes(); + } - CollectFields(typeHandleExact, thApprox, &(pData->m_fieldList)); + CollectFields(typeHandleExact, thApprox, fpCallback, pUserData); } EX_CATCH_HRESULT(hr); return hr; } -// DacDbi API: Get field information and object size for an instantiated generic type -HRESULT STDMETHODCALLTYPE DacDbiInterfaceImpl::GetInstantiationFieldInfo(VMPTR_Assembly vmAssembly, VMPTR_TypeHandle vmThExact, VMPTR_TypeHandle vmThApprox, OUT DacDbiArrayList * pFieldList, OUT SIZE_T * pObjectSize) +// DacDbi API: Enumerate the FieldData entries for an instantiated generic type +// and report its instantiation-specific object size. +HRESULT STDMETHODCALLTYPE DacDbiInterfaceImpl::EnumerateInstantiationFields(VMPTR_Assembly vmAssembly, + VMPTR_TypeHandle vmThExact, + VMPTR_TypeHandle vmThApprox, + OUT SIZE_T *pObjectSize, + FP_FIELDDATA_CALLBACK fpCallback, + CALLBACK_DATA pUserData) { DD_ENTER_MAY_THROW; @@ -1706,9 +1650,7 @@ HRESULT STDMETHODCALLTYPE DacDbiInterfaceImpl::GetInstantiationFieldInfo(VMPTR_A *pObjectSize = thApprox.GetMethodTable()->GetNumInstanceFieldBytes(); - pFieldList->Alloc(GetTotalFieldCount(thApprox)); - - CollectFields(thExact, thApprox, pFieldList); + CollectFields(thExact, thApprox, fpCallback, pUserData); } EX_CATCH_HRESULT(hr); @@ -3773,7 +3715,7 @@ void DacDbiInterfaceImpl::InitFieldData(const FieldDesc * pFD, _ASSERTE(!pFieldData->m_fFldIsRVA); // pORField contains the absolute address - pFieldData->SetStaticAddress(PTR_TO_TADDR(pORField)); + pFieldData->SetStaticAddress(PTR_TO_CORDB_ADDRESS(PTR_TO_TADDR(pORField))); } else { diff --git a/src/coreclr/debug/daccess/dacdbiimpl.h b/src/coreclr/debug/daccess/dacdbiimpl.h index 814c8372139dd6..96176bb32730c8 100644 --- a/src/coreclr/debug/daccess/dacdbiimpl.h +++ b/src/coreclr/debug/daccess/dacdbiimpl.h @@ -233,11 +233,19 @@ class DacDbiInterfaceImpl : // Determine if a type has generic parameters HRESULT STDMETHODCALLTYPE HasTypeParams(VMPTR_TypeHandle th, OUT BOOL * pResult); - // Get type information for a class - HRESULT STDMETHODCALLTYPE GetClassInfo(VMPTR_TypeHandle thExact, ClassInfo * pData); + // Enumerate the FieldData entries for a class. + HRESULT STDMETHODCALLTYPE EnumerateClassFields(VMPTR_TypeHandle thExact, + OUT SIZE_T *pObjectSize, + FP_FIELDDATA_CALLBACK fpCallback, + CALLBACK_DATA pUserData); - // get field information and object size for an instantiated generic type - HRESULT STDMETHODCALLTYPE GetInstantiationFieldInfo(VMPTR_Assembly vmAssembly, VMPTR_TypeHandle vmThExact, VMPTR_TypeHandle vmThApprox, OUT DacDbiArrayList * pFieldList, OUT SIZE_T * pObjectSize); + // Enumerate the FieldData entries for an instantiated generic type. + HRESULT STDMETHODCALLTYPE EnumerateInstantiationFields(VMPTR_Assembly vmAssembly, + VMPTR_TypeHandle vmThExact, + VMPTR_TypeHandle vmThApprox, + OUT SIZE_T *pObjectSize, + FP_FIELDDATA_CALLBACK fpCallback, + CALLBACK_DATA pUserData); HRESULT STDMETHODCALLTYPE GetObjectExpandedTypeInfo(AreValueTypesBoxed boxed, CORDB_ADDRESS addr, OUT DebuggerIPCE_ExpandedTypeData * pTypeInfo); @@ -334,15 +342,6 @@ class DacDbiInterfaceImpl : TypeHandle * pThExact, TypeHandle * pThApprox); - // Gets the total number of fields for a type. - unsigned int GetTotalFieldCount(TypeHandle thApprox); - - // initializes various values of the ClassInfo data structure, including the - // field count, generic args count, size and value class flag - void InitClassData(TypeHandle thApprox, - BOOL fIsInstantiatedType, - ClassInfo * pData); - // Gets the base table addresses for both GC and non-GC statics void GetStaticsBases(TypeHandle thExact, PTR_BYTE * ppGCStaticsBase, @@ -354,10 +353,11 @@ class DacDbiInterfaceImpl : PTR_BYTE pNonGCStaticsBase, FieldData * pCurrentFieldData); - // Gets information for all the fields for a given type - void CollectFields(TypeHandle thExact, - TypeHandle thApprox, - DacDbiArrayList * pFieldList); + // Reports per-field FieldData entries for a given type to the supplied callback. + void CollectFields(TypeHandle thExact, + TypeHandle thApprox, + FP_FIELDDATA_CALLBACK fpCallback, + CALLBACK_DATA pUserData); // Gets additional information to convert a type handle to an instance of CordbType if the type is E_T_ARRAY void GetArrayTypeInfo(TypeHandle typeHandle, diff --git a/src/coreclr/debug/di/divalue.cpp b/src/coreclr/debug/di/divalue.cpp index 39357400b3ba8e..576a10bf370848 100644 --- a/src/coreclr/debug/di/divalue.cpp +++ b/src/coreclr/debug/di/divalue.cpp @@ -2056,7 +2056,7 @@ HRESULT CordbObjectValue::GetFieldValueForType(ICorDebugType * pType, EX_TRY { BOOL fSyncBlockField = FALSE; - SIZE_T fldOffset; + CORDB_ADDRESS fldOffset; // // @todo: need to ensure that pType is really on the class @@ -3148,7 +3148,7 @@ HRESULT CordbVCObjectValue::GetFieldValueForType(ICorDebugType * pType, _ASSERTE(pFieldData->OkToGetOrSetInstanceOffset()); // Compute the address of the field contents in our local object cache - SIZE_T fieldOffset = pFieldData->GetInstanceOffset(); + CORDB_ADDRESS fieldOffset = pFieldData->GetInstanceOffset(); ULONG32 size = GetSizeForType(pFieldType, kUnboxed); // verify that the field starts before the end of m_pObjectCopy diff --git a/src/coreclr/debug/di/rsclass.cpp b/src/coreclr/debug/di/rsclass.cpp index 072958692d1a82..b698aad02cc119 100644 --- a/src/coreclr/debug/di/rsclass.cpp +++ b/src/coreclr/debug/di/rsclass.cpp @@ -784,7 +784,18 @@ void CordbClass::Init(ClassLoadLevel desiredLoadLevel) // full info load level if(desiredLoadLevel == FullInfo) { - IfFailThrow(pDac->GetClassInfo(vmTypeHandle, &m_classInfo)); + CallbackAccumulator acc; + + HRESULT hrEnum = pDac->EnumerateClassFields(vmTypeHandle, + &m_classInfo.m_objectSize, + &CallbackAccumulator::PushCallback, + &acc); + if (SUCCEEDED(hrEnum) && FAILED(acc.hrError)) + hrEnum = acc.hrError; + IfFailThrow(hrEnum); + + int fieldCount = (int)acc.items.Size(); + m_classInfo.m_fieldList.Init(fieldCount > 0 ? &acc.items[0] : NULL, fieldCount); BOOL fGotUnallocatedStatic = GotUnallocatedStatic(&m_classInfo.m_fieldList); diff --git a/src/coreclr/debug/di/rspriv.h b/src/coreclr/debug/di/rspriv.h index 9906aa94619bc5..7390bfffa2b959 100644 --- a/src/coreclr/debug/di/rspriv.h +++ b/src/coreclr/debug/di/rspriv.h @@ -8201,7 +8201,7 @@ class ValueHome // ReadProcessMemory. virtual void CreateInternalValue(CordbType * pType, - SIZE_T offset, + CORDB_ADDRESS offset, void * localAddress, ULONG32 size, ICorDebugValue ** ppValue) = 0; @@ -8265,7 +8265,7 @@ class RemoteValueHome: public ValueHome // creates an ICDValue for a field or array element or for the value type of a boxed object virtual void CreateInternalValue(CordbType * pType, - SIZE_T offset, + CORDB_ADDRESS offset, void * localAddress, ULONG32 size, ICorDebugValue ** ppValue); @@ -8318,7 +8318,7 @@ class RegisterValueHome: public ValueHome // creates an ICDValue for a field or array element or for the value type of a boxed object virtual void CreateInternalValue(CordbType * pType, - SIZE_T offset, + CORDB_ADDRESS offset, void * localAddress, ULONG32 size, ICorDebugValue ** ppValue); @@ -8385,7 +8385,7 @@ class HandleValueHome: public ValueHome // creates an ICDValue for a field or array element or for the value type of a boxed object virtual void CreateInternalValue(CordbType * pType, - SIZE_T offset, + CORDB_ADDRESS offset, void * localAddress, ULONG32 size, ICorDebugValue ** ppValue); diff --git a/src/coreclr/debug/di/rstype.cpp b/src/coreclr/debug/di/rstype.cpp index 4e1b06c4cd6a34..c2021026a30f46 100644 --- a/src/coreclr/debug/di/rstype.cpp +++ b/src/coreclr/debug/di/rstype.cpp @@ -1704,11 +1704,22 @@ HRESULT CordbType::InitInstantiationFieldInfo(BOOL fForceInit) // this may be called multiple times. Each call will discard previous values in m_fieldList and reinitialize // the list with updated information RSLockHolder lockHolder(pProcess->GetProcessLock()); - IfFailThrow(pProcess->GetDAC()->GetInstantiationFieldInfo(m_pClass->GetModule()->GetRuntimeAssembly(), + + CallbackAccumulator acc; + + HRESULT hrEnum = pProcess->GetDAC()->EnumerateInstantiationFields( + m_pClass->GetModule()->GetRuntimeAssembly(), m_typeHandleExact, typeHandleApprox, - &m_fieldList, - &m_objectSize)); + &m_objectSize, + &CallbackAccumulator::PushCallback, + &acc); + if (SUCCEEDED(hrEnum) && FAILED(acc.hrError)) + hrEnum = acc.hrError; + IfFailThrow(hrEnum); + + int fieldCount = (int)acc.items.Size(); + m_fieldList.Init(fieldCount > 0 ? &acc.items[0] : NULL, fieldCount); } } EX_CATCH_HRESULT(hr); diff --git a/src/coreclr/debug/di/valuehome.cpp b/src/coreclr/debug/di/valuehome.cpp index c9014871af9944..1317df02b810a4 100644 --- a/src/coreclr/debug/di/valuehome.cpp +++ b/src/coreclr/debug/di/valuehome.cpp @@ -620,7 +620,7 @@ void RemoteValueHome::SetValue(MemoryRange src, CordbType * pType) // creates an ICDValue for a field or array element or for the value type of a boxed object // virtual void RemoteValueHome::CreateInternalValue(CordbType * pType, - SIZE_T offset, + CORDB_ADDRESS offset, void * localAddress, ULONG32 size, ICorDebugValue ** ppValue) @@ -719,7 +719,7 @@ void RegisterValueHome::SetValue(MemoryRange src, CordbType * pType) // creates an ICDValue for a field or array element or for the value type of a boxed object // virtual void RegisterValueHome::CreateInternalValue(CordbType * pType, - SIZE_T offset, + CORDB_ADDRESS offset, void * localAddress, ULONG32 size, ICorDebugValue ** ppValue) @@ -930,7 +930,7 @@ void HandleValueHome::SetValue(MemoryRange src, CordbType * pType) // creates an ICDValue for a field or array element or for the value type of a boxed object // virtual void HandleValueHome::CreateInternalValue(CordbType * pType, - SIZE_T offset, + CORDB_ADDRESS offset, void * localAddress, ULONG32 size, ICorDebugValue ** ppValue) diff --git a/src/coreclr/debug/inc/dacdbiinterface.h b/src/coreclr/debug/inc/dacdbiinterface.h index 8bf01dcb955876..9f75fa5ec97038 100644 --- a/src/coreclr/debug/inc/dacdbiinterface.h +++ b/src/coreclr/debug/inc/dacdbiinterface.h @@ -1518,28 +1518,51 @@ IDacDbiInterface : public IUnknown virtual HRESULT STDMETHODCALLTYPE HasTypeParams(VMPTR_TypeHandle th, OUT BOOL * pResult) = 0; - // Get type information for a class + // Callback invoked for each FieldData computed by EnumerateClassFields or + // EnumerateInstantiationFields. // // Arguments: - // input: thExact - exact type handle for type - // output: - // pData - structure containing information about the class and its - // fields + // pFieldData - the FieldData for this field. Only valid for the duration of the + // callback; the consumer must copy the contents if it needs to keep + // them. The pointer is not null. + // pUserData - user data passed to the Enumerate* call. + typedef void (*FP_FIELDDATA_CALLBACK)(FieldData *pFieldData, CALLBACK_DATA pUserData); - virtual HRESULT STDMETHODCALLTYPE GetClassInfo(VMPTR_TypeHandle thExact, ClassInfo * pData) = 0; + // Enumerate the FieldData entries for a class. + // + // Arguments: + // input: thExact - exact type handle for type + // fpCallback - callback invoked once per field (in iterator order: + // regular FieldDescs first, then EnC-added instance, + // then EnC-added static). + // pUserData - opaque user data passed back to the callback. + // output: pObjectSize - size of the object in bytes for non-generic types; + // zero for open generic types. Always written on S_OK. + // + // The callback is invoked once per field and must not throw. + virtual HRESULT STDMETHODCALLTYPE EnumerateClassFields(VMPTR_TypeHandle thExact, + OUT SIZE_T *pObjectSize, + FP_FIELDDATA_CALLBACK fpCallback, + CALLBACK_DATA pUserData) = 0; - // get field information and object size for an instantiated generic + // Enumerate the FieldData entries for an instantiated generic type and report its + // instantiation-specific object size. // // Arguments: // input: vmAssembly - module containing metadata for the type - // thExact - exact type handle for type (may be NULL) - // thApprox - approximate type handle for the type - // output: - // pFieldList - array of structures containing information about the fields. Clears any previous - // contents. Allocated and initialized by this function. - // pObjectSize - size of the instantiated object - // - virtual HRESULT STDMETHODCALLTYPE GetInstantiationFieldInfo(VMPTR_Assembly vmAssembly, VMPTR_TypeHandle vmThExact, VMPTR_TypeHandle vmThApprox, OUT DacDbiArrayList * pFieldList, OUT SIZE_T * pObjectSize) = 0; + // vmThExact - exact type handle for type (may be NULL) + // vmThApprox - approximate type handle for the type + // fpCallback - callback invoked once per field. + // pUserData - opaque user data passed back to the callback. + // output: pObjectSize - size of the instantiated object. Always written on S_OK. + // + // The callback is invoked once per field and must not throw. + virtual HRESULT STDMETHODCALLTYPE EnumerateInstantiationFields(VMPTR_Assembly vmAssembly, + VMPTR_TypeHandle vmThExact, + VMPTR_TypeHandle vmThApprox, + OUT SIZE_T *pObjectSize, + FP_FIELDDATA_CALLBACK fpCallback, + CALLBACK_DATA pUserData) = 0; // use a type handle to get the information needed to create the corresponding RS CordbType instance // diff --git a/src/coreclr/debug/inc/dacdbistructures.h b/src/coreclr/debug/inc/dacdbistructures.h index 07637e944b0d28..3e19185b90e695 100644 --- a/src/coreclr/debug/inc/dacdbistructures.h +++ b/src/coreclr/debug/inc/dacdbistructures.h @@ -664,19 +664,19 @@ class MSLAYOUT FieldData BOOL OkToGetOrSetStaticAddress(); // If this is an instance field, store its offset - void SetInstanceOffset( SIZE_T offset ); + void SetInstanceOffset( ULONG64 offset ); // If this is a "normal" static, store its absolute address - void SetStaticAddress( TADDR addr ); + void SetStaticAddress( CORDB_ADDRESS addr ); // If this is an instance field, return its offset // Note that this offset is always a real offset (possibly larger than 22 bits), which isn't // necessarily the same as the overloaded FieldDesc.dwOffset field. - SIZE_T GetInstanceOffset(); + CORDB_ADDRESS GetInstanceOffset(); // If this is a "normal" static, get its absolute address // TLS and context-specific statics are "special". - TADDR GetStaticAddress(); + CORDB_ADDRESS GetStaticAddress(); // // Data members @@ -696,11 +696,11 @@ class MSLAYOUT FieldData private: // The m_fldInstanceOffset and m_pFldStaticAddress are mutually exclusive. Only one is ever set at a time. - SIZE_T m_fldInstanceOffset; // The offset of a field within an object instance + ULONG64 m_fldInstanceOffset; // The offset of a field within an object instance // For EnC fields, this isn't actually within the object instance, // but has been cooked to still be relative to the beginning of // the object. - TADDR m_pFldStaticAddress; // The absolute target address of a static field + CORDB_ADDRESS m_pFldStaticAddress; // The absolute target address of a static field PCCOR_SIGNATURE m_fldSignatureCache; // This is passed across as null. It is a RS-only cache, and SHOULD // NEVER BE ACCESSED DIRECTLY! diff --git a/src/coreclr/debug/inc/dacdbistructures.inl b/src/coreclr/debug/inc/dacdbistructures.inl index dec99ecbc85edd..78c44538c8662a 100644 --- a/src/coreclr/debug/inc/dacdbistructures.inl +++ b/src/coreclr/debug/inc/dacdbistructures.inl @@ -640,25 +640,25 @@ void FieldData::ClearFields() m_fldSignatureCache = NULL; m_fldSignatureCacheSize = 0; m_fldInstanceOffset = 0; - m_pFldStaticAddress = (TADDR)NULL; + m_pFldStaticAddress = (CORDB_ADDRESS)NULL; } inline BOOL FieldData::OkToGetOrSetInstanceOffset() { return (!m_fFldIsStatic && !m_fFldIsRVA && !m_fFldIsTLS && - m_fFldStorageAvailable && (m_pFldStaticAddress == (TADDR)NULL)); + m_fFldStorageAvailable && (m_pFldStaticAddress == (CORDB_ADDRESS)NULL)); } // If this is an instance field, store its offset inline -void FieldData::SetInstanceOffset(SIZE_T offset) +void FieldData::SetInstanceOffset(ULONG64 offset) { _ASSERTE(!m_fFldIsStatic); _ASSERTE(!m_fFldIsRVA); _ASSERTE(!m_fFldIsTLS); _ASSERTE(m_fFldStorageAvailable); - _ASSERTE(m_pFldStaticAddress == (TADDR)NULL); + _ASSERTE(m_pFldStaticAddress == (CORDB_ADDRESS)NULL); m_fldInstanceOffset = offset; } @@ -671,34 +671,34 @@ BOOL FieldData::OkToGetOrSetStaticAddress() // If this is a "normal" static, store its absolute address inline -void FieldData::SetStaticAddress(TADDR addr) +void FieldData::SetStaticAddress(CORDB_ADDRESS addr) { _ASSERTE(m_fFldIsStatic); _ASSERTE(!m_fFldIsTLS); _ASSERTE(m_fFldStorageAvailable); _ASSERTE(m_fldInstanceOffset == 0); - m_pFldStaticAddress = TADDR(addr); + m_pFldStaticAddress = addr; } // Get the offset of a field inline -SIZE_T FieldData::GetInstanceOffset() +CORDB_ADDRESS FieldData::GetInstanceOffset() { _ASSERTE(!m_fFldIsStatic); _ASSERTE(!m_fFldIsRVA); _ASSERTE(!m_fFldIsTLS); _ASSERTE(m_fFldStorageAvailable); - _ASSERTE(m_pFldStaticAddress == (TADDR)NULL); + _ASSERTE(m_pFldStaticAddress == (CORDB_ADDRESS)NULL); return m_fldInstanceOffset; } // Get the static address for a field inline -TADDR FieldData::GetStaticAddress() +CORDB_ADDRESS FieldData::GetStaticAddress() { _ASSERTE(m_fFldIsStatic); _ASSERTE(!m_fFldIsTLS); - _ASSERTE(m_fFldStorageAvailable || (m_pFldStaticAddress == (TADDR)NULL)); + _ASSERTE(m_fFldStorageAvailable || (m_pFldStaticAddress == (CORDB_ADDRESS)NULL)); _ASSERTE(m_fldInstanceOffset == 0); return m_pFldStaticAddress; } diff --git a/src/coreclr/inc/dacdbi.idl b/src/coreclr/inc/dacdbi.idl index 16768d8fc5992a..2831455ef965a4 100644 --- a/src/coreclr/inc/dacdbi.idl +++ b/src/coreclr/inc/dacdbi.idl @@ -21,7 +21,6 @@ struct SequencePoints; struct Debugger_STRData; struct DebuggerREGDISPLAY; struct NativeCodeFunctionData; -struct ClassInfo; struct FieldData; struct DebuggerIPCE_ExpandedTypeData; struct DebuggerIPCE_BasicTypeData; @@ -125,7 +124,6 @@ typedef int ArgInfoList; typedef int TypeParamsList; // DacDbiArrayList instantiations -typedef struct { void *pList; int nEntries; } DacDbiArrayList_FieldData; typedef struct { void *pList; int nEntries; } DacDbiArrayList_DacExceptionCallStackData; typedef struct { void *pList; int nEntries; } DacDbiArrayList_ExpandedTypeData; typedef struct { void *pList; int nEntries; } DacDbiArrayList_CORDB_ADDRESS; @@ -146,6 +144,7 @@ typedef BOOL (*FP_INTERNAL_FRAME_ENUMERATION_CALLBACK)(FramePointer fpFrame, CAL typedef void (*FP_HEAPSEGMENT_CALLBACK)(CORDB_ADDRESS rangeStart, CORDB_ADDRESS rangeEnd, int generation, ULONG heap, CALLBACK_DATA pUserData); typedef void (*FP_RCW_INTERFACE_CALLBACK)(CORDB_ADDRESS itfPtr, CALLBACK_DATA pUserData); typedef void (*FP_EXCEPTION_STACK_FRAME_CALLBACK)(VMPTR_AppDomain vmAppDomain, VMPTR_Assembly vmAssembly, CORDB_ADDRESS ip, mdMethodDef methodDef, BOOL isLastForeignExceptionFrame, CALLBACK_DATA pUserData); +typedef void (*FP_FIELDDATA_CALLBACK)(struct FieldData * pFieldData, CALLBACK_DATA pUserData); // @@ -305,13 +304,18 @@ interface IDacDbiInterface : IUnknown // Type HRESULT IsValueType([in] VMPTR_TypeHandle th, [out] BOOL * pResult); HRESULT HasTypeParams([in] VMPTR_TypeHandle th, [out] BOOL * pResult); - HRESULT GetClassInfo([in] VMPTR_TypeHandle thExact, [out] struct ClassInfo * pData); - HRESULT GetInstantiationFieldInfo( + HRESULT EnumerateClassFields( + [in] VMPTR_TypeHandle thExact, + [out] SIZE_T * pObjectSize, + [in] FP_FIELDDATA_CALLBACK fpCallback, + [in] CALLBACK_DATA pUserData); + HRESULT EnumerateInstantiationFields( [in] VMPTR_Assembly vmAssembly, [in] VMPTR_TypeHandle vmThExact, [in] VMPTR_TypeHandle vmThApprox, - [out] DacDbiArrayList_FieldData * pFieldList, - [out] SIZE_T * pObjectSize); + [out] SIZE_T * pObjectSize, + [in] FP_FIELDDATA_CALLBACK fpCallback, + [in] CALLBACK_DATA pUserData); HRESULT TypeHandleToExpandedTypeInfo([in] AreValueTypesBoxed boxed, [in] CORDB_ADDRESS vmTypeHandle, [out] struct DebuggerIPCE_ExpandedTypeData * pTypeInfo); HRESULT GetObjectExpandedTypeInfo([in] AreValueTypesBoxed boxed, [in] CORDB_ADDRESS addr, [out] struct DebuggerIPCE_ExpandedTypeData * pTypeInfo); HRESULT GetTypeHandle([in] VMPTR_Module vmModule, [in] mdTypeDef metadataToken, [out] VMPTR_TypeHandle * pRetVal); diff --git a/src/coreclr/inc/utilcode.h b/src/coreclr/inc/utilcode.h index 9e4d0b6dd608d9..b60d6a75255449 100644 --- a/src/coreclr/inc/utilcode.h +++ b/src/coreclr/inc/utilcode.h @@ -44,6 +44,7 @@ using std::nothrow; #include "clrnt.h" #include "random.h" +#include "cdacdata.h" #define WINDOWS_KERNEL32_DLLNAME_A "kernel32" #define WINDOWS_KERNEL32_DLLNAME_W W("kernel32") @@ -681,28 +682,40 @@ class SimpleList final // class. //***************************************************************************** +class CUnorderedArrayBase +{ + friend struct ::cdac_data; +protected: + int m_iCount; // # of elements used in the list. + TADDR m_pTable; // Pointer to the list of elements (opaque address; + // the templated derived class supplies the element type). + + CUnorderedArrayBase() + : m_iCount(0), m_pTable((TADDR)NULL) + { + } +}; + +template<> +struct cdac_data +{ + static constexpr size_t Count = offsetof(CUnorderedArrayBase, m_iCount); + static constexpr size_t Table = offsetof(CUnorderedArrayBase, m_pTable); +}; + template -class CUnorderedArrayWithAllocator +class CUnorderedArrayWithAllocator : public CUnorderedArrayBase { - int m_iCount; // # of elements used in the list. int m_iSize; // # of elements allocated in the list. -public: -#ifndef DACCESS_COMPILE - T *m_pTable; // Pointer to the list of elements. -#else - TADDR m_pTable; // Pointer to the list of elements. -#endif public: #ifndef DACCESS_COMPILE CUnorderedArrayWithAllocator() : - m_iCount(0), - m_iSize(0), - m_pTable(NULL) + m_iSize(0) { LIMITED_METHOD_CONTRACT; } @@ -710,29 +723,29 @@ class CUnorderedArrayWithAllocator { LIMITED_METHOD_CONTRACT; // Free the chunk of memory. - if (m_pTable != NULL) - ALLOCATOR::Free(this, m_pTable); + if (m_pTable != (TADDR)NULL) + ALLOCATOR::Free(this, reinterpret_cast(m_pTable)); } CUnorderedArrayWithAllocator(CUnorderedArrayWithAllocator const&) = delete; CUnorderedArrayWithAllocator& operator=(CUnorderedArrayWithAllocator const&) = delete; CUnorderedArrayWithAllocator(CUnorderedArrayWithAllocator&& other) - : m_iCount{ other.m_iCount } - , m_iSize{ other.m_iSize } - , m_pTable{ other.m_pTable } + : m_iSize{ other.m_iSize } { LIMITED_METHOD_CONTRACT; + m_iCount = other.m_iCount; + m_pTable = other.m_pTable; other.m_iCount = 0; other.m_iSize = 0; - other.m_pTable = NULL; + other.m_pTable = (TADDR)NULL; } CUnorderedArrayWithAllocator& operator=(CUnorderedArrayWithAllocator&& other) { LIMITED_METHOD_CONTRACT; if (this != &other) { - if (m_pTable != NULL) - ALLOCATOR::Free(this, m_pTable); + if (m_pTable != (TADDR)NULL) + ALLOCATOR::Free(this, reinterpret_cast(m_pTable)); m_iCount = other.m_iCount; m_iSize = other.m_iSize; @@ -740,7 +753,7 @@ class CUnorderedArrayWithAllocator other.m_iCount = 0; other.m_iSize = 0; - other.m_pTable = NULL; + other.m_pTable = (TADDR)NULL; } return *this; } @@ -753,8 +766,8 @@ class CUnorderedArrayWithAllocator { T* tmp = ALLOCATOR::AllocNoThrow(this, iGrowInc); if (tmp) { - ALLOCATOR::Free(this, m_pTable); - m_pTable = tmp; + ALLOCATOR::Free(this, reinterpret_cast(m_pTable)); + m_pTable = reinterpret_cast(tmp); m_iSize = iGrowInc; } } @@ -764,9 +777,10 @@ class CUnorderedArrayWithAllocator { WRAPPER_NO_CONTRACT; int iSize; + T* pTable = reinterpret_cast(m_pTable); if (iFirst + iCount < m_iCount) - memmove(&m_pTable[iFirst], &m_pTable[iFirst + iCount], sizeof(T) * (m_iCount - (iFirst + iCount))); + memmove(&pTable[iFirst], &pTable[iFirst + iCount], sizeof(T) * (m_iCount - (iFirst + iCount))); m_iCount -= iCount; @@ -775,9 +789,9 @@ class CUnorderedArrayWithAllocator { T *tmp = ALLOCATOR::AllocNoThrow(this, iSize); if (tmp) { - memcpy (tmp, m_pTable, iSize * sizeof(T)); - delete [] m_pTable; - m_pTable = tmp; + memcpy (tmp, pTable, iSize * sizeof(T)); + delete [] pTable; + m_pTable = reinterpret_cast(tmp); m_iSize = iSize; } } @@ -787,7 +801,7 @@ class CUnorderedArrayWithAllocator T *Table() { LIMITED_METHOD_CONTRACT; - return (m_pTable); + return reinterpret_cast(m_pTable); } T *Append() @@ -799,7 +813,7 @@ class CUnorderedArrayWithAllocator // The array should grow, if we can't fit one more element into the array. if (m_iSize <= m_iCount && GrowNoThrow() == NULL) return (NULL); - return (&m_pTable[m_iCount++]); + return (&reinterpret_cast(m_pTable)[m_iCount++]); } T *AppendThrowing() @@ -811,17 +825,18 @@ class CUnorderedArrayWithAllocator // The array should grow, if we can't fit one more element into the array. if (m_iSize <= m_iCount) Grow(); - return (&m_pTable[m_iCount++]); + return (&reinterpret_cast(m_pTable)[m_iCount++]); } void Delete(const T &Entry) { LIMITED_METHOD_CONTRACT; + T* pTable = reinterpret_cast(m_pTable); --m_iCount; for (int i=0; i <= m_iCount; ++i) - if (m_pTable[i] == Entry) + if (pTable[i] == Entry) { - m_pTable[i] = m_pTable[m_iCount]; + pTable[i] = pTable[m_iCount]; return; } @@ -832,20 +847,22 @@ class CUnorderedArrayWithAllocator void DeleteByIndex(int i) { LIMITED_METHOD_CONTRACT; + T* pTable = reinterpret_cast(m_pTable); --m_iCount; - m_pTable[i] = m_pTable[m_iCount]; + pTable[i] = pTable[m_iCount]; } void Swap(int i,int j) { LIMITED_METHOD_CONTRACT; T tmp; + T* pTable = reinterpret_cast(m_pTable); if (i == j) return; - tmp = m_pTable[i]; - m_pTable[i] = m_pTable[j]; - m_pTable[j] = tmp; + tmp = pTable[i]; + pTable[i] = pTable[j]; + pTable[j] = tmp; } #else @@ -890,13 +907,14 @@ T *CUnorderedArrayWithAllocator::GrowNoThrow() // NULL if { WRAPPER_NO_CONTRACT; T *pTemp; + T *pTable = reinterpret_cast(m_pTable); // try to allocate memory for reallocation. if ((pTemp = ALLOCATOR::AllocNoThrow(this, m_iSize+iGrowInc)) == NULL) return (NULL); - memcpy (pTemp, m_pTable, m_iSize*sizeof(T)); - ALLOCATOR::Free(this, m_pTable); - m_pTable = pTemp; + memcpy (pTemp, pTable, m_iSize*sizeof(T)); + ALLOCATOR::Free(this, pTable); + m_pTable = reinterpret_cast(pTemp); m_iSize += iGrowInc; _ASSERTE(m_iSize > 0); return (pTemp); @@ -909,13 +927,14 @@ T *CUnorderedArrayWithAllocator::Grow() // exception if c { WRAPPER_NO_CONTRACT; T *pTemp; + T *pTable = reinterpret_cast(m_pTable); // try to allocate memory for reallocation. pTemp = ALLOCATOR::AllocThrowing(this, m_iSize+iGrowInc); if (m_iSize > 0) - memcpy (pTemp, m_pTable, m_iSize*sizeof(T)); - ALLOCATOR::Free(this, m_pTable); - m_pTable = pTemp; + memcpy (pTemp, pTable, m_iSize*sizeof(T)); + ALLOCATOR::Free(this, pTable); + m_pTable = reinterpret_cast(pTemp); m_iSize += iGrowInc; _ASSERTE(m_iSize > 0); return (pTemp); diff --git a/src/coreclr/vm/ceeload.h b/src/coreclr/vm/ceeload.h index 24737cb71c8576..6153aca63f75e8 100644 --- a/src/coreclr/vm/ceeload.h +++ b/src/coreclr/vm/ceeload.h @@ -1728,6 +1728,9 @@ struct cdac_data static constexpr size_t MethodDefToILCodeVersioningStateMap = offsetof(Module, m_ILCodeVersioningStateMap); #endif // FEATURE_CODE_VERSIONING static constexpr size_t DynamicILBlobTable = offsetof(Module, m_debuggerSpecificData.m_pDynamicILBlobTable); +#ifdef FEATURE_METADATA_UPDATER + static constexpr size_t EnCClassList = offsetof(Module, m_ClassList); +#endif // FEATURE_METADATA_UPDATER }; // diff --git a/src/coreclr/vm/class.h b/src/coreclr/vm/class.h index bc5f2df4753b5a..5d324262e4d61d 100644 --- a/src/coreclr/vm/class.h +++ b/src/coreclr/vm/class.h @@ -1784,6 +1784,7 @@ template<> struct cdac_data static constexpr size_t NumStaticFields = offsetof(EEClass, m_NumStaticFields); static constexpr size_t NumThreadStaticFields = offsetof(EEClass, m_NumThreadStaticFields); static constexpr size_t NumNonVirtualSlots = offsetof(EEClass, m_NumNonVirtualSlots); + static constexpr size_t BaseSizePadding = offsetof(EEClass, m_cbBaseSizePadding); }; // -------------------------------------------------------------------------------------------- diff --git a/src/coreclr/vm/datadescriptor/datadescriptor.inc b/src/coreclr/vm/datadescriptor/datadescriptor.inc index 9eaaae3460dbd5..9c3a858fb6306b 100644 --- a/src/coreclr/vm/datadescriptor/datadescriptor.inc +++ b/src/coreclr/vm/datadescriptor/datadescriptor.inc @@ -292,8 +292,32 @@ CDAC_TYPE_FIELD(Module, T_POINTER, TypeRefToMethodTableMap, cdac_data::T CDAC_TYPE_FIELD(Module, T_POINTER, MethodDefToILCodeVersioningStateMap, cdac_data::MethodDefToILCodeVersioningStateMap) #endif // FEATURE_CODE_VERSIONING CDAC_TYPE_FIELD(Module, T_POINTER, DynamicILBlobTable, cdac_data::DynamicILBlobTable) +#ifdef FEATURE_METADATA_UPDATER +CDAC_TYPE_FIELD(Module, TYPE(UnorderedArrayBase), EnCClassList, cdac_data::EnCClassList) +#endif // FEATURE_METADATA_UPDATER CDAC_TYPE_END(Module) +CDAC_TYPE_BEGIN(UnorderedArrayBase) +CDAC_TYPE_INDETERMINATE(UnorderedArrayBase) +CDAC_TYPE_FIELD(UnorderedArrayBase, T_UINT32, Count, cdac_data::Count) +CDAC_TYPE_FIELD(UnorderedArrayBase, T_POINTER, Table, cdac_data::Table) +CDAC_TYPE_END(UnorderedArrayBase) + +#ifdef FEATURE_METADATA_UPDATER +CDAC_TYPE_BEGIN(EnCEEClassData) +CDAC_TYPE_INDETERMINATE(EnCEEClassData) +CDAC_TYPE_FIELD(EnCEEClassData, T_POINTER, MethodTable, cdac_data::MethodTable) +CDAC_TYPE_FIELD(EnCEEClassData, T_POINTER, AddedInstanceFields, cdac_data::AddedInstanceFields) +CDAC_TYPE_FIELD(EnCEEClassData, T_POINTER, AddedStaticFields, cdac_data::AddedStaticFields) +CDAC_TYPE_END(EnCEEClassData) + +CDAC_TYPE_BEGIN(EnCAddedFieldElement) +CDAC_TYPE_INDETERMINATE(EnCAddedFieldElement) +CDAC_TYPE_FIELD(EnCAddedFieldElement, T_POINTER, Next, cdac_data::Next) +CDAC_TYPE_FIELD(EnCAddedFieldElement, T_POINTER, FieldDesc, cdac_data::FieldDesc) +CDAC_TYPE_END(EnCAddedFieldElement) +#endif // FEATURE_METADATA_UPDATER + CDAC_TYPE_BEGIN(ModuleLookupMap) CDAC_TYPE_FIELD(ModuleLookupMap, T_POINTER, TableData, offsetof(LookupMapBase, pTable)) CDAC_TYPE_FIELD(ModuleLookupMap, T_POINTER, Next, offsetof(LookupMapBase, pNext)) @@ -480,6 +504,7 @@ CDAC_TYPE_FIELD(EEClass, T_UINT16, NumInstanceFields, cdac_data::NumIns CDAC_TYPE_FIELD(EEClass, T_UINT16, NumStaticFields, cdac_data::NumStaticFields) CDAC_TYPE_FIELD(EEClass, T_UINT16, NumThreadStaticFields, cdac_data::NumThreadStaticFields) CDAC_TYPE_FIELD(EEClass, T_UINT16, NumNonVirtualSlots, cdac_data::NumNonVirtualSlots) +CDAC_TYPE_FIELD(EEClass, T_UINT8, BaseSizePadding, cdac_data::BaseSizePadding) CDAC_TYPE_END(EEClass) CDAC_TYPE_BEGIN(GenericsDictInfo) @@ -1549,6 +1574,7 @@ CDAC_GLOBAL(StaticsPointerMask, T_NUINT, DynamicStaticsInfo::STATICSPOINTERMASK) CDAC_GLOBAL(PtrArrayOffsetToDataArray, T_NUINT, offsetof(PtrArray, m_Array)) CDAC_GLOBAL(NumberOfTlsOffsetsNotUsedInNoncollectibleArray, T_UINT8, NUMBER_OF_TLSOFFSETS_NOT_USED_IN_NONCOLLECTIBLE_ARRAY) CDAC_GLOBAL(MaxClrNotificationArgs, T_UINT32, MAX_CLR_NOTIFICATION_ARGS) +CDAC_GLOBAL(FieldOffsetNewEnc, T_UINT32, FIELD_OFFSET_NEW_ENC) CDAC_GLOBAL(FieldOffsetBigRVA, T_UINT32, FIELD_OFFSET_BIG_RVA) CDAC_GLOBAL(FieldOffsetDynamicRVA, T_UINT32, FIELD_OFFSET_DYNAMIC_RVA) CDAC_GLOBAL_POINTER(ClrNotificationArguments, &::g_clrNotificationArguments) @@ -1642,6 +1668,9 @@ CDAC_GLOBAL_CONTRACT(Debugger, c1) #endif // DEBUGGING_SUPPORTED && !TARGET_WASM CDAC_GLOBAL_CONTRACT(DebugInfo, c2) CDAC_GLOBAL_CONTRACT(EcmaMetadata, c1) +#ifdef FEATURE_METADATA_UPDATER +CDAC_GLOBAL_CONTRACT(EditAndContinue, c1) +#endif // FEATURE_METADATA_UPDATER CDAC_GLOBAL_CONTRACT(Exception, c1) CDAC_GLOBAL_CONTRACT(ExecutionManager, c2) CDAC_GLOBAL_CONTRACT(GCInfo, c1) diff --git a/src/coreclr/vm/encee.h b/src/coreclr/vm/encee.h index 53079f0052769b..d7c4fc0a969f47 100644 --- a/src/coreclr/vm/encee.h +++ b/src/coreclr/vm/encee.h @@ -175,6 +175,23 @@ class EnCEEClassData // Linked list of EnCFieldDescs for all the added static fields PTR_EnCAddedFieldElement m_pAddedStaticFields; + + friend struct ::cdac_data; +}; + +template<> +struct cdac_data +{ + static constexpr size_t MethodTable = offsetof(EnCEEClassData, m_pMT); + static constexpr size_t AddedInstanceFields = offsetof(EnCEEClassData, m_pAddedInstanceFields); + static constexpr size_t AddedStaticFields = offsetof(EnCEEClassData, m_pAddedStaticFields); +}; + +template<> +struct cdac_data +{ + static constexpr size_t Next = offsetof(EnCAddedFieldElement, m_next); + static constexpr size_t FieldDesc = offsetof(EnCAddedFieldElement, m_fieldDesc); }; //--------------------------------------------------------------------------------------- diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/ContractRegistry.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/ContractRegistry.cs index a539964b0fe7d6..e5a09db99311c2 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/ContractRegistry.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/ContractRegistry.cs @@ -129,6 +129,11 @@ public abstract class ContractRegistry /// public virtual IDebugger Debugger => GetContract(); + /// + /// Gets an instance of the EditAndContinue contract for the target. + /// + public virtual IEditAndContinue EditAndContinue => GetContract(); + /// /// Attempts to get an instance of the requested contract for the target. /// diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IEditAndContinue.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IEditAndContinue.cs new file mode 100644 index 00000000000000..ec0d0b96398dd0 --- /dev/null +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IEditAndContinue.cs @@ -0,0 +1,20 @@ +// 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; + +namespace Microsoft.Diagnostics.DataContractReader.Contracts; + +public interface IEditAndContinue : IContract +{ + static string IContract.Name { get; } = nameof(EditAndContinue); + + IEnumerable EnumerateAddedFieldDescs(TypeHandle typeHandle, bool staticFields) + => throw new NotImplementedException(); +} + +public readonly struct EditAndContinue : IEditAndContinue +{ + // Everything throws NotImplementedException +} diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IRuntimeTypeSystem.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IRuntimeTypeSystem.cs index 59d5315292764a..976895634c6f5d 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IRuntimeTypeSystem.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IRuntimeTypeSystem.cs @@ -122,6 +122,7 @@ public interface IRuntimeTypeSystem : IContract TargetCodePointer GetSlot(TypeHandle typeHandle, uint slot) => throw new NotImplementedException(); uint GetBaseSize(TypeHandle typeHandle) => throw new NotImplementedException(); + uint GetNumInstanceFieldBytes(TypeHandle typeHandle) => 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) uint GetComponentSize(TypeHandle typeHandle) => throw new NotImplementedException(); @@ -271,8 +272,10 @@ public interface IRuntimeTypeSystem : IContract uint GetFieldDescMemberDef(TargetPointer fieldDescPointer) => throw new NotImplementedException(); bool IsFieldDescThreadStatic(TargetPointer fieldDescPointer) => throw new NotImplementedException(); bool IsFieldDescStatic(TargetPointer fieldDescPointer) => throw new NotImplementedException(); + bool IsFieldDescRVA(TargetPointer fieldDescPointer) => throw new NotImplementedException(); + bool IsFieldDescEnCNew(TargetPointer fieldDescPointer) => throw new NotImplementedException(); CorElementType GetFieldDescType(TargetPointer fieldDescPointer) => throw new NotImplementedException(); - uint GetFieldDescOffset(TargetPointer fieldDescPointer, FieldDefinition fieldDef) => throw new NotImplementedException(); + uint GetFieldDescOffset(TargetPointer fieldDescPointer, FieldDefinition? fieldDef) => throw new NotImplementedException(); TargetPointer GetFieldDescByName(TypeHandle typeHandle, string fieldName) => throw new NotImplementedException(); TargetPointer GetFieldDescStaticAddress(TargetPointer fieldDescPointer, bool unboxValueTypes = true) => throw new NotImplementedException(); TargetPointer GetFieldDescThreadStaticAddress(TargetPointer fieldDescPointer, TargetPointer thread, bool unboxValueTypes = true) => throw new NotImplementedException(); diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Constants.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Constants.cs index 22f5e22764949c..aa69ba3e453cf2 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Constants.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Constants.cs @@ -94,6 +94,7 @@ public static class Globals public const string NumberOfTlsOffsetsNotUsedInNoncollectibleArray = nameof(NumberOfTlsOffsetsNotUsedInNoncollectibleArray); public const string MaxClrNotificationArgs = nameof(MaxClrNotificationArgs); public const string ClrNotificationArguments = nameof(ClrNotificationArguments); + public const string FieldOffsetNewEnc = nameof(FieldOffsetNewEnc); public const string FieldOffsetBigRVA = nameof(FieldOffsetBigRVA); public const string FieldOffsetDynamicRVA = nameof(FieldOffsetDynamicRVA); public const string PlatformMetadata = nameof(PlatformMetadata); diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/EditAndContinue_1.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/EditAndContinue_1.cs new file mode 100644 index 00000000000000..3333227906e4b4 --- /dev/null +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/EditAndContinue_1.cs @@ -0,0 +1,73 @@ +// 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 Microsoft.Diagnostics.DataContractReader.RuntimeTypeSystemHelpers; + +namespace Microsoft.Diagnostics.DataContractReader.Contracts; + +internal readonly struct EditAndContinue_1 : IEditAndContinue +{ + private readonly Target _target; + + public EditAndContinue_1(Target target) + { + _target = target; + } + + IEnumerable IEditAndContinue.EnumerateAddedFieldDescs(TypeHandle typeHandle, bool staticFields) + { + // Only MethodTable type handles can have EnC-added fields. TypeDescs (TypeVar, FnPtr, etc.) cannot. + if (!typeHandle.IsMethodTable()) + yield break; + + IRuntimeTypeSystem rts = _target.Contracts.RuntimeTypeSystem; + ILoader loader = _target.Contracts.Loader; + + TargetPointer modulePtr = rts.GetModule(typeHandle); + if (modulePtr == TargetPointer.Null) + yield break; + + ModuleHandle moduleHandle = loader.GetModuleHandleFromModulePtr(modulePtr); + if (!loader.GetFlags(moduleHandle).HasFlag(ModuleFlags.EditAndContinue)) + yield break; + + // The Module field backing the EnC class list is only present in builds compiled with + // FEATURE_METADATA_UPDATER. If absent, there can be no EnC data. + Data.Module module = _target.ProcessedData.GetOrAdd(modulePtr); + if (module.EnCClassList is not TargetPointer classListAddr) + yield break; + + Data.UnorderedArrayBase classList = _target.ProcessedData.GetOrAdd(classListAddr); + if (classList.Count == 0 || classList.Table == TargetPointer.Null) + yield break; + + // Locate the EnCEEClassData entry for this MethodTable. + TargetPointer mtPtr = typeHandle.Address; + ulong ptrSize = (ulong)_target.PointerSize; + TargetPointer classDataPtr = TargetPointer.Null; + for (uint i = 0; i < classList.Count; i++) + { + TargetPointer entry = _target.ReadPointer(classList.Table + i * ptrSize); + if (entry == TargetPointer.Null) + continue; + Data.EnCEEClassData candidate = _target.ProcessedData.GetOrAdd(entry); + if (candidate.MethodTable == mtPtr) + { + classDataPtr = entry; + break; + } + } + if (classDataPtr == TargetPointer.Null) + yield break; + + Data.EnCEEClassData classData = _target.ProcessedData.GetOrAdd(classDataPtr); + TargetPointer node = staticFields ? classData.AddedStaticFields : classData.AddedInstanceFields; + while (node != TargetPointer.Null) + { + Data.EnCAddedFieldElement element = _target.ProcessedData.GetOrAdd(node); + yield return element.FieldDesc; + node = element.Next; + } + } +} diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeTypeSystem_1.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeTypeSystem_1.cs index 1b8f3ad37049ab..6bca3c1678a6d9 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeTypeSystem_1.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeTypeSystem_1.cs @@ -521,6 +521,8 @@ public TargetPointer GetModule(TypeHandle typeHandle) public uint GetBaseSize(TypeHandle typeHandle) => !typeHandle.IsMethodTable() ? (uint)0 : _methodTables[typeHandle.Address].Flags.BaseSize; + public uint GetNumInstanceFieldBytes(TypeHandle typeHandle) => !typeHandle.IsMethodTable() ? (uint)0 : _methodTables[typeHandle.Address].Flags.BaseSize - GetClassData(typeHandle).BaseSizePadding; + public uint GetComponentSize(TypeHandle typeHandle) => !typeHandle.IsMethodTable() ? (uint)0 : _methodTables[typeHandle.Address].Flags.ComponentSize; public TargetPointer GetClassPointer(TypeHandle typeHandle) @@ -1898,7 +1900,7 @@ bool IRuntimeTypeSystem.IsFieldDescThreadStatic(TargetPointer fieldDescPointer) return (fieldDesc.DWord1 & (uint)FieldDescFlags1.IsThreadStatic) != 0; } - private bool IsFieldDescRVA(TargetPointer fieldDescPointer) + bool IRuntimeTypeSystem.IsFieldDescRVA(TargetPointer fieldDescPointer) { Data.FieldDesc fieldDesc = _target.ProcessedData.GetOrAdd(fieldDescPointer); return (fieldDesc.DWord1 & (uint)FieldDescFlags1.IsRVA) != 0; @@ -1910,6 +1912,13 @@ bool IRuntimeTypeSystem.IsFieldDescStatic(TargetPointer fieldDescPointer) return (fieldDesc.DWord1 & (uint)FieldDescFlags1.IsStatic) != 0; } + bool IRuntimeTypeSystem.IsFieldDescEnCNew(TargetPointer fieldDescPointer) + { + Data.FieldDesc fieldDesc = _target.ProcessedData.GetOrAdd(fieldDescPointer); + uint offset = fieldDesc.DWord2 & (uint)FieldDescFlags2.OffsetMask; + return offset == _target.ReadGlobal(Constants.Globals.FieldOffsetNewEnc); + } + CorElementType IRuntimeTypeSystem.GetFieldDescType(TargetPointer fieldDescPointer) { Data.FieldDesc fieldDesc = _target.ProcessedData.GetOrAdd(fieldDescPointer); @@ -1917,12 +1926,14 @@ CorElementType IRuntimeTypeSystem.GetFieldDescType(TargetPointer fieldDescPointe return (CorElementType)((fieldDesc.DWord2 & (uint)FieldDescFlags2.TypeMask) >> TYPE_MASK_OFFSET); } - uint IRuntimeTypeSystem.GetFieldDescOffset(TargetPointer fieldDescPointer, FieldDefinition fieldDef) + uint IRuntimeTypeSystem.GetFieldDescOffset(TargetPointer fieldDescPointer, FieldDefinition? fieldDef) { Data.FieldDesc fieldDesc = _target.ProcessedData.GetOrAdd(fieldDescPointer); if (fieldDesc.DWord2 == _target.ReadGlobal(Constants.Globals.FieldOffsetBigRVA)) { - return (uint)fieldDef.GetRelativeVirtualAddress(); + if (fieldDef is null) + throw new ArgumentNullException(nameof(fieldDef), "Field definition is required for big RVA fields"); + return (uint)fieldDef.Value.GetRelativeVirtualAddress(); } return fieldDesc.DWord2 & (uint)FieldDescFlags2.OffsetMask; } @@ -2023,7 +2034,7 @@ private TargetPointer GetFieldDescStaticOrThreadStaticAddress(TargetPointer fiel FieldDefinition fieldDef = mdReader.GetFieldDefinition(fieldHandle); uint offset = ((IRuntimeTypeSystem)this).GetFieldDescOffset(fieldDescPointer, fieldDef); - bool isRVA = IsFieldDescRVA(fieldDescPointer); + bool isRVA = ((IRuntimeTypeSystem)this).IsFieldDescRVA(fieldDescPointer); TargetPointer handleAddr = GetStaticAddressHandle(@base, offset, isRVA, fieldDescPointer, moduleHandle); if (unboxValueTypes && type == CorElementType.ValueType && !isRVA) { diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/CoreCLRContracts.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/CoreCLRContracts.cs index f477f3ed9bfd8b..e0589d5d184d69 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/CoreCLRContracts.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/CoreCLRContracts.cs @@ -72,5 +72,7 @@ public static void Register(ContractRegistry registry) registry.Register("c1", static t => new ExecutionManager_1(t)); registry.Register("c2", static t => new ExecutionManager_2(t)); + + registry.Register("c1", static t => new EditAndContinue_1(t)); } } diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/EEClass.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/EEClass.cs index 06732c5a1a3fca..2fcbd027729c4b 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/EEClass.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/EEClass.cs @@ -24,4 +24,5 @@ internal sealed partial class EEClass : IData [Field] public ushort NumThreadStaticFields { get; } [Field] public TargetPointer FieldDescList { get; } [Field] public ushort NumNonVirtualSlots { get; } + [Field] public byte BaseSizePadding { get; } } diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/EnCAddedFieldElement.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/EnCAddedFieldElement.cs new file mode 100644 index 00000000000000..ca987564cc9ea1 --- /dev/null +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/EnCAddedFieldElement.cs @@ -0,0 +1,11 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.Diagnostics.DataContractReader.Data; + +[CdacType(nameof(DataType.EnCAddedFieldElement))] +internal sealed partial class EnCAddedFieldElement : IData +{ + [Field] public TargetPointer Next { get; } + [FieldAddress] public TargetPointer FieldDesc { get; } +} diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/EnCEEClassData.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/EnCEEClassData.cs new file mode 100644 index 00000000000000..72f2ca70c3467f --- /dev/null +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/EnCEEClassData.cs @@ -0,0 +1,12 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.Diagnostics.DataContractReader.Data; + +[CdacType(nameof(DataType.EnCEEClassData))] +internal sealed partial class EnCEEClassData : IData +{ + [Field] public TargetPointer MethodTable { get; } + [Field] public TargetPointer AddedInstanceFields { get; } + [Field] public TargetPointer AddedStaticFields { get; } +} diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Module.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Module.cs index e10cbaf10d67ad..58d29e1d81ea2e 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Module.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Module.cs @@ -30,5 +30,6 @@ internal sealed partial class Module : IData [FieldAddress] public TargetPointer TypeDefToMethodTableMap { get; } [FieldAddress] public TargetPointer TypeRefToMethodTableMap { get; } [FieldAddress] public TargetPointer MethodDefToILCodeVersioningStateMap { get; } + [FieldAddress] public TargetPointer? EnCClassList { get; } [Field] public TargetPointer DynamicILBlobTable { get; } } diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/UnorderedArrayBase.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/UnorderedArrayBase.cs new file mode 100644 index 00000000000000..22ba066faf1107 --- /dev/null +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/UnorderedArrayBase.cs @@ -0,0 +1,11 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.Diagnostics.DataContractReader.Data; + +[CdacType(nameof(DataType.UnorderedArrayBase))] +internal sealed partial class UnorderedArrayBase : IData +{ + [Field] public uint Count { get; } + [Field] public TargetPointer Table { get; } +} diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/DataType.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/DataType.cs index 145a2278801878..c4f27438bb305f 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/DataType.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/DataType.cs @@ -199,6 +199,10 @@ public enum DataType TableSegment, CardTableInfo, RegionFreeList, + + EnCEEClassData, + EnCAddedFieldElement, + UnorderedArrayBase, } public static class DataTypeTargetExtensions diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.DataGenerator/Emitter.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.DataGenerator/Emitter.cs index bb1080ca64efb5..78679c89d1ee3a 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.DataGenerator/Emitter.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.DataGenerator/Emitter.cs @@ -224,10 +224,22 @@ private static void EmitMemberAssignment(StringBuilder sb, MemberModel member) break; } case MemberKind.FieldAddress: - sb.AppendLine($" {{"); - sb.AppendLine($" layouts.Select(address, out var t, out var b, out var n, {NameArgs(member)});"); - sb.AppendLine($" {member.Name} = b + (ulong)t.Fields[n].Offset;"); - sb.AppendLine($" }}"); + if (member.IsOptional) + { + sb.AppendLine($" {{"); + sb.AppendLine($" if (layouts.TrySelect(address, out var t, out var b, out var n, {NameArgs(member)}))"); + sb.AppendLine($" {member.Name} = b + (ulong)t.Fields[n].Offset;"); + sb.AppendLine($" else"); + sb.AppendLine($" {member.Name} = null;"); + sb.AppendLine($" }}"); + } + else + { + sb.AppendLine($" {{"); + sb.AppendLine($" layouts.Select(address, out var t, out var b, out var n, {NameArgs(member)});"); + sb.AppendLine($" {member.Name} = b + (ulong)t.Fields[n].Offset;"); + sb.AppendLine($" }}"); + } break; case MemberKind.InstanceDataStart: sb.AppendLine($" {member.Name} = address + layouts.InstanceSize;"); diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.DataGenerator/Parser.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.DataGenerator/Parser.cs index 9dd288f427c80e..216f59107bf423 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.DataGenerator/Parser.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.DataGenerator/Parser.cs @@ -263,6 +263,11 @@ private static bool TryParseProperty(IPropertySymbol prop, out MemberModel? mode string[]? rawNames = ctorNames ?? GetNamedStringArray(addrAttr, "Names"); bool usePropertyName = !addrAttr.NamedArguments.Any(kv => kv.Key == "UsePropertyName" && kv.Value.Value is false); string descriptorName = rawNames is { Length: > 0 } ? rawNames[0] : prop.Name; + + bool isNullable = prop.Type is INamedTypeSymbol named + && named.IsGenericType + && named.ConstructedFrom.ToDisplayString() == "System.Nullable"; + model = new MemberModel( Name: prop.Name, Kind: MemberKind.FieldAddress, @@ -270,8 +275,8 @@ private static bool TryParseProperty(IPropertySymbol prop, out MemberModel? mode PropertyOrReturnTypeFqn: prop.Type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat), ReadKind: FieldReadKind.Pointer, DataTypeArgumentFqn: null, - IsOptional: false, - IsNullable: false, + IsOptional: isNullable, + IsNullable: isNullable, RawOffset: null, LittleEndian: false, HasSetter: false, diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/DacDbiImpl.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/DacDbiImpl.cs index 0c0aed2a623aa8..dd43eb471b29b9 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/DacDbiImpl.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/DacDbiImpl.cs @@ -1743,11 +1743,266 @@ public int HasTypeParams(ulong vmTypeHandle, Interop.BOOL* pResult) return hr; } - public int GetClassInfo(ulong thExact, nint pData) - => LegacyFallbackHelper.CanFallback() && _legacy is not null ? _legacy.GetClassInfo(thExact, pData) : HResults.E_NOTIMPL; + public int EnumerateClassFields(ulong thExact, nuint* pObjectSize, delegate* unmanaged fpCallback, nint pUserData) + { + nuint cdacObjectSize = 0; + List? cdacFields = null; +#if DEBUG + if (_legacy is not null) + cdacFields = new(); +#endif + int hr = HResults.S_OK; + try + { + IRuntimeTypeSystem rts = _target.Contracts.RuntimeTypeSystem; + TypeHandle thExactHandle = rts.GetTypeHandle(thExact); + // Native semantics: thApprox is the same TypeHandle that was passed in. + if (thExactHandle.IsNull) + throw Marshal.GetExceptionForHR(CorDbgHResults.CORDBG_E_CLASS_NOT_LOADED)!; + + TypeHandle thApprox = thExactHandle; + + // For Generic classes the object size only comes through with an instantiated type. + cdacObjectSize = 0; + if (rts.GetInstantiation(thApprox).Length == 0) + { + cdacObjectSize = rts.GetNumInstanceFieldBytes(thApprox); + } + + CollectFieldsForDbi(rts, thExactHandle, thApprox, fpCallback, pUserData, cdacFields); + } + catch (System.Exception ex) + { + hr = ex.HResult; + } + + if (hr == HResults.S_OK && pObjectSize != null) + *pObjectSize = cdacObjectSize; + +#if DEBUG + if (_legacy is not null) + { + ValidateEnumerateFieldsAgainstLegacy( + nameof(IDacDbiInterface.EnumerateClassFields), + cdacObjectSize, + cdacFields, + hr, + (pSize, pUser) => _legacy!.EnumerateClassFields(thExact, pSize, (delegate* unmanaged)&CollectFieldDataCallback, pUser)); + } +#endif + return hr; + } + + public int EnumerateInstantiationFields(ulong vmAssembly, ulong vmThExact, ulong vmThApprox, nuint* pObjectSize, delegate* unmanaged fpCallback, nint pUserData) + { + nuint cdacObjectSize = 0; + List? cdacFields = null; +#if DEBUG + if (_legacy is not null) + cdacFields = new(); +#endif + int hr = HResults.S_OK; + try + { + IRuntimeTypeSystem rts = _target.Contracts.RuntimeTypeSystem; + TypeHandle thExactHandle = rts.GetTypeHandle(vmThExact); + TypeHandle thApproxHandle = rts.GetTypeHandle(vmThApprox); + if (thApproxHandle.IsNull) + throw Marshal.GetExceptionForHR(CorDbgHResults.CORDBG_E_CLASS_NOT_LOADED)!; + + cdacObjectSize = rts.GetNumInstanceFieldBytes(thApproxHandle); + + CollectFieldsForDbi(rts, thExactHandle, thApproxHandle, fpCallback, pUserData, cdacFields); + } + catch (System.Exception ex) + { + hr = ex.HResult; + } + + if (hr == HResults.S_OK && pObjectSize != null) + *pObjectSize = cdacObjectSize; + +#if DEBUG + if (_legacy is not null) + { + ValidateEnumerateFieldsAgainstLegacy( + nameof(IDacDbiInterface.EnumerateInstantiationFields), + cdacObjectSize, + cdacFields, + hr, + (pSize, pUser) => _legacy!.EnumerateInstantiationFields(vmAssembly, vmThExact, vmThApprox, pSize, (delegate* unmanaged)&CollectFieldDataCallback, pUser)); + } +#endif + return hr; + } + + // Mirrors native DacDbiInterfaceImpl::CollectFields. Iterates the regular FieldDescs first + // then EnC-added instance fields, then EnC-added static fields. + private void CollectFieldsForDbi( + IRuntimeTypeSystem rts, + TypeHandle thExact, + TypeHandle thApprox, + delegate* unmanaged fpCallback, + nint pUserData, + List? cdacFields) + { + TargetPointer gcStaticsBase = TargetPointer.Null; + TargetPointer nonGCStaticsBase = TargetPointer.Null; + if (!thExact.IsNull && !rts.IsCollectible(thExact)) + { + gcStaticsBase = rts.GetGCStaticsBasePointer(thExact); + nonGCStaticsBase = rts.GetNonGCStaticsBasePointer(thExact); + } + + foreach (TargetPointer fdPtr in rts.GetFieldDescList(thApprox)) + EmitFieldData(rts, fdPtr, gcStaticsBase, nonGCStaticsBase, fpCallback, pUserData, cdacFields); + + if (_target.Contracts.TryGetContract(out IEditAndContinue enc)) + { + foreach (TargetPointer fdPtr in enc.EnumerateAddedFieldDescs(thApprox, staticFields: false)) + EmitFieldData(rts, fdPtr, gcStaticsBase, nonGCStaticsBase, fpCallback, pUserData, cdacFields); + foreach (TargetPointer fdPtr in enc.EnumerateAddedFieldDescs(thApprox, staticFields: true)) + EmitFieldData(rts, fdPtr, gcStaticsBase, nonGCStaticsBase, fpCallback, pUserData, cdacFields); + } + } - public int GetInstantiationFieldInfo(ulong vmAssembly, ulong vmTypeHandle, ulong vmExactMethodTable, nint pFieldList, nuint* pObjectSize) - => LegacyFallbackHelper.CanFallback() && _legacy is not null ? _legacy.GetInstantiationFieldInfo(vmAssembly, vmTypeHandle, vmExactMethodTable, pFieldList, pObjectSize) : HResults.E_NOTIMPL; + // Mirrors native DacDbiInterfaceImpl::ComputeFieldData for one FieldDesc and then invokes the + // user callback. + private static void EmitFieldData( + IRuntimeTypeSystem rts, + TargetPointer fdPtr, + TargetPointer gcStaticsBase, + TargetPointer nonGCStaticsBase, + delegate* unmanaged fpCallback, + nint pUserData, + List? cdacFields) + { + bool isStatic = rts.IsFieldDescStatic(fdPtr); + CorElementType type = rts.GetFieldDescType(fdPtr); + bool isPrimitive = IsPrimitiveType(type); + + FieldData fd = default; + fd.m_fldMetadataToken = rts.GetFieldDescMemberDef(fdPtr); + fd.m_fFldIsStatic = isStatic ? (byte)1 : (byte)0; + fd.m_fFldIsPrimitive = isPrimitive ? (byte)1 : (byte)0; + fd.m_fldSignatureCache = 0; + fd.m_fldSignatureCacheSize = 0; + fd.m_vmFieldDesc = fdPtr.Value; + + bool isEnCNew = rts.IsFieldDescEnCNew(fdPtr); + if (isEnCNew) + { + // Mirrors native: storage not yet available; carry the FieldDesc pointer through + // m_vmFieldDesc but leave all "is" flags false. + fd.m_fFldStorageAvailable = Interop.BOOL.FALSE; + fd.m_fFldIsTLS = 0; + fd.m_fFldIsRVA = 0; + fd.m_fFldIsCollectibleStatic = 0; + } + if (!isEnCNew) + { + fd.m_fFldStorageAvailable = Interop.BOOL.TRUE; + bool isTLS = rts.IsFieldDescThreadStatic(fdPtr); + bool isRVA = rts.IsFieldDescRVA(fdPtr); + bool isCollectibleStatic = false; + if (isStatic) + { + TargetPointer enclosingMT = rts.GetMTOfEnclosingClass(fdPtr); + if (enclosingMT != TargetPointer.Null) + { + TypeHandle enclosingTh = rts.GetTypeHandle(enclosingMT); + isCollectibleStatic = rts.IsCollectible(enclosingTh); + } + } + + fd.m_fFldIsTLS = isTLS ? (byte)1 : (byte)0; + fd.m_fFldIsRVA = isRVA ? (byte)1 : (byte)0; + fd.m_fFldIsCollectibleStatic = isCollectibleStatic ? (byte)1 : (byte)0; + + if (isStatic) + { + if (isRVA) + { + TargetPointer addr = rts.GetFieldDescStaticAddress(fdPtr, unboxValueTypes: false); + fd.m_pFldStaticAddress = addr.Value; + } + else if (!isTLS && !isCollectibleStatic) + { + TargetPointer baseAddr = isPrimitive ? nonGCStaticsBase : gcStaticsBase; + if (baseAddr != TargetPointer.Null) + { + uint offset = rts.GetFieldDescOffset(fdPtr, null); + fd.m_pFldStaticAddress = baseAddr + offset; + } + } + } + else + { + // instance: store the offset + uint offset = rts.GetFieldDescOffset(fdPtr, null); + fd.m_fldInstanceOffset = offset; + } + } + +#if DEBUG + cdacFields?.Add(fd); +#endif + fpCallback(&fd, (void*)pUserData); + } + + private static bool IsPrimitiveType(CorElementType type) + { + return (type < CorElementType.Ptr) + || type == CorElementType.I + || type == CorElementType.U; + } + +#if DEBUG + [UnmanagedCallersOnly] + private static void CollectFieldDataCallback(FieldData* data, void* pUserData) + { + GCHandle handle = GCHandle.FromIntPtr((nint)pUserData); + ((List)handle.Target!).Add(*data); + } + + private delegate int LegacyEnumerateFieldsFn(nuint* pObjectSize, nint pUserData); + + private static void ValidateEnumerateFieldsAgainstLegacy(string label, nuint cdacObjectSize, List? cdacFields, int hr, LegacyEnumerateFieldsFn legacyEnumerate) + { + List dacFields = new(); + GCHandle dacHandle = GCHandle.Alloc(dacFields); + nuint dacObjectSize = 0; + int hrLocal = legacyEnumerate(&dacObjectSize, GCHandle.ToIntPtr(dacHandle)); + dacHandle.Free(); + Debug.ValidateHResult(hr, hrLocal); + if (hr == HResults.S_OK) + { + Debug.Assert(cdacObjectSize == dacObjectSize, $"{label} object size mismatch - cDAC: {cdacObjectSize}, DAC: {dacObjectSize}"); + AssertFieldListsEqual(cdacFields, dacFields, label); + } + } + + private static void AssertFieldListsEqual(List? cdacFields, List dacFields, string label) + { + Debug.Assert(cdacFields!.Count == dacFields.Count, $"{label} field count mismatch - cDAC: {cdacFields!.Count}, DAC: {dacFields.Count}"); + int n = Math.Min(cdacFields!.Count, dacFields.Count); + for (int i = 0; i < n; i++) + { + FieldData c = cdacFields![i]; + FieldData d = dacFields[i]; + Debug.Assert(c.m_fldMetadataToken == d.m_fldMetadataToken, $"{label} field[{i}] m_fldMetadataToken mismatch - cDAC: 0x{c.m_fldMetadataToken:x}, DAC: 0x{d.m_fldMetadataToken:x}"); + Debug.Assert(c.m_fFldStorageAvailable == d.m_fFldStorageAvailable, $"{label} field[{i}] m_fFldStorageAvailable mismatch - cDAC: {c.m_fFldStorageAvailable}, DAC: {d.m_fFldStorageAvailable}"); + Debug.Assert(c.m_fFldIsStatic == d.m_fFldIsStatic, $"{label} field[{i}] m_fFldIsStatic mismatch - cDAC: {c.m_fFldIsStatic}, DAC: {d.m_fFldIsStatic}"); + Debug.Assert(c.m_fFldIsRVA == d.m_fFldIsRVA, $"{label} field[{i}] m_fFldIsRVA mismatch - cDAC: {c.m_fFldIsRVA}, DAC: {d.m_fFldIsRVA}"); + Debug.Assert(c.m_fFldIsTLS == d.m_fFldIsTLS, $"{label} field[{i}] m_fFldIsTLS mismatch - cDAC: {c.m_fFldIsTLS}, DAC: {d.m_fFldIsTLS}"); + Debug.Assert(c.m_fFldIsPrimitive == d.m_fFldIsPrimitive, $"{label} field[{i}] m_fFldIsPrimitive mismatch - cDAC: {c.m_fFldIsPrimitive}, DAC: {d.m_fFldIsPrimitive}"); + Debug.Assert(c.m_fFldIsCollectibleStatic == d.m_fFldIsCollectibleStatic, $"{label} field[{i}] m_fFldIsCollectibleStatic mismatch - cDAC: {c.m_fFldIsCollectibleStatic}, DAC: {d.m_fFldIsCollectibleStatic}"); + Debug.Assert(c.m_fldInstanceOffset == d.m_fldInstanceOffset, $"{label} field[{i}] m_fldInstanceOffset mismatch - cDAC: 0x{c.m_fldInstanceOffset:x}, DAC: 0x{d.m_fldInstanceOffset:x}"); + Debug.Assert(c.m_pFldStaticAddress == d.m_pFldStaticAddress, $"{label} field[{i}] m_pFldStaticAddress mismatch - cDAC: 0x{c.m_pFldStaticAddress:x}, DAC: 0x{d.m_pFldStaticAddress:x}"); + Debug.Assert(c.m_vmFieldDesc == d.m_vmFieldDesc, $"{label} field[{i}] m_vmFieldDesc mismatch - cDAC: 0x{c.m_vmFieldDesc:x}, DAC: 0x{d.m_vmFieldDesc:x}"); + } + } +#endif public int TypeHandleToExpandedTypeInfo(AreValueTypesBoxed boxed, ulong vmTypeHandle, DebuggerIPCE_ExpandedTypeData* pTypeInfo) { diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/IDacDbiInterface.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/IDacDbiInterface.cs index 37eda025de015c..3d92e0d6550674 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/IDacDbiInterface.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/IDacDbiInterface.cs @@ -24,6 +24,26 @@ public struct COR_TYPEID public ulong token2; } +[StructLayout(LayoutKind.Sequential)] +public struct FieldData +{ + public uint m_fldMetadataToken; + public Interop.BOOL m_fFldStorageAvailable; + + public byte m_fFldIsStatic; + public byte m_fFldIsRVA; + public byte m_fFldIsTLS; + public byte m_fFldIsPrimitive; + public byte m_fFldIsCollectibleStatic; + + public ulong m_fldInstanceOffset; + public ulong m_pFldStaticAddress; + public nuint m_fldSignatureCache; + public uint m_fldSignatureCacheSize; + + public ulong m_vmFieldDesc; +} + #pragma warning disable CS0649 // Field is never assigned to, and will always have its default value [StructLayout(LayoutKind.Sequential)] @@ -532,10 +552,10 @@ public unsafe partial interface IDacDbiInterface int HasTypeParams(ulong vmTypeHandle, Interop.BOOL* pResult); [PreserveSig] - int GetClassInfo(ulong thExact, nint pData); + int EnumerateClassFields(ulong thExact, nuint* pObjectSize, delegate* unmanaged fpCallback, nint pUserData); [PreserveSig] - int GetInstantiationFieldInfo(ulong vmAssembly, ulong vmTypeHandle, ulong vmExactMethodTable, nint pFieldList, nuint* pObjectSize); + int EnumerateInstantiationFields(ulong vmAssembly, ulong vmThExact, ulong vmThApprox, nuint* pObjectSize, delegate* unmanaged fpCallback, nint pUserData); [PreserveSig] int TypeHandleToExpandedTypeInfo(AreValueTypesBoxed boxed, ulong vmTypeHandle, DebuggerIPCE_ExpandedTypeData* pData); diff --git a/src/native/managed/cdac/tests/DataGenerator/DataGeneratorTests.cs b/src/native/managed/cdac/tests/DataGenerator/DataGeneratorTests.cs index 968c25ce517ce7..2d8ae2bff973c9 100644 --- a/src/native/managed/cdac/tests/DataGenerator/DataGeneratorTests.cs +++ b/src/native/managed/cdac/tests/DataGenerator/DataGeneratorTests.cs @@ -305,6 +305,32 @@ public void FieldAddress_ManagedResolves() Assert.Equal((TargetPointer)(InstanceAddr + 12), t.AnchorAddress); } + [Fact] + public void FieldAddress_OptionalPresent() + { + var target = new TestTarget() + .AddNativeType("TestOptionalFieldAddr", size: 8, ("Required", 0), ("OptionalAddress", 4)) + .Allocate(InstanceAddr, 8, (0, U32(7u))); + + TestOptionalFieldAddr t = Materialize(target, InstanceAddr); + + Assert.Equal(7u, t.Required); + Assert.Equal((TargetPointer)(InstanceAddr + 4), t.OptionalAddress); + } + + [Fact] + public void FieldAddress_OptionalAbsent() + { + var target = new TestTarget() + .AddNativeType("TestOptionalFieldAddr", size: 4, ("Required", 0)) + .Allocate(InstanceAddr, 4, (0, U32(9u))); + + TestOptionalFieldAddr t = Materialize(target, InstanceAddr); + + Assert.Equal(9u, t.Required); + Assert.Null(t.OptionalAddress); + } + // ================================================================ // UsePropertyName = false // ================================================================ diff --git a/src/native/managed/cdac/tests/DataGenerator/TestTypes.cs b/src/native/managed/cdac/tests/DataGenerator/TestTypes.cs index d21ccff79b6793..61d3eae6b91cf2 100644 --- a/src/native/managed/cdac/tests/DataGenerator/TestTypes.cs +++ b/src/native/managed/cdac/tests/DataGenerator/TestTypes.cs @@ -94,6 +94,15 @@ internal sealed partial class TestFieldAddr : IData [FieldAddress("Anchor")] public TargetPointer AnchorAddress { get; } } +// 11b. Optional nullable FieldAddress (TargetPointer?). The descriptor is +// allowed to omit the field; absent => null, present => base + offset. +[CdacType("TestOptionalFieldAddr")] +internal sealed partial class TestOptionalFieldAddr : IData +{ + [Field] public uint Required { get; } + [FieldAddress] public TargetPointer? OptionalAddress { get; } +} + // 12. UsePropertyName = false with explicit names -- suppresses property name. [CdacType("TestNoPropertyName")] internal sealed partial class TestNoPropertyName : IData diff --git a/src/native/managed/cdac/tests/EditAndContinueTests.cs b/src/native/managed/cdac/tests/EditAndContinueTests.cs new file mode 100644 index 00000000000000..d993f9d155771a --- /dev/null +++ b/src/native/managed/cdac/tests/EditAndContinueTests.cs @@ -0,0 +1,235 @@ +// 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.Linq; +using Microsoft.Diagnostics.DataContractReader.Contracts; +using Moq; +using Xunit; + +namespace Microsoft.Diagnostics.DataContractReader.Tests; + +public class EditAndContinueTests +{ + private const string EnCContractVersion = "c1"; + + private static Dictionary CreateContractTypes(MockEditAndContinueBuilder builder) + => new() + { + [DataType.Module] = TargetTestHelpers.CreateTypeInfo(builder.ModuleLayout), + [DataType.UnorderedArrayBase] = TargetTestHelpers.CreateTypeInfo(builder.UnorderedArrayBaseLayout), + [DataType.EnCEEClassData] = TargetTestHelpers.CreateTypeInfo(builder.ClassDataLayout), + [DataType.EnCAddedFieldElement] = TargetTestHelpers.CreateTypeInfo(builder.AddedFieldElementLayout), + }; + + private static TestPlaceholderTarget CreateTarget( + MockTarget.Architecture arch, + MockEditAndContinueBuilder builder, + Mock mockRuntimeTypeSystem, + Mock mockLoader) + { + return new TestPlaceholderTarget.Builder(arch) + .UseReader(builder.Builder.GetMemoryContext().ReadFromTarget) + .AddTypes(CreateContractTypes(builder)) + .AddContract(version: EnCContractVersion) + .AddMockContract(mockRuntimeTypeSystem) + .AddMockContract(mockLoader) + .Build(); + } + + private static (Mock Rts, Mock Loader) CreateMocks( + TargetPointer mtPtr, + TargetPointer modulePtr, + ModuleFlags flags) + { + var rts = new Mock(); + rts.Setup(r => r.GetTypeHandle(mtPtr)).Returns(new TypeHandle(mtPtr)); + rts.Setup(r => r.GetModule(It.Is(th => th.Address == mtPtr))).Returns(modulePtr); + + var loader = new Mock(); + Contracts.ModuleHandle moduleHandle = new Contracts.ModuleHandle(modulePtr); + loader.Setup(l => l.GetModuleHandleFromModulePtr(modulePtr)).Returns(moduleHandle); + loader.Setup(l => l.GetFlags(moduleHandle)).Returns(flags); + + return (rts, loader); + } + + [Theory] + [ClassData(typeof(MockTarget.StdArch))] + public void TypeDescHandle_ReturnsEmpty(MockTarget.Architecture arch) + { + var builder = new MockEditAndContinueBuilder(new MockMemorySpace.Builder(new TargetTestHelpers(arch))); + // Allocate a Module just so the rest of the wiring is valid. + MockEnCModule module = builder.AddModule(Array.Empty()); + + // TypeDesc handles have a non-zero low bit. We pick a pointer with the low bit set. + TargetPointer tdPtr = new TargetPointer(0x1000_0001); + var (rts, loader) = CreateMocks(tdPtr, module.Address, ModuleFlags.EditAndContinue); + + TestPlaceholderTarget target = CreateTarget(arch, builder, rts, loader); + IEditAndContinue contract = target.Contracts.EditAndContinue; + Assert.NotNull(contract); + + TypeHandle th = target.Contracts.RuntimeTypeSystem.GetTypeHandle(tdPtr); + Assert.Empty(contract.EnumerateAddedFieldDescs(th, staticFields: false)); + Assert.Empty(contract.EnumerateAddedFieldDescs(th, staticFields: true)); + } + + [Theory] + [ClassData(typeof(MockTarget.StdArch))] + public void EnCNotEnabled_ReturnsEmpty(MockTarget.Architecture arch) + { + var builder = new MockEditAndContinueBuilder(new MockMemorySpace.Builder(new TargetTestHelpers(arch))); + // Class data exists, but EditAndContinue flag is not set on the module. + ulong mtPtr = 0x0000_8000; + MockEnCEEClassData classData = builder.AddClassData(mtPtr); + builder.AddInstanceFields(classData, count: 3); + MockEnCModule module = builder.AddModule(new[] { classData }); + + var (rts, loader) = CreateMocks(new TargetPointer(mtPtr), module.Address, flags: 0); + TestPlaceholderTarget target = CreateTarget(arch, builder, rts, loader); + + IEditAndContinue contract = target.Contracts.EditAndContinue; + TypeHandle th = target.Contracts.RuntimeTypeSystem.GetTypeHandle(mtPtr); + Assert.Empty(contract.EnumerateAddedFieldDescs(th, staticFields: false)); + Assert.Empty(contract.EnumerateAddedFieldDescs(th, staticFields: true)); + } + + [Theory] + [ClassData(typeof(MockTarget.StdArch))] + public void NoMatchingClassData_ReturnsEmpty(MockTarget.Architecture arch) + { + var builder = new MockEditAndContinueBuilder(new MockMemorySpace.Builder(new TargetTestHelpers(arch))); + // ClassData entry exists but for a different MT. + MockEnCEEClassData classData = builder.AddClassData(methodTable: 0x0000_8000); + builder.AddInstanceFields(classData, count: 2); + MockEnCModule module = builder.AddModule(new[] { classData }); + + ulong otherMt = 0x0000_9000; + var (rts, loader) = CreateMocks(new TargetPointer(otherMt), module.Address, ModuleFlags.EditAndContinue); + TestPlaceholderTarget target = CreateTarget(arch, builder, rts, loader); + + IEditAndContinue contract = target.Contracts.EditAndContinue; + TypeHandle th = target.Contracts.RuntimeTypeSystem.GetTypeHandle(otherMt); + Assert.Empty(contract.EnumerateAddedFieldDescs(th, staticFields: false)); + Assert.Empty(contract.EnumerateAddedFieldDescs(th, staticFields: true)); + } + + [Theory] + [ClassData(typeof(MockTarget.StdArch))] + public void EmptyClassList_ReturnsEmpty(MockTarget.Architecture arch) + { + var builder = new MockEditAndContinueBuilder(new MockMemorySpace.Builder(new TargetTestHelpers(arch))); + MockEnCModule module = builder.AddModule(Array.Empty()); + + ulong mtPtr = 0x0000_8000; + var (rts, loader) = CreateMocks(new TargetPointer(mtPtr), module.Address, ModuleFlags.EditAndContinue); + TestPlaceholderTarget target = CreateTarget(arch, builder, rts, loader); + + IEditAndContinue contract = target.Contracts.EditAndContinue; + TypeHandle th = target.Contracts.RuntimeTypeSystem.GetTypeHandle(mtPtr); + Assert.Empty(contract.EnumerateAddedFieldDescs(th, staticFields: false)); + Assert.Empty(contract.EnumerateAddedFieldDescs(th, staticFields: true)); + } + + [Theory] + [ClassData(typeof(MockTarget.StdArch))] + public void InstanceFields_ReturnedInOrder(MockTarget.Architecture arch) + { + var builder = new MockEditAndContinueBuilder(new MockMemorySpace.Builder(new TargetTestHelpers(arch))); + ulong mtPtr = 0x0000_8000; + MockEnCEEClassData classData = builder.AddClassData(mtPtr); + IReadOnlyList instanceElems = builder.AddInstanceFields(classData, count: 3); + // No static fields for this class. + MockEnCModule module = builder.AddModule(new[] { classData }); + + var (rts, loader) = CreateMocks(new TargetPointer(mtPtr), module.Address, ModuleFlags.EditAndContinue); + TestPlaceholderTarget target = CreateTarget(arch, builder, rts, loader); + + IEditAndContinue contract = target.Contracts.EditAndContinue; + TypeHandle th = target.Contracts.RuntimeTypeSystem.GetTypeHandle(mtPtr); + + // FieldDesc is the address of the FieldDesc subfield within each element. + ulong fieldDescOffset = (ulong)builder.AddedFieldElementLayout.GetField("FieldDesc").Offset; + ulong[] expected = instanceElems.Select(e => e.Address + fieldDescOffset).ToArray(); + ulong[] actual = contract.EnumerateAddedFieldDescs(th, staticFields: false).Select(p => (ulong)p).ToArray(); + Assert.Equal(expected, actual); + + Assert.Empty(contract.EnumerateAddedFieldDescs(th, staticFields: true)); + } + + [Theory] + [ClassData(typeof(MockTarget.StdArch))] + public void StaticFields_ReturnedInOrder(MockTarget.Architecture arch) + { + var builder = new MockEditAndContinueBuilder(new MockMemorySpace.Builder(new TargetTestHelpers(arch))); + ulong mtPtr = 0x0000_8000; + MockEnCEEClassData classData = builder.AddClassData(mtPtr); + IReadOnlyList staticElems = builder.AddStaticFields(classData, count: 2); + MockEnCModule module = builder.AddModule(new[] { classData }); + + var (rts, loader) = CreateMocks(new TargetPointer(mtPtr), module.Address, ModuleFlags.EditAndContinue); + TestPlaceholderTarget target = CreateTarget(arch, builder, rts, loader); + + IEditAndContinue contract = target.Contracts.EditAndContinue; + TypeHandle th = target.Contracts.RuntimeTypeSystem.GetTypeHandle(mtPtr); + + ulong fieldDescOffset = (ulong)builder.AddedFieldElementLayout.GetField("FieldDesc").Offset; + ulong[] expected = staticElems.Select(e => e.Address + fieldDescOffset).ToArray(); + ulong[] actual = contract.EnumerateAddedFieldDescs(th, staticFields: true).Select(p => (ulong)p).ToArray(); + Assert.Equal(expected, actual); + + Assert.Empty(contract.EnumerateAddedFieldDescs(th, staticFields: false)); + } + + [Theory] + [ClassData(typeof(MockTarget.StdArch))] + public void InstanceAndStaticFields_ReturnedSeparately(MockTarget.Architecture arch) + { + var builder = new MockEditAndContinueBuilder(new MockMemorySpace.Builder(new TargetTestHelpers(arch))); + ulong mtPtr = 0x0000_8000; + MockEnCEEClassData classData = builder.AddClassData(mtPtr); + IReadOnlyList instanceElems = builder.AddInstanceFields(classData, count: 4); + IReadOnlyList staticElems = builder.AddStaticFields(classData, count: 1); + MockEnCModule module = builder.AddModule(new[] { classData }); + + var (rts, loader) = CreateMocks(new TargetPointer(mtPtr), module.Address, ModuleFlags.EditAndContinue); + TestPlaceholderTarget target = CreateTarget(arch, builder, rts, loader); + + IEditAndContinue contract = target.Contracts.EditAndContinue; + TypeHandle th = target.Contracts.RuntimeTypeSystem.GetTypeHandle(mtPtr); + + ulong fieldDescOffset = (ulong)builder.AddedFieldElementLayout.GetField("FieldDesc").Offset; + Assert.Equal( + instanceElems.Select(e => e.Address + fieldDescOffset).ToArray(), + contract.EnumerateAddedFieldDescs(th, staticFields: false).Select(p => (ulong)p).ToArray()); + Assert.Equal( + staticElems.Select(e => e.Address + fieldDescOffset).ToArray(), + contract.EnumerateAddedFieldDescs(th, staticFields: true).Select(p => (ulong)p).ToArray()); + } + + [Theory] + [ClassData(typeof(MockTarget.StdArch))] + public void SecondEntryMatches(MockTarget.Architecture arch) + { + // Validate linear search picks the second entry when the first does not match. + var builder = new MockEditAndContinueBuilder(new MockMemorySpace.Builder(new TargetTestHelpers(arch))); + MockEnCEEClassData other = builder.AddClassData(methodTable: 0x0000_7000); + builder.AddInstanceFields(other, count: 5); + ulong mtPtr = 0x0000_8000; + MockEnCEEClassData mine = builder.AddClassData(mtPtr); + IReadOnlyList instanceElems = builder.AddInstanceFields(mine, count: 2); + MockEnCModule module = builder.AddModule(new[] { other, mine }); + + var (rts, loader) = CreateMocks(new TargetPointer(mtPtr), module.Address, ModuleFlags.EditAndContinue); + TestPlaceholderTarget target = CreateTarget(arch, builder, rts, loader); + + IEditAndContinue contract = target.Contracts.EditAndContinue; + TypeHandle th = target.Contracts.RuntimeTypeSystem.GetTypeHandle(mtPtr); + + ulong fieldDescOffset = (ulong)builder.AddedFieldElementLayout.GetField("FieldDesc").Offset; + ulong[] expected = instanceElems.Select(e => e.Address + fieldDescOffset).ToArray(); + Assert.Equal(expected, contract.EnumerateAddedFieldDescs(th, staticFields: false).Select(p => (ulong)p).ToArray()); + } +} diff --git a/src/native/managed/cdac/tests/MockDescriptors/MockDescriptors.EditAndContinue.cs b/src/native/managed/cdac/tests/MockDescriptors/MockDescriptors.EditAndContinue.cs new file mode 100644 index 00000000000000..a1267754113fc4 --- /dev/null +++ b/src/native/managed/cdac/tests/MockDescriptors/MockDescriptors.EditAndContinue.cs @@ -0,0 +1,242 @@ +// 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; + +namespace Microsoft.Diagnostics.DataContractReader.Tests; + +/// +/// A minimal mock of Module that contains only the Edit-and-Continue +/// related fields read by EditAndContinue_1. The EnC contract uses +/// DataType.Module, so tests register this layout under that key. +/// The class list is modeled as a single embedded +/// substructure to mirror the native CUnorderedArrayBaseWithAllocator layout. +/// +internal sealed class MockEnCModule : TypedView +{ + private const string EnCClassListFieldName = "EnCClassList"; + + public static Layout CreateLayout(MockTarget.Architecture architecture) + => new SequentialLayoutBuilder("Module", architecture) + .AddField(EnCClassListFieldName, MockUnorderedArrayBase.GetSize(architecture)) + .Build(); + + public LayoutField EnCClassListField => Layout.GetField(EnCClassListFieldName); + + public ulong EnCClassListAddress => Address + (ulong)EnCClassListField.Offset; + + public Memory EnCClassListMemory => Memory.Slice(EnCClassListField.Offset, EnCClassListField.Size); +} + +/// +/// Models the binary layout of CUnorderedArrayBaseWithAllocator<T, N, A>: +/// an entry count, an allocated size, and a pointer to a contiguous array of +/// T. The cdac descriptor (DataType.UnorderedArrayBase) only exposes +/// the count and table; the size field is present here so the pointer falls at +/// the correct (pointer-aligned) offset. +/// +internal sealed class MockUnorderedArrayBase : TypedView +{ + private const string CountFieldName = "Count"; + private const string SizeFieldName = "Size"; + private const string TableFieldName = "Table"; + + public static Layout CreateLayout(MockTarget.Architecture architecture) + => new SequentialLayoutBuilder("UnorderedArrayBase", architecture) + .AddUInt32Field(CountFieldName) + .AddUInt32Field(SizeFieldName) + .AddPointerField(TableFieldName) + .Build(); + + public static int GetSize(MockTarget.Architecture architecture) + => CreateLayout(architecture).Size; + + public uint Count + { + get => ReadUInt32Field(CountFieldName); + set => WriteUInt32Field(CountFieldName, value); + } + + public uint Size + { + get => ReadUInt32Field(SizeFieldName); + set => WriteUInt32Field(SizeFieldName, value); + } + + public ulong Table + { + get => ReadPointerField(TableFieldName); + set => WritePointerField(TableFieldName, value); + } +} + +internal sealed class MockEnCEEClassData : TypedView +{ + private const string MethodTableFieldName = "MethodTable"; + private const string AddedInstanceFieldsFieldName = "AddedInstanceFields"; + private const string AddedStaticFieldsFieldName = "AddedStaticFields"; + + public static Layout CreateLayout(MockTarget.Architecture architecture) + => new SequentialLayoutBuilder("EnCEEClassData", architecture) + .AddPointerField(MethodTableFieldName) + .AddPointerField(AddedInstanceFieldsFieldName) + .AddPointerField(AddedStaticFieldsFieldName) + .Build(); + + public ulong MethodTable + { + get => ReadPointerField(MethodTableFieldName); + set => WritePointerField(MethodTableFieldName, value); + } + + public ulong AddedInstanceFields + { + get => ReadPointerField(AddedInstanceFieldsFieldName); + set => WritePointerField(AddedInstanceFieldsFieldName, value); + } + + public ulong AddedStaticFields + { + get => ReadPointerField(AddedStaticFieldsFieldName); + set => WritePointerField(AddedStaticFieldsFieldName, value); + } +} + +internal sealed class MockEnCAddedFieldElement : TypedView +{ + private const string NextFieldName = "Next"; + private const string FieldDescFieldName = "FieldDesc"; + + public static Layout CreateLayout(MockTarget.Architecture architecture) + => new SequentialLayoutBuilder("EnCAddedFieldElement", architecture) + .AddPointerField(NextFieldName) + // FieldDesc is the address of the embedded EnCFieldDesc payload. We model that + // payload as a single pointer-sized opaque blob; tests use the FieldDesc field + // address directly as the returned FieldDesc pointer. + .AddPointerField(FieldDescFieldName) + .Build(); + + public ulong Next + { + get => ReadPointerField(NextFieldName); + set => WritePointerField(NextFieldName, value); + } +} + +internal sealed class MockEditAndContinueBuilder +{ + private const ulong DefaultAllocationRangeStart = 0x0010_0000; + private const ulong DefaultAllocationRangeEnd = 0x0020_0000; + + internal MockMemorySpace.Builder Builder { get; } + internal Layout ModuleLayout { get; } + internal Layout UnorderedArrayBaseLayout { get; } + internal Layout ClassDataLayout { get; } + internal Layout AddedFieldElementLayout { get; } + + private readonly MockMemorySpace.BumpAllocator _allocator; + + public MockEditAndContinueBuilder(MockMemorySpace.Builder builder) + : this(builder, (DefaultAllocationRangeStart, DefaultAllocationRangeEnd)) + { + } + + public MockEditAndContinueBuilder(MockMemorySpace.Builder builder, (ulong Start, ulong End) allocationRange) + { + ArgumentNullException.ThrowIfNull(builder); + + Builder = builder; + _allocator = Builder.CreateAllocator(allocationRange.Start, allocationRange.End); + + ModuleLayout = MockEnCModule.CreateLayout(builder.TargetTestHelpers.Arch); + UnorderedArrayBaseLayout = MockUnorderedArrayBase.CreateLayout(builder.TargetTestHelpers.Arch); + ClassDataLayout = MockEnCEEClassData.CreateLayout(builder.TargetTestHelpers.Arch); + AddedFieldElementLayout = MockEnCAddedFieldElement.CreateLayout(builder.TargetTestHelpers.Arch); + } + + /// + /// Allocate a Module with the EnC class list pre-populated. + /// is written into a contiguous pointer-sized array referenced by the embedded + /// CUnorderedArrayBaseWithAllocator's table pointer. + /// + public MockEnCModule AddModule(ReadOnlySpan classDataEntries) + { + MockEnCModule module = ModuleLayout.Create(_allocator.Allocate((ulong)ModuleLayout.Size, "EnC Module")); + MockUnorderedArrayBase classList = UnorderedArrayBaseLayout.Create(module.EnCClassListMemory, module.EnCClassListAddress); + if (classDataEntries.Length > 0) + { + ulong ptrSize = (ulong)Builder.TargetTestHelpers.PointerSize; + MockMemorySpace.HeapFragment table = _allocator.Allocate(ptrSize * (ulong)classDataEntries.Length, "EnC ClassList table"); + for (int i = 0; i < classDataEntries.Length; i++) + { + Span slot = table.Data.AsSpan((int)(ptrSize * (ulong)i), (int)ptrSize); + Builder.TargetTestHelpers.WritePointer(slot, classDataEntries[i].Address); + } + classList.Table = table.Address; + classList.Count = (uint)classDataEntries.Length; + classList.Size = (uint)classDataEntries.Length; + } + else + { + classList.Table = 0; + classList.Count = 0; + classList.Size = 0; + } + return module; + } + + public MockEnCEEClassData AddClassData(ulong methodTable) + { + MockEnCEEClassData entry = ClassDataLayout.Create(_allocator.Allocate((ulong)ClassDataLayout.Size, "EnCEEClassData")); + entry.MethodTable = methodTable; + return entry; + } + + /// + /// Build a linked list of EnCAddedFieldElement nodes whose FieldDesc addresses are the + /// addresses of the FieldDesc subfield inside each element. Returns the list of element + /// allocations in the order requested; the head (first element) is element[0]. + /// + public IReadOnlyList BuildFieldList(int count) + { + if (count <= 0) + return Array.Empty(); + var elements = new List(count); + for (int i = 0; i < count; i++) + { + MockEnCAddedFieldElement element = AddedFieldElementLayout.Create( + _allocator.Allocate((ulong)AddedFieldElementLayout.Size, $"EnCAddedFieldElement[{i}]")); + elements.Add(element); + } + // Link them: elements[i].Next = elements[i+1].Address; last.Next = 0 + for (int i = 0; i < count - 1; i++) + { + elements[i].Next = elements[i + 1].Address; + } + elements[count - 1].Next = 0; + return elements; + } + + /// + /// Attach an instance-field linked list to . Returns the + /// elements for inspection. + /// + public IReadOnlyList AddInstanceFields(MockEnCEEClassData classData, int count) + { + IReadOnlyList elements = BuildFieldList(count); + classData.AddedInstanceFields = elements.Count > 0 ? elements[0].Address : 0; + return elements; + } + + /// + /// Attach a static-field linked list to . Returns the + /// elements for inspection. + /// + public IReadOnlyList AddStaticFields(MockEnCEEClassData classData, int count) + { + IReadOnlyList elements = BuildFieldList(count); + classData.AddedStaticFields = elements.Count > 0 ? elements[0].Address : 0; + return elements; + } +}