diff --git a/docs/design/datacontracts/RuntimeMutableTypeSystem.md b/docs/design/datacontracts/RuntimeMutableTypeSystem.md index 7818b6b6ae290c..b4a9d70d080f08 100644 --- a/docs/design/datacontracts/RuntimeMutableTypeSystem.md +++ b/docs/design/datacontracts/RuntimeMutableTypeSystem.md @@ -7,6 +7,9 @@ This contract exposes runtime type system information about changes that occurre ```csharp IEnumerable EnumerateAddedFieldDescs(TypeHandle typeHandle, bool staticFields); bool IsFieldDescEnCNew(TargetPointer fieldDescPointer); +bool DoesEnCFieldDescNeedFixup(TargetPointer encFieldDescPointer); +TargetPointer GetEnCStaticFieldDataAddress(TargetPointer encFieldDescPointer); +TargetPointer GetEnCInstanceFieldAddress(TargetPointer objectAddress, TargetPointer encFieldDescPointer); ``` ## Version 1 @@ -23,7 +26,17 @@ bool IsFieldDescEnCNew(TargetPointer fieldDescPointer); | `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`). | +| `EnCFieldDesc` | `NeedsFixup` | Non-zero when the `EnCFieldDesc` still needs fixup (i.e., it has not been fully initialized). | +| `EnCFieldDesc` | `StaticFieldData` | Pointer to the `EnCAddedStaticField` that backs this static field (NULL until storage has been allocated). | +| `EnCAddedStaticField` | `FieldDesc` | Pointer back to the `EnCFieldDesc` that owns this storage. | +| `EnCAddedStaticField` | `FieldData` | Address of the first byte of static field storage on this entry. | +| `EnCAddedField` | `Next` | Pointer to the next `EnCAddedField` entry in the per-object linked list hanging off the SyncBlock. | +| `EnCAddedField` | `FieldDesc` | Pointer to the `EnCFieldDesc` for the added instance field. | +| `EnCAddedField` | `FieldData` | The dependent-handle pair (typed as `ObjectHandle`) whose primary OBJECTREF is the dependency anchor and whose secondary OBJECTREF is the `System.Diagnostics.EditAndContinueHelper` instance. | +| `EnCSyncBlockInfo` | `List` | Head of the linked list of `EnCAddedField` entries for the EnC-added instance fields associated with an object. | +| `SyncBlock` | `EnCInfo` | Pointer to the `EnCSyncBlockInfo` for this object (NULL if the object has no added EnC fields). Optional: only present when EditAndContinue is configured. | | `FieldDesc` | `DWord2` | Packed flags/offset word containing the field's offset; the EnC-new sentinel `FieldOffsetNewEnc` is stored here for fields that have been added but do not yet have storage assigned. | +| `System.Diagnostics.EditAndContinueHelper` | `_objectReference` | Holds the per-field storage for an EnC-added instance field. | ### Globals used @@ -37,6 +50,8 @@ bool IsFieldDescEnCNew(TargetPointer fieldDescPointer); | --- | | `IRuntimeTypeSystem` | | `ILoader` | +| `IObject` | +| `IGC` | ```csharp internal enum FieldDescFlags2 : uint @@ -81,4 +96,80 @@ bool IsFieldDescEnCNew(TargetPointer fieldDescPointer) uint offset = DWord2 & (uint)FieldDescFlags2.OffsetMask; return offset == target.ReadGlobal("FieldOffsetNewEnc"); } + +bool DoesEnCFieldDescNeedFixup(TargetPointer encFieldDescPointer) +{ + int needsFixup = target.Read(encFieldDescPointer + /* EnCFieldDesc::NeedsFixup offset */); + return needsFixup != 0; +} + +TargetPointer GetEnCStaticFieldDataAddress(TargetPointer encFieldDescPointer) +{ + TargetPointer staticFieldData = target.ReadPointer(encFieldDescPointer + /* EnCFieldDesc::StaticFieldData offset */); + if (staticFieldData == TargetPointer.Null) + return TargetPointer.Null; + + // [FieldAddress] on EnCAddedStaticField::FieldData returns the address of the field slot + return staticFieldData + /* EnCAddedStaticField::FieldData offset */; +} + +TargetPointer GetEnCInstanceFieldAddress(TargetPointer objectAddress, TargetPointer encFieldDescPointer) +{ + // get the SyncBlock from the object header via IObject + TargetPointer syncBlockAddress = target.Contracts.Object.GetSyncBlockAddress(objectAddress); + if (syncBlockAddress == TargetPointer.Null) + return TargetPointer.Null; + + // SyncBlock::EnCInfo is an optional field; if absent the object has no EnC-added fields + TargetPointer encInfoAddress = target.ReadPointer(syncBlockAddress + /* SyncBlock::EnCInfo offset */); + if (encInfoAddress == TargetPointer.Null) + return TargetPointer.Null; + + // Walk the linked list of EnCAddedField entries to find the matching FieldDesc + TargetPointer entryPtr = target.ReadPointer(encInfoAddress + /* EnCSyncBlockInfo::List offset */); + while (entryPtr != TargetPointer.Null) + { + TargetPointer entryFieldDesc = target.ReadPointer(entryPtr + /* EnCAddedField::FieldDesc offset */); + if (entryFieldDesc == encFieldDescPointer) + { + // The FieldData is a dependent handle; the secondary is the EnCHelper object + TargetPointer handleAddress = ReadObjectHandle(entryPtr + /* EnCAddedField::FieldData offset */); + if (handleAddress == TargetPointer.Null) + return TargetPointer.Null; + + TargetNUInt secondary = target.Contracts.GC.GetHandleExtraInfo(handleAddress); + TargetPointer helperObjectAddress = new TargetPointer(secondary.Value); + if (helperObjectAddress == TargetPointer.Null) + return TargetPointer.Null; + + // [FieldAddress] on _objectReference yields the address of the OBJECTREF slot + TargetPointer objectReferenceAddress = helperObjectAddress + /* EditAndContinueHelper::_objectReference offset */; + TargetPointer fieldObject = target.ReadPointer(objectReferenceAddress); + + CorElementType fieldType = target.Contracts.RuntimeTypeSystem.GetFieldDescType(encFieldDescPointer); + if (fieldType == CorElementType.ValueType) + { + // Value-typed field stored as a boxed object; unbox to get the data. + if (fieldObject == TargetPointer.Null) + return TargetPointer.Null; + return fieldObject + /* Object::Data offset */; + } + else if (fieldType == CorElementType.Class) + { + // The OBJECTREF slot itself is the field's value location. + return objectReferenceAddress; + } + else + { + // Primitive stored in a 1-element array. Return the address of the first element. + if (fieldObject == TargetPointer.Null) + return TargetPointer.Null; + return target.Contracts.Object.GetArrayData(fieldObject, out _, out _, out _); + } + } + entryPtr = target.ReadPointer(entryPtr + /* EnCAddedField::Next offset */); + } + + return TargetPointer.Null; +} ``` diff --git a/src/coreclr/debug/daccess/dacdbiimpl.cpp b/src/coreclr/debug/daccess/dacdbiimpl.cpp index 383914db7e1e1f..2df122e01679f6 100644 --- a/src/coreclr/debug/daccess/dacdbiimpl.cpp +++ b/src/coreclr/debug/daccess/dacdbiimpl.cpp @@ -3678,7 +3678,7 @@ void DacDbiInterfaceImpl::InitFieldData(const FieldDesc * pFD, // GENERICS: TODO: this method will need to be modified if we ever support EnC on // generic classes. //----------------------------------------------------------------------------- -HRESULT STDMETHODCALLTYPE DacDbiInterfaceImpl::GetEnCHangingFieldInfo(const EnCHangingFieldInfo * pEnCFieldInfo, OUT FieldData * pFieldData, OUT BOOL * pfStatic) +HRESULT STDMETHODCALLTYPE DacDbiInterfaceImpl::GetEnCHangingFieldInfo(const EnCHangingFieldInfo * pEnCFieldInfo, OUT FieldData * pFieldData) { DD_ENTER_MAY_THROW; @@ -3704,8 +3704,6 @@ HRESULT STDMETHODCALLTYPE DacDbiInterfaceImpl::GetEnCHangingFieldInfo(const EnCH #endif // FEATURE_METADATA_UPDATER InitFieldData(pFD, pORField, pEnCFieldInfo, pFieldData); - *pfStatic = (pFD->IsStatic() != 0); - } EX_CATCH_HRESULT(hr); return hr; diff --git a/src/coreclr/debug/daccess/dacdbiimpl.h b/src/coreclr/debug/daccess/dacdbiimpl.h index 9c5f64de8d9c8c..1ccce255807cbb 100644 --- a/src/coreclr/debug/daccess/dacdbiimpl.h +++ b/src/coreclr/debug/daccess/dacdbiimpl.h @@ -279,7 +279,7 @@ class DacDbiInterfaceImpl : HRESULT STDMETHODCALLTYPE GetCollectibleTypeStaticAddress(VMPTR_FieldDesc vmField, OUT CORDB_ADDRESS * pRetVal); // Get information about a field added with Edit And Continue. - HRESULT STDMETHODCALLTYPE GetEnCHangingFieldInfo(const EnCHangingFieldInfo * pEnCFieldInfo, OUT FieldData * pFieldData, OUT BOOL * pfStatic); + HRESULT STDMETHODCALLTYPE GetEnCHangingFieldInfo(const EnCHangingFieldInfo * pEnCFieldInfo, OUT FieldData * pFieldData); // EnumerateTypeHandleParams gets the necessary data for a type handle, i.e. its type // parameters, e.g. "String" and "List" from the type handle for diff --git a/src/coreclr/debug/di/rsclass.cpp b/src/coreclr/debug/di/rsclass.cpp index b698aad02cc119..4b1f5dc535c97d 100644 --- a/src/coreclr/debug/di/rsclass.cpp +++ b/src/coreclr/debug/di/rsclass.cpp @@ -996,7 +996,6 @@ FieldData * CordbClass::GetEnCFieldFromDac(BOOL fStatic, mdTypeDef metadataToken; FieldData fieldData, * pInfo = NULL; - BOOL fDacStatic; CordbProcess * pProcess = GetModule()->GetProcess(); _ASSERTE(pProcess != NULL); @@ -1004,8 +1003,8 @@ FieldData * CordbClass::GetEnCFieldFromDac(BOOL fStatic, InitEnCFieldInfo(&encField, fStatic, pObject, fieldToken, metadataToken); // Go get this particular field. - IfFailThrow(pProcess->GetDAC()->GetEnCHangingFieldInfo(&encField, &fieldData, &fDacStatic)); - _ASSERTE(fStatic == fDacStatic); + IfFailThrow(pProcess->GetDAC()->GetEnCHangingFieldInfo(&encField, &fieldData)); + _ASSERTE((fStatic != 0) == fieldData.m_fFldIsStatic); // Save the field results in our cache and get a stable pointer to the data if (fStatic) diff --git a/src/coreclr/debug/inc/dacdbiinterface.h b/src/coreclr/debug/inc/dacdbiinterface.h index 56abc73cfc22a0..5bc5287d184ef3 100644 --- a/src/coreclr/debug/inc/dacdbiinterface.h +++ b/src/coreclr/debug/inc/dacdbiinterface.h @@ -1673,8 +1673,7 @@ IDacDbiInterface : public IUnknown // the assembly // an indication of the type: whether it's a class or value type // output: pFieldData - information about the EnC added field - // pfStatic - flag to indicate whether the field is static - virtual HRESULT STDMETHODCALLTYPE GetEnCHangingFieldInfo(const EnCHangingFieldInfo * pEnCFieldInfo, OUT FieldData * pFieldData, OUT BOOL * pfStatic) = 0; + virtual HRESULT STDMETHODCALLTYPE GetEnCHangingFieldInfo(const EnCHangingFieldInfo * pEnCFieldInfo, OUT FieldData * pFieldData) = 0; // EnumerateTypeHandleParams gets the necessary data for a type handle, i.e. its diff --git a/src/coreclr/debug/inc/dacdbistructures.h b/src/coreclr/debug/inc/dacdbistructures.h index 310123cabf03ff..4b6e699429676c 100644 --- a/src/coreclr/debug/inc/dacdbistructures.h +++ b/src/coreclr/debug/inc/dacdbistructures.h @@ -739,7 +739,7 @@ class MSLAYOUT EnCHangingFieldInfo public: // Init will initialize fields, taking into account whether the field is static or not. void Init(VMPTR_Object pObject, - SIZE_T offset, + UINT offset, mdFieldDef fieldToken, CorElementType elementType, mdTypeDef metadataToken, @@ -748,13 +748,13 @@ class MSLAYOUT EnCHangingFieldInfo DebuggerIPCE_BasicTypeData GetObjectTypeData() const { return m_objectTypeData; }; mdFieldDef GetFieldToken() const { return m_fldToken; }; VMPTR_Object GetVmObject() const { return m_vmObject; }; - SIZE_T GetOffsetToVars() const { return m_offsetToVars; }; + UINT GetOffsetToVars() const { return m_offsetToVars; }; private: DebuggerIPCE_BasicTypeData m_objectTypeData; // type data for the EnC field VMPTR_Object m_vmObject; // object instance to which the field has been added--if the field is // static, this will be NULL instead of pointing to an instance - SIZE_T m_offsetToVars; // offset to the beginning of variable storage in the object + UINT m_offsetToVars; // offset to the beginning of variable storage in the object mdFieldDef m_fldToken; // metadata token for the added field }; // EnCHangingFieldInfo diff --git a/src/coreclr/debug/inc/dacdbistructures.inl b/src/coreclr/debug/inc/dacdbistructures.inl index a447afae233e90..cb477aa98c484c 100644 --- a/src/coreclr/debug/inc/dacdbistructures.inl +++ b/src/coreclr/debug/inc/dacdbistructures.inl @@ -709,7 +709,7 @@ CORDB_ADDRESS FieldData::GetStaticAddress() inline void EnCHangingFieldInfo::Init(VMPTR_Object pObject, - SIZE_T offset, + UINT offset, mdFieldDef fieldToken, CorElementType elementType, mdTypeDef metadataToken, diff --git a/src/coreclr/inc/dacdbi.idl b/src/coreclr/inc/dacdbi.idl index b017222be7a8c1..a2976aa28a1b3e 100644 --- a/src/coreclr/inc/dacdbi.idl +++ b/src/coreclr/inc/dacdbi.idl @@ -328,7 +328,7 @@ interface IDacDbiInterface : IUnknown // Field HRESULT GetThreadStaticAddress([in] VMPTR_FieldDesc vmField, [in] VMPTR_Thread vmRuntimeThread, [out] CORDB_ADDRESS * pRetVal); HRESULT GetCollectibleTypeStaticAddress([in] VMPTR_FieldDesc vmField, [out] CORDB_ADDRESS * pRetVal); - HRESULT GetEnCHangingFieldInfo([in] const struct EnCHangingFieldInfo * pEnCFieldInfo, [out] struct FieldData * pFieldData, [out] BOOL * pfStatic); + HRESULT GetEnCHangingFieldInfo([in] const struct EnCHangingFieldInfo * pEnCFieldInfo, [out] struct FieldData * pFieldData); HRESULT GetTypeHandleParams([in] VMPTR_TypeHandle vmTypeHandle, [out] TypeParamsList * pParams); HRESULT GetSimpleType( [in] CorElementType simpleType, diff --git a/src/coreclr/vm/datadescriptor/datadescriptor.inc b/src/coreclr/vm/datadescriptor/datadescriptor.inc index c0dbba1a0e6c19..ac3dce95fef5c1 100644 --- a/src/coreclr/vm/datadescriptor/datadescriptor.inc +++ b/src/coreclr/vm/datadescriptor/datadescriptor.inc @@ -235,6 +235,9 @@ CDAC_TYPE_FIELD(SyncBlock, TYPE(ObjectHandle), Lock, cdac_data::Lock) CDAC_TYPE_FIELD(SyncBlock, T_UINT32, ThinLock, cdac_data::ThinLock) CDAC_TYPE_FIELD(SyncBlock, T_POINTER, LinkNext, cdac_data::LinkNext) CDAC_TYPE_FIELD(SyncBlock, T_UINT32, HashCode, cdac_data::HashCode) +#ifdef FEATURE_METADATA_UPDATER +CDAC_TYPE_FIELD(SyncBlock, T_POINTER, EnCInfo, cdac_data::EnCInfo) +#endif // FEATURE_METADATA_UPDATER CDAC_TYPE_END(SyncBlock) @@ -325,9 +328,33 @@ 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_FIELD(EnCAddedFieldElement, T_POINTER, Next, offsetof(EnCAddedFieldElement, m_next)) +CDAC_TYPE_FIELD(EnCAddedFieldElement, T_POINTER, FieldDesc, offsetof(EnCAddedFieldElement, m_fieldDesc)) CDAC_TYPE_END(EnCAddedFieldElement) + +CDAC_TYPE_BEGIN(EnCFieldDesc) +CDAC_TYPE_INDETERMINATE(EnCFieldDesc) +CDAC_TYPE_FIELD(EnCFieldDesc, T_INT32, NeedsFixup, cdac_data::NeedsFixup) +CDAC_TYPE_FIELD(EnCFieldDesc, T_POINTER, StaticFieldData, cdac_data::StaticFieldData) +CDAC_TYPE_END(EnCFieldDesc) + +CDAC_TYPE_BEGIN(EnCAddedField) +CDAC_TYPE_INDETERMINATE(EnCAddedField) +CDAC_TYPE_FIELD(EnCAddedField, T_POINTER, Next, offsetof(EnCAddedField, m_pNext)) +CDAC_TYPE_FIELD(EnCAddedField, T_POINTER, FieldDesc, offsetof(EnCAddedField, m_pFieldDesc)) +CDAC_TYPE_FIELD(EnCAddedField, TYPE(ObjectHandle), FieldData, offsetof(EnCAddedField, m_FieldData)) +CDAC_TYPE_END(EnCAddedField) + +CDAC_TYPE_BEGIN(EnCAddedStaticField) +CDAC_TYPE_INDETERMINATE(EnCAddedStaticField) +CDAC_TYPE_FIELD(EnCAddedStaticField, T_POINTER, FieldDesc, offsetof(EnCAddedStaticField, m_pFieldDesc)) +CDAC_TYPE_FIELD(EnCAddedStaticField, T_UINT8, FieldData, offsetof(EnCAddedStaticField, m_FieldData)) +CDAC_TYPE_END(EnCAddedStaticField) + +CDAC_TYPE_BEGIN(EnCSyncBlockInfo) +CDAC_TYPE_INDETERMINATE(EnCSyncBlockInfo) +CDAC_TYPE_FIELD(EnCSyncBlockInfo, T_POINTER, List, cdac_data::List) +CDAC_TYPE_END(EnCSyncBlockInfo) #endif // FEATURE_METADATA_UPDATER CDAC_TYPE_BEGIN(ModuleLookupMap) diff --git a/src/coreclr/vm/encee.h b/src/coreclr/vm/encee.h index d7c4fc0a969f47..09f0e8a47051a8 100644 --- a/src/coreclr/vm/encee.h +++ b/src/coreclr/vm/encee.h @@ -78,6 +78,8 @@ class EnCFieldDesc : public FieldDesc private: + friend struct ::cdac_data; + // True if Fixup() has been called on this instance BOOL m_bNeedsFixup; @@ -188,10 +190,10 @@ struct cdac_data }; template<> -struct cdac_data +struct cdac_data { - static constexpr size_t Next = offsetof(EnCAddedFieldElement, m_next); - static constexpr size_t FieldDesc = offsetof(EnCAddedFieldElement, m_fieldDesc); + static constexpr size_t NeedsFixup = offsetof(EnCFieldDesc, m_bNeedsFixup); + static constexpr size_t StaticFieldData = offsetof(EnCFieldDesc, m_pStaticFieldData); }; //--------------------------------------------------------------------------------------- @@ -379,6 +381,8 @@ class EnCSyncBlockInfo void Cleanup(); private: + friend struct ::cdac_data; + // Gets the address of an EnC field accounting for its type: valuetype, class or primitive PTR_CBYTE GetEnCFieldAddrFromHelperFieldDesc(FieldDesc * pHelperFieldDesc, OBJECTREF pHelper, @@ -388,6 +392,12 @@ class EnCSyncBlockInfo PTR_EnCAddedField m_pList; }; +template<> +struct cdac_data +{ + static constexpr size_t List = offsetof(EnCSyncBlockInfo, m_pList); +}; + // The DPTR is actually defined in syncblk.h to make it visible to SyncBlock // typedef DPTR(EnCSyncBlockInfo) PTR_EnCSyncBlockInfo; diff --git a/src/coreclr/vm/syncblk.h b/src/coreclr/vm/syncblk.h index 10938071588b3a..238416cc583568 100644 --- a/src/coreclr/vm/syncblk.h +++ b/src/coreclr/vm/syncblk.h @@ -555,7 +555,9 @@ struct cdac_data static constexpr size_t ThinLock = offsetof(SyncBlock, m_thinLock); static constexpr size_t LinkNext = offsetof(SyncBlock, m_pNext); static constexpr size_t HashCode = offsetof(SyncBlock, m_dwHashCode); - +#ifdef FEATURE_METADATA_UPDATER + static constexpr size_t EnCInfo = offsetof(SyncBlock, m_pEnCInfo); +#endif // FEATURE_METADATA_UPDATER }; class SyncTableEntry diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IRuntimeMutableTypeSystem.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IRuntimeMutableTypeSystem.cs index b028832e40d840..cecb94c10aeb81 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IRuntimeMutableTypeSystem.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IRuntimeMutableTypeSystem.cs @@ -9,11 +9,11 @@ namespace Microsoft.Diagnostics.DataContractReader.Contracts; public interface IRuntimeMutableTypeSystem : IContract { static string IContract.Name { get; } = nameof(RuntimeMutableTypeSystem); - - IEnumerable EnumerateAddedFieldDescs(TypeHandle typeHandle, bool staticFields) - => throw new NotImplementedException(); - + IEnumerable EnumerateAddedFieldDescs(TypeHandle typeHandle, bool staticFields) => throw new NotImplementedException(); bool IsFieldDescEnCNew(TargetPointer fieldDescPointer) => throw new NotImplementedException(); + bool DoesEnCFieldDescNeedFixup(TargetPointer encFieldDescPointer) => throw new NotImplementedException(); + TargetPointer GetEnCStaticFieldDataAddress(TargetPointer encFieldDescPointer) => throw new NotImplementedException(); + TargetPointer GetEnCInstanceFieldAddress(TargetPointer objectAddress, TargetPointer encFieldDescPointer) => throw new NotImplementedException(); } public readonly struct RuntimeMutableTypeSystem : IRuntimeMutableTypeSystem diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/CorDbHResults.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/CorDbHResults.cs index 5e425868cb22cc..e9677b6b92b48e 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/CorDbHResults.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/CorDbHResults.cs @@ -15,4 +15,5 @@ public static class CorDbgHResults public const int CORDBG_S_NOT_ALL_BITS_SET = unchecked((int)0x00131c13); public const int CORDBG_E_NON_MATCHING_CONTEXT = unchecked((int)0x80131327); public const int CORDBG_E_UNSUPPORTED_DELEGATE = unchecked((int)0x80131c68); + public const int CORDBG_E_ENC_HANGING_FIELD = unchecked((int)0x80131342); } diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeMutableTypeSystem_1.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeMutableTypeSystem_1.cs index b6f4f219ddd72a..c58e5ef4a58b59 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeMutableTypeSystem_1.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeMutableTypeSystem_1.cs @@ -81,4 +81,87 @@ IEnumerable IRuntimeMutableTypeSystem.EnumerateAddedFieldDescs(Ty node = element.Next; } } + + bool IRuntimeMutableTypeSystem.DoesEnCFieldDescNeedFixup(TargetPointer encFieldDescPointer) + { + Data.EnCFieldDesc encFieldDesc = _target.ProcessedData.GetOrAdd(encFieldDescPointer); + return encFieldDesc.NeedsFixup != 0; + } + + TargetPointer IRuntimeMutableTypeSystem.GetEnCStaticFieldDataAddress(TargetPointer encFieldDescPointer) + { + Data.EnCFieldDesc encFieldDesc = _target.ProcessedData.GetOrAdd(encFieldDescPointer); + if (encFieldDesc.StaticFieldData == TargetPointer.Null) + return TargetPointer.Null; + + Data.EnCAddedStaticField staticField = _target.ProcessedData.GetOrAdd(encFieldDesc.StaticFieldData); + return staticField.FieldData; + } + + TargetPointer IRuntimeMutableTypeSystem.GetEnCInstanceFieldAddress(TargetPointer objectAddress, TargetPointer encFieldDescPointer) + { + IObject objectContract = _target.Contracts.Object; + IGC gcContract = _target.Contracts.GC; + IRuntimeTypeSystem rts = _target.Contracts.RuntimeTypeSystem; + TargetPointer syncBlockAddress = objectContract.GetSyncBlockAddress(objectAddress); + if (syncBlockAddress == TargetPointer.Null) + return TargetPointer.Null; + + Data.SyncBlock syncBlock = _target.ProcessedData.GetOrAdd(syncBlockAddress); + if (syncBlock.EnCInfo is not TargetPointer encInfoAddress) + return TargetPointer.Null; + + Data.EnCSyncBlockInfo encInfo = _target.ProcessedData.GetOrAdd(encInfoAddress); + + // Walk the linked list of EnCAddedField entries to find the matching FieldDesc + TargetPointer entryPtr = encInfo.List; + while (entryPtr != TargetPointer.Null) + { + Data.EnCAddedField entry = _target.ProcessedData.GetOrAdd(entryPtr); + if (entry.FieldDesc == encFieldDescPointer) + { + // Found it. Get the dependent handle secondary (the EnC helper object). + TargetPointer handleAddress = entry.FieldData.Handle; + if (handleAddress == TargetPointer.Null) + return TargetPointer.Null; + + TargetNUInt secondary = gcContract.GetHandleExtraInfo(handleAddress); + TargetPointer helperObjectAddress = new TargetPointer(secondary.Value); + if (helperObjectAddress == TargetPointer.Null) + return TargetPointer.Null; + + Data.EditAndContinueHelperObject helper = + _target.ProcessedData.GetOrAdd(helperObjectAddress); + TargetPointer objectReferenceAddress = helper.ObjectReferenceAddress; + + // Read the OBJECTREF stored in _objectReference + TargetPointer fieldObject = _target.ReadPointer(objectReferenceAddress); + // Determine field type and compute final address + CorElementType fieldType = rts.GetFieldDescType(encFieldDescPointer); + if (fieldType == CorElementType.ValueType) + { + // Value type is boxed, so unbox to get at the data + if (fieldObject == TargetPointer.Null) + return TargetPointer.Null; + Data.Object boxedObj = _target.ProcessedData.GetOrAdd(fieldObject); + return boxedObj.Data; + } + else if (fieldType == CorElementType.Class) + { + // The OBJECTREF slot itself is the field value location + return objectReferenceAddress; + } + else + { + // Primitive stored in a 1-element array. Get pointer to first element. + if (fieldObject == TargetPointer.Null) + return TargetPointer.Null; + return objectContract.GetArrayData(fieldObject, out _, out _, out _); + } + } + entryPtr = entry.Next; + } + + return TargetPointer.Null; + } } diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/EditAndContinueHelperObject.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/EditAndContinueHelperObject.cs new file mode 100644 index 00000000000000..f2313cbf24528b --- /dev/null +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/EditAndContinueHelperObject.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("System.Diagnostics.EditAndContinueHelper")] +internal sealed partial class EditAndContinueHelperObject : IData +{ + [FieldAddress("_objectReference")] + public TargetPointer ObjectReferenceAddress { get; } +} diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/EnCAddedField.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/EnCAddedField.cs new file mode 100644 index 00000000000000..50eee250a4c4df --- /dev/null +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/EnCAddedField.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.EnCAddedField))] +internal sealed partial class EnCAddedField : IData +{ + [Field] public TargetPointer Next { get; } + [Field] public TargetPointer FieldDesc { get; } + [Field] public ObjectHandle FieldData { get; } +} diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/EnCAddedStaticField.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/EnCAddedStaticField.cs new file mode 100644 index 00000000000000..dfc2ab809cf82b --- /dev/null +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/EnCAddedStaticField.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.EnCAddedStaticField))] +internal sealed partial class EnCAddedStaticField : IData +{ + [Field] public TargetPointer FieldDesc { get; } + [FieldAddress] public TargetPointer FieldData { get; } +} diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/EnCFieldDesc.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/EnCFieldDesc.cs new file mode 100644 index 00000000000000..b69c1c211bd07e --- /dev/null +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/EnCFieldDesc.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.EnCFieldDesc))] +internal sealed partial class EnCFieldDesc : IData +{ + [Field] public int NeedsFixup { get; } + [Field] public TargetPointer StaticFieldData { get; } +} diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/EnCSyncBlockInfo.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/EnCSyncBlockInfo.cs new file mode 100644 index 00000000000000..1e5dfff6b30c91 --- /dev/null +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/EnCSyncBlockInfo.cs @@ -0,0 +1,10 @@ +// 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.EnCSyncBlockInfo))] +internal sealed partial class EnCSyncBlockInfo : IData +{ + [Field] public TargetPointer List { get; } +} diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/SyncBlock.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/SyncBlock.cs index 0235cb7a7d9c75..7123f11d74d27c 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/SyncBlock.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/SyncBlock.cs @@ -12,6 +12,7 @@ internal sealed partial class SyncBlock : IData public InteropSyncBlockInfo? InteropInfo { get; private set; } public ObjectHandle? Lock { get; private set; } + public TargetPointer? EnCInfo { get; private set; } partial void OnInit(Target target, TargetPointer address) { @@ -23,5 +24,12 @@ partial void OnInit(Target target, TargetPointer address) ObjectHandle lockHandle = target.ReadDataField(address, type, nameof(Lock)); if (lockHandle.Handle != TargetPointer.Null) Lock = lockHandle; + + if (type.Fields.ContainsKey(nameof(EnCInfo))) + { + TargetPointer encInfoPointer = target.ReadPointerField(address, type, nameof(EnCInfo)); + if (encInfoPointer != TargetPointer.Null) + EnCInfo = encInfoPointer; + } } } 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 c699a2c4453074..d88b6a11efba06 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/DataType.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/DataType.cs @@ -204,6 +204,10 @@ public enum DataType EnCEEClassData, EnCAddedFieldElement, + EnCFieldDesc, + EnCAddedField, + EnCAddedStaticField, + EnCSyncBlockInfo, UnorderedArrayBase, } 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 b63a939a45de19..3ff659c26dc786 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 @@ -2663,8 +2663,101 @@ public int GetCollectibleTypeStaticAddress(ulong vmField, ulong* pRetVal) return hr; } - public int GetEnCHangingFieldInfo(nint pEnCFieldInfo, nint pFieldData, Interop.BOOL* pfStatic) - => LegacyFallbackHelper.CanFallback() && _legacy is not null ? _legacy.GetEnCHangingFieldInfo(pEnCFieldInfo, pFieldData, pfStatic) : HResults.E_NOTIMPL; + public int GetEnCHangingFieldInfo(EnCHangingFieldInfo* pEnCFieldInfo, FieldData* pFieldData) + { + int hr = HResults.S_OK; + try + { + IRuntimeTypeSystem rts = _target.Contracts.RuntimeTypeSystem; + IRuntimeMutableTypeSystem mrts = _target.Contracts.RuntimeMutableTypeSystem; + Contracts.ILoader loader = _target.Contracts.Loader; + + // Resolve the assembly and module + ulong vmAssembly = ReadLittleEndian(pEnCFieldInfo->objectTypeData.vmAssembly); + uint metadataToken = ReadLittleEndian(pEnCFieldInfo->objectTypeData.metadataToken); + uint fldToken = pEnCFieldInfo->fldToken; + + _ = LookupTypeDefOrRefInAssembly(rts, vmAssembly, metadataToken); + Contracts.ModuleHandle moduleHandle = loader.GetModuleHandleFromAssemblyPtr(new TargetPointer(vmAssembly)); + Contracts.ModuleLookupTables lookupTables = loader.GetLookupTables(moduleHandle); + TargetPointer fieldDescPointer = loader.GetModuleLookupMapElement(lookupTables.FieldDefToDesc, fldToken, out _); + if (fieldDescPointer == TargetPointer.Null || mrts.DoesEnCFieldDescNeedFixup(fieldDescPointer)) + throw Marshal.GetExceptionForHR(CorDbgHResults.CORDBG_E_ENC_HANGING_FIELD)!; + + bool isStatic = rts.IsFieldDescStatic(fieldDescPointer); + TargetPointer fieldAddress; + + if (isStatic) + { + fieldAddress = mrts.GetEnCStaticFieldDataAddress(fieldDescPointer); + } + else + { + TargetPointer objectPointer = new TargetPointer(pEnCFieldInfo->vmObject); + fieldAddress = mrts.GetEnCInstanceFieldAddress(objectPointer, fieldDescPointer); + } + + if (fieldAddress == TargetPointer.Null) + throw Marshal.GetExceptionForHR(CorDbgHResults.CORDBG_E_ENC_HANGING_FIELD)!; + + // Fill the FieldData output struct + *pFieldData = default; + pFieldData->m_fldMetadataToken = fldToken; + pFieldData->m_fFldStorageAvailable = Interop.BOOL.TRUE; + pFieldData->m_fFldIsStatic = isStatic ? (byte)1 : (byte)0; + pFieldData->m_vmFieldDesc = fieldDescPointer.Value; + + if (isStatic) + { + pFieldData->m_pFldStaticAddress = fieldAddress.Value; + } + else + { + // Instance offset is: fieldAddress - (objectAddress + offsetToVars) + ulong objectAddr = pEnCFieldInfo->vmObject; + ulong offsetToVars = pEnCFieldInfo->offsetToVars; + pFieldData->m_fldInstanceOffset = fieldAddress.Value - (objectAddr + offsetToVars); + } + } + catch (System.Exception ex) + { + hr = ex.HResult; + } +#if DEBUG + if (_legacy is not null) + { + FieldData fieldDataLocal; + int hrLocal = _legacy.GetEnCHangingFieldInfo(pEnCFieldInfo, &fieldDataLocal); + Debug.ValidateHResult(hr, hrLocal); + if (hr == HResults.S_OK) + { + Debug.Assert(pFieldData->m_fldMetadataToken == fieldDataLocal.m_fldMetadataToken, + $"cDAC m_fldMetadataToken: {pFieldData->m_fldMetadataToken:X}, DAC: {fieldDataLocal.m_fldMetadataToken:X}"); + Debug.Assert(pFieldData->m_fFldStorageAvailable == fieldDataLocal.m_fFldStorageAvailable, + $"cDAC m_fFldStorageAvailable: {pFieldData->m_fFldStorageAvailable}, DAC: {fieldDataLocal.m_fFldStorageAvailable}"); + Debug.Assert(pFieldData->m_fFldIsStatic == fieldDataLocal.m_fFldIsStatic, + $"cDAC m_fFldIsStatic: {pFieldData->m_fFldIsStatic}, DAC: {fieldDataLocal.m_fFldIsStatic}"); + Debug.Assert(pFieldData->m_fFldIsRVA == fieldDataLocal.m_fFldIsRVA, + $"cDAC m_fFldIsRVA: {pFieldData->m_fFldIsRVA}, DAC: {fieldDataLocal.m_fFldIsRVA}"); + Debug.Assert(pFieldData->m_fFldIsTLS == fieldDataLocal.m_fFldIsTLS, + $"cDAC m_fFldIsTLS: {pFieldData->m_fFldIsTLS}, DAC: {fieldDataLocal.m_fFldIsTLS}"); + Debug.Assert(pFieldData->m_fFldIsPrimitive == fieldDataLocal.m_fFldIsPrimitive, + $"cDAC m_fFldIsPrimitive: {pFieldData->m_fFldIsPrimitive}, DAC: {fieldDataLocal.m_fFldIsPrimitive}"); + Debug.Assert(pFieldData->m_fFldIsCollectibleStatic == fieldDataLocal.m_fFldIsCollectibleStatic, + $"cDAC m_fFldIsCollectibleStatic: {pFieldData->m_fFldIsCollectibleStatic}, DAC: {fieldDataLocal.m_fFldIsCollectibleStatic}"); + Debug.Assert(pFieldData->m_vmFieldDesc == fieldDataLocal.m_vmFieldDesc, + $"cDAC m_vmFieldDesc: {pFieldData->m_vmFieldDesc:X}, DAC: {fieldDataLocal.m_vmFieldDesc:X}"); + if (pFieldData->m_fFldIsStatic != 0) + Debug.Assert(pFieldData->m_pFldStaticAddress == fieldDataLocal.m_pFldStaticAddress, + $"cDAC static addr: {pFieldData->m_pFldStaticAddress:X}, DAC: {fieldDataLocal.m_pFldStaticAddress:X}"); + else + Debug.Assert(pFieldData->m_fldInstanceOffset == fieldDataLocal.m_fldInstanceOffset, + $"cDAC instance offset: {pFieldData->m_fldInstanceOffset:X}, DAC: {fieldDataLocal.m_fldInstanceOffset:X}"); + } + } +#endif + return hr; + } public int EnumerateTypeHandleParams(ulong vmTypeHandle, delegate* unmanaged fpCallback, nint pUserData) 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 754a848e2e8d5a..75d6f2c6d00731 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 @@ -276,6 +276,15 @@ public struct DebuggerIPCE_BasicTypeData [FieldOffset(16)] public ulong vmTypeHandle; // VMPTR_TypeHandle (Portable) } +[StructLayout(LayoutKind.Sequential)] +public struct EnCHangingFieldInfo +{ + public DebuggerIPCE_BasicTypeData objectTypeData; + public ulong vmObject; + public uint offsetToVars; + public uint fldToken; +} + // Matches native DebuggerIPCE_ExpandedTypeData layout (40 bytes). // Contains a union at offset 8 (4 bytes of padding after elementType to align the // 8-byte VMPTR fields inside the union). All fields are stored in little-endian format. @@ -598,7 +607,7 @@ int EnumerateMethodDescParams(ulong vmMethodDesc, ulong genericsToken, uint* pcG int GetCollectibleTypeStaticAddress(ulong vmField, ulong* pRetVal); [PreserveSig] - int GetEnCHangingFieldInfo(nint pEnCFieldInfo, nint pFieldData, Interop.BOOL* pfStatic); + int GetEnCHangingFieldInfo(EnCHangingFieldInfo* pEnCFieldInfo, FieldData* pFieldData); [PreserveSig] int EnumerateTypeHandleParams(ulong vmTypeHandle, diff --git a/src/native/managed/cdac/tests/UnitTests/RuntimeMutableTypeSystemTests.cs b/src/native/managed/cdac/tests/UnitTests/RuntimeMutableTypeSystemTests.cs index 8a14361e17d6e5..5134255237294d 100644 --- a/src/native/managed/cdac/tests/UnitTests/RuntimeMutableTypeSystemTests.cs +++ b/src/native/managed/cdac/tests/UnitTests/RuntimeMutableTypeSystemTests.cs @@ -233,4 +233,164 @@ public void SecondEntryMatches(MockTarget.Architecture arch) ulong[] expected = instanceElems.Select(e => e.Address + fieldDescOffset).ToArray(); Assert.Equal(expected, contract.EnumerateAddedFieldDescs(th, staticFields: false).Select(p => (ulong)p).ToArray()); } + + #region DoesEnCFieldDescNeedFixup tests + + [Theory] + [ClassData(typeof(MockTarget.StdArch))] + public void DoesEnCFieldDescNeedFixup_True(MockTarget.Architecture arch) + { + var helpers = new TargetTestHelpers(arch); + var memBuilder = new MockMemorySpace.Builder(helpers); + var allocator = memBuilder.CreateAllocator(0x0010_0000, 0x0020_0000); + + // EnCFieldDesc layout: NeedsFixup (int32) + StaticFieldData (pointer) + var encFieldDescLayout = new SequentialLayoutBuilder("EnCFieldDesc", arch) + .AddUInt32Field(nameof(Data.EnCFieldDesc.NeedsFixup)) + .AddPointerField(nameof(Data.EnCFieldDesc.StaticFieldData)) + .Build(); + + var fragment = allocator.Allocate((ulong)encFieldDescLayout.Size, "EnCFieldDesc"); + // Write NeedsFixup = 1 + helpers.Write(fragment.Data.AsSpan(encFieldDescLayout.GetField(nameof(Data.EnCFieldDesc.NeedsFixup)).Offset, sizeof(int)), 1); + // StaticFieldData = Null + helpers.WritePointer(fragment.Data.AsSpan(encFieldDescLayout.GetField(nameof(Data.EnCFieldDesc.StaticFieldData)).Offset, helpers.PointerSize), 0); + + var types = new Dictionary + { + [DataType.EnCFieldDesc] = TargetTestHelpers.CreateTypeInfo(encFieldDescLayout), + }; + + var target = new TestPlaceholderTarget.Builder(arch) + .UseReader(memBuilder.GetMemoryContext().ReadFromTarget) + .AddTypes(types) + .AddContract(version: EnCContractVersion) + .Build(); + + IRuntimeMutableTypeSystem contract = target.Contracts.RuntimeMutableTypeSystem; + Assert.True(contract.DoesEnCFieldDescNeedFixup(new TargetPointer(fragment.Address))); + } + + [Theory] + [ClassData(typeof(MockTarget.StdArch))] + public void DoesEnCFieldDescNeedFixup_False(MockTarget.Architecture arch) + { + var helpers = new TargetTestHelpers(arch); + var memBuilder = new MockMemorySpace.Builder(helpers); + var allocator = memBuilder.CreateAllocator(0x0010_0000, 0x0020_0000); + + var encFieldDescLayout = new SequentialLayoutBuilder("EnCFieldDesc", arch) + .AddUInt32Field(nameof(Data.EnCFieldDesc.NeedsFixup)) + .AddPointerField(nameof(Data.EnCFieldDesc.StaticFieldData)) + .Build(); + + var fragment = allocator.Allocate((ulong)encFieldDescLayout.Size, "EnCFieldDesc"); + // Write NeedsFixup = 0 + helpers.Write(fragment.Data.AsSpan(encFieldDescLayout.GetField(nameof(Data.EnCFieldDesc.NeedsFixup)).Offset, sizeof(int)), 0); + helpers.WritePointer(fragment.Data.AsSpan(encFieldDescLayout.GetField(nameof(Data.EnCFieldDesc.StaticFieldData)).Offset, helpers.PointerSize), 0); + + var types = new Dictionary + { + [DataType.EnCFieldDesc] = TargetTestHelpers.CreateTypeInfo(encFieldDescLayout), + }; + + var target = new TestPlaceholderTarget.Builder(arch) + .UseReader(memBuilder.GetMemoryContext().ReadFromTarget) + .AddTypes(types) + .AddContract(version: EnCContractVersion) + .Build(); + + IRuntimeMutableTypeSystem contract = target.Contracts.RuntimeMutableTypeSystem; + Assert.False(contract.DoesEnCFieldDescNeedFixup(new TargetPointer(fragment.Address))); + } + + #endregion + + #region GetEnCStaticFieldDataAddress tests + + [Theory] + [ClassData(typeof(MockTarget.StdArch))] + public void GetEnCStaticFieldDataAddress_NullStaticFieldData_ReturnsNull(MockTarget.Architecture arch) + { + var helpers = new TargetTestHelpers(arch); + var memBuilder = new MockMemorySpace.Builder(helpers); + var allocator = memBuilder.CreateAllocator(0x0010_0000, 0x0020_0000); + + var encFieldDescLayout = new SequentialLayoutBuilder("EnCFieldDesc", arch) + .AddUInt32Field(nameof(Data.EnCFieldDesc.NeedsFixup)) + .AddPointerField(nameof(Data.EnCFieldDesc.StaticFieldData)) + .Build(); + + var fragment = allocator.Allocate((ulong)encFieldDescLayout.Size, "EnCFieldDesc"); + helpers.Write(fragment.Data.AsSpan(encFieldDescLayout.GetField(nameof(Data.EnCFieldDesc.NeedsFixup)).Offset, sizeof(int)), 0); + // StaticFieldData = Null + helpers.WritePointer(fragment.Data.AsSpan(encFieldDescLayout.GetField(nameof(Data.EnCFieldDesc.StaticFieldData)).Offset, helpers.PointerSize), 0); + + var types = new Dictionary + { + [DataType.EnCFieldDesc] = TargetTestHelpers.CreateTypeInfo(encFieldDescLayout), + }; + + var target = new TestPlaceholderTarget.Builder(arch) + .UseReader(memBuilder.GetMemoryContext().ReadFromTarget) + .AddTypes(types) + .AddContract(version: EnCContractVersion) + .Build(); + + IRuntimeMutableTypeSystem contract = target.Contracts.RuntimeMutableTypeSystem; + Assert.Equal(TargetPointer.Null, contract.GetEnCStaticFieldDataAddress(new TargetPointer(fragment.Address))); + } + + [Theory] + [ClassData(typeof(MockTarget.StdArch))] + public void GetEnCStaticFieldDataAddress_ReturnsFieldDataAddress(MockTarget.Architecture arch) + { + var helpers = new TargetTestHelpers(arch); + var memBuilder = new MockMemorySpace.Builder(helpers); + var allocator = memBuilder.CreateAllocator(0x0010_0000, 0x0020_0000); + + // EnCAddedStaticField layout: FieldDesc (pointer) + FieldData (pointer-sized blob) + // FieldData uses [FieldAddress], so the contract returns the address of the FieldData field itself. + var encAddedStaticFieldLayout = new SequentialLayoutBuilder("EnCAddedStaticField", arch) + .AddPointerField(nameof(Data.EnCAddedStaticField.FieldDesc)) + .AddPointerField(nameof(Data.EnCAddedStaticField.FieldData)) + .Build(); + + var encFieldDescLayout = new SequentialLayoutBuilder("EnCFieldDesc", arch) + .AddUInt32Field(nameof(Data.EnCFieldDesc.NeedsFixup)) + .AddPointerField(nameof(Data.EnCFieldDesc.StaticFieldData)) + .Build(); + + // Allocate EnCAddedStaticField + var staticFieldFragment = allocator.Allocate((ulong)encAddedStaticFieldLayout.Size, "EnCAddedStaticField"); + helpers.WritePointer(staticFieldFragment.Data.AsSpan(encAddedStaticFieldLayout.GetField(nameof(Data.EnCAddedStaticField.FieldDesc)).Offset, helpers.PointerSize), 0xABCD_0000); + // FieldData content doesn't matter - we want the address of the field itself + helpers.WritePointer(staticFieldFragment.Data.AsSpan(encAddedStaticFieldLayout.GetField(nameof(Data.EnCAddedStaticField.FieldData)).Offset, helpers.PointerSize), 0x1234_5678); + + // Allocate EnCFieldDesc pointing to the static field + var fieldDescFragment = allocator.Allocate((ulong)encFieldDescLayout.Size, "EnCFieldDesc"); + helpers.Write(fieldDescFragment.Data.AsSpan(encFieldDescLayout.GetField(nameof(Data.EnCFieldDesc.NeedsFixup)).Offset, sizeof(int)), 0); + helpers.WritePointer(fieldDescFragment.Data.AsSpan(encFieldDescLayout.GetField(nameof(Data.EnCFieldDesc.StaticFieldData)).Offset, helpers.PointerSize), staticFieldFragment.Address); + + var types = new Dictionary + { + [DataType.EnCFieldDesc] = TargetTestHelpers.CreateTypeInfo(encFieldDescLayout), + [DataType.EnCAddedStaticField] = TargetTestHelpers.CreateTypeInfo(encAddedStaticFieldLayout), + }; + + var target = new TestPlaceholderTarget.Builder(arch) + .UseReader(memBuilder.GetMemoryContext().ReadFromTarget) + .AddTypes(types) + .AddContract(version: EnCContractVersion) + .Build(); + + IRuntimeMutableTypeSystem contract = target.Contracts.RuntimeMutableTypeSystem; + TargetPointer result = contract.GetEnCStaticFieldDataAddress(new TargetPointer(fieldDescFragment.Address)); + + // The [FieldAddress] attribute means it returns the address of the FieldData field + ulong expectedAddress = staticFieldFragment.Address + (ulong)encAddedStaticFieldLayout.GetField(nameof(Data.EnCAddedStaticField.FieldData)).Offset; + Assert.Equal(new TargetPointer(expectedAddress), result); + } + + #endregion }