From a89ab6d941cf3cc735ef1179ad428acce7560336 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 21 Mar 2026 21:09:14 +0000 Subject: [PATCH 1/3] Add ObjectiveCMarshal cDAC contract and IsTrackedReferenceWithFinalizer on RuntimeTypeSystem Co-authored-by: rcj1 <77995559+rcj1@users.noreply.github.com> Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/b1b4479b-2345-4f90-b6b6-ea9f33340c64 --- .../design/datacontracts/ObjectiveCMarshal.md | 57 ++++++++++ .../design/datacontracts/RuntimeTypeSystem.md | 4 + .../vm/datadescriptor/datadescriptor.inc | 6 ++ src/coreclr/vm/syncblk.h | 3 + .../ContractRegistry.cs | 4 + .../Contracts/IObjectiveCMarshal.cs | 21 ++++ .../Contracts/IRuntimeTypeSystem.cs | 2 + .../Contracts/ObjectiveCMarshalFactory.cs | 21 ++++ .../Contracts/ObjectiveCMarshal_1.cs | 43 ++++++++ .../Contracts/RuntimeTypeSystem_1.cs | 1 + .../Data/InteropSyncBlockInfo.cs | 4 + .../MethodTableFlags_1.cs | 2 + .../SOSDacImpl.cs | 102 +++++++++++++++++- .../CachingContractRegistry.cs | 1 + 14 files changed, 269 insertions(+), 2 deletions(-) create mode 100644 docs/design/datacontracts/ObjectiveCMarshal.md create mode 100644 src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IObjectiveCMarshal.cs create mode 100644 src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ObjectiveCMarshalFactory.cs create mode 100644 src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ObjectiveCMarshal_1.cs diff --git a/docs/design/datacontracts/ObjectiveCMarshal.md b/docs/design/datacontracts/ObjectiveCMarshal.md new file mode 100644 index 00000000000000..692493f57932da --- /dev/null +++ b/docs/design/datacontracts/ObjectiveCMarshal.md @@ -0,0 +1,57 @@ +# Contract ObjectiveCMarshal + +This contract is for getting information related to Objective-C interop marshaling. + +## APIs of contract + +``` csharp +// Get the tagged memory for an Objective-C tracked reference object. +// Returns TargetPointer.Null if the object does not have tagged memory. +// On success, size is set to the size of the tagged memory in bytes. +TargetPointer GetTaggedMemory(TargetPointer address, out TargetNUInt size); +``` + +## Version 1 + +Data descriptors used: +| Data Descriptor Name | Field | Meaning | +| --- | --- | --- | +| `InteropSyncBlockInfo` | `TaggedMemory` | Pointer to the tagged memory for the object (if it exists) | +| `Object` | `m_pMethTab` | Method table for the object | +| `ObjectHeader` | `SyncBlockValue` | Sync block value for the object header | +| `SyncTableEntry` | `SyncBlock` | Pointer to the sync block for the entry | + +Globals used: +| Global Name | Type | Purpose | +| --- | --- | --- | +| `SyncTableEntries` | pointer | Pointer to the sync table entries array | +| `SyncBlockIsHashOrSyncBlockIndex` | uint32 | Bitmask: set if sync block value is a hash code or sync block index | +| `SyncBlockIsHashCode` | uint32 | Bitmask: set if sync block value is a hash code | +| `SyncBlockIndexMask` | uint32 | Mask to extract the sync block index from the sync block value | + +``` csharp +TargetPointer GetTaggedMemory(TargetPointer address, out TargetNUInt size) +{ + size = new TargetNUInt(2 * target.PointerSize); + + ulong objectHeaderSize = /* ObjectHeader size */; + uint syncBlockValue = target.Read(address - objectHeaderSize + /* ObjectHeader::SyncBlockValue offset */); + + // Check if the sync block value represents a sync block index + if ((syncBlockValue & (SyncBlockIsHashCode | SyncBlockIsHashOrSyncBlockIndex)) + != SyncBlockIsHashOrSyncBlockIndex) + return TargetPointer.Null; + + uint index = syncBlockValue & SyncBlockIndexMask; + TargetPointer syncTableEntry = SyncTableEntries + index * /* SyncTableEntry size */; + TargetPointer syncBlockPtr = target.ReadPointer(syncTableEntry + /* SyncTableEntry::SyncBlock offset */); + if (syncBlockPtr == TargetPointer.Null) + return TargetPointer.Null; + + TargetPointer interopInfoPtr = target.ReadPointer(syncBlockPtr + /* SyncBlock::InteropInfo offset */); + if (interopInfoPtr == TargetPointer.Null) + return TargetPointer.Null; + + return target.ReadPointer(interopInfoPtr + /* InteropSyncBlockInfo::TaggedMemory offset */); +} +``` diff --git a/docs/design/datacontracts/RuntimeTypeSystem.md b/docs/design/datacontracts/RuntimeTypeSystem.md index ece3211a5f0cfe..c7a2dcfb20aead 100644 --- a/docs/design/datacontracts/RuntimeTypeSystem.md +++ b/docs/design/datacontracts/RuntimeTypeSystem.md @@ -72,6 +72,8 @@ partial interface IRuntimeTypeSystem : IContract public TargetPointer GetGCThreadStaticsBasePointer(TypeHandle typeHandle, TargetPointer threadPtr); public TargetPointer GetNonGCThreadStaticsBasePointer(TypeHandle typeHandle, TargetPointer threadPtr); public TargetPointer GetFieldDescList(TypeHandle typeHandle); + // True if the MethodTable represents a type tracked as an Objective-C reference type with a finalizer + public bool IsTrackedReferenceWithFinalizer(TypeHandle typeHandle); public TargetPointer GetGCStaticsBasePointer(TypeHandle typeHandle); public TargetPointer GetNonGCStaticsBasePointer(TypeHandle typeHandle); public virtual ReadOnlySpan GetInstantiation(TypeHandle typeHandle); @@ -535,6 +537,8 @@ Contracts used: public TargetPointer GetFieldDescList(TypeHandle typeHandle) => !typeHandle.IsMethodTable() ? TargetPointer.Null : GetClassData(typeHandle).FieldDescList; + public bool IsTrackedReferenceWithFinalizer(TypeHandle typeHandle) => typeHandle.IsMethodTable() && _methodTables[typeHandle.Address].Flags.IsTrackedReferenceWithFinalizer; + public TargetPointer GetGCStaticsBasePointer(TypeHandle typeHandle) { if (!typeHandle.IsMethodTable()) diff --git a/src/coreclr/vm/datadescriptor/datadescriptor.inc b/src/coreclr/vm/datadescriptor/datadescriptor.inc index e73483de784ae5..0b54cfd89a0d66 100644 --- a/src/coreclr/vm/datadescriptor/datadescriptor.inc +++ b/src/coreclr/vm/datadescriptor/datadescriptor.inc @@ -174,6 +174,9 @@ CDAC_TYPE_FIELD(InteropSyncBlockInfo, /*pointer*/, CCW, cdac_data::RCW) CDAC_TYPE_FIELD(InteropSyncBlockInfo, /*pointer*/, CCF, cdac_data::CCF) #endif // FEATURE_COMINTEROP +#ifdef FEATURE_OBJCMARSHAL +CDAC_TYPE_FIELD(InteropSyncBlockInfo, /*pointer*/, TaggedMemory, cdac_data::TaggedMemory) +#endif // FEATURE_OBJCMARSHAL CDAC_TYPE_END(InteropSyncBlockInfo) CDAC_TYPE_BEGIN(SyncBlock) @@ -1428,6 +1431,9 @@ CDAC_GLOBAL_SUB_DESCRIPTOR(GC, &(g_gc_dac_vars.gc_descriptor)) #if FEATURE_COMINTEROP CDAC_GLOBAL_CONTRACT(BuiltInCOM, 1) #endif // FEATURE_COMINTEROP +#ifdef FEATURE_OBJCMARSHAL +CDAC_GLOBAL_CONTRACT(ObjectiveCMarshal, 1) +#endif // FEATURE_OBJCMARSHAL CDAC_GLOBAL_CONTRACT(CodeVersions, 1) #ifdef FEATURE_COMWRAPPERS CDAC_GLOBAL_CONTRACT(ComWrappers, 1) diff --git a/src/coreclr/vm/syncblk.h b/src/coreclr/vm/syncblk.h index 43c46739a5154f..40d9ed4dcb73ce 100644 --- a/src/coreclr/vm/syncblk.h +++ b/src/coreclr/vm/syncblk.h @@ -383,6 +383,9 @@ struct cdac_data static constexpr size_t RCW = offsetof(InteropSyncBlockInfo, m_pRCW); static constexpr size_t CCF = offsetof(InteropSyncBlockInfo, m_pCCF); #endif // FEATURE_COMINTEROP +#ifdef FEATURE_OBJCMARSHAL + static constexpr size_t TaggedMemory = offsetof(InteropSyncBlockInfo, m_taggedMemory); +#endif // FEATURE_OBJCMARSHAL }; typedef DPTR(InteropSyncBlockInfo) PTR_InteropSyncBlockInfo; 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 7362458b770241..d0a3d549cb685d 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/ContractRegistry.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/ContractRegistry.cs @@ -103,6 +103,10 @@ public abstract class ContractRegistry /// public virtual IBuiltInCOM BuiltInCOM => GetContract(); /// + /// Gets an instance of the ObjectiveCMarshal contract for the target. + /// + public virtual IObjectiveCMarshal ObjectiveCMarshal => GetContract(); + /// /// Gets an instance of the ConditionalWeakTable contract for the target. /// public virtual IConditionalWeakTable ConditionalWeakTable => GetContract(); diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IObjectiveCMarshal.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IObjectiveCMarshal.cs new file mode 100644 index 00000000000000..58506bf50f9c7e --- /dev/null +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IObjectiveCMarshal.cs @@ -0,0 +1,21 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; + +namespace Microsoft.Diagnostics.DataContractReader.Contracts; + +public interface IObjectiveCMarshal : IContract +{ + static string IContract.Name { get; } = nameof(ObjectiveCMarshal); + + // Get the tagged memory for an Objective-C tracked reference object. + // Returns TargetPointer.Null if the object does not have tagged memory. + // On success, size is set to the size of the tagged memory in bytes. + TargetPointer GetTaggedMemory(TargetPointer address, out TargetNUInt size) => throw new NotImplementedException(); +} + +public readonly struct ObjectiveCMarshal : IObjectiveCMarshal +{ + // 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 19f20929911633..ee11ea17944b1b 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 @@ -121,6 +121,8 @@ public interface IRuntimeTypeSystem : IContract ushort GetNumStaticFields(TypeHandle typeHandle) => throw new NotImplementedException(); ushort GetNumThreadStaticFields(TypeHandle typeHandle) => throw new NotImplementedException(); TargetPointer GetFieldDescList(TypeHandle typeHandle) => throw new NotImplementedException(); + // True if the MethodTable represents a type tracked as an Objective-C reference type with a finalizer + bool IsTrackedReferenceWithFinalizer(TypeHandle typeHandle) => throw new NotImplementedException(); TargetPointer GetGCStaticsBasePointer(TypeHandle typeHandle) => throw new NotImplementedException(); TargetPointer GetNonGCStaticsBasePointer(TypeHandle typeHandle) => throw new NotImplementedException(); TargetPointer GetGCThreadStaticsBasePointer(TypeHandle typeHandle, TargetPointer threadPtr) => throw new NotImplementedException(); diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ObjectiveCMarshalFactory.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ObjectiveCMarshalFactory.cs new file mode 100644 index 00000000000000..ce677409a6855b --- /dev/null +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ObjectiveCMarshalFactory.cs @@ -0,0 +1,21 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.Diagnostics.DataContractReader.Contracts; + +public sealed class ObjectiveCMarshalFactory : IContractFactory +{ + IObjectiveCMarshal IContractFactory.CreateContract(Target target, int version) + { + TargetPointer syncTableEntries = target.ReadPointer( + target.ReadGlobalPointer(Constants.Globals.SyncTableEntries)); + uint syncBlockIsHashOrSyncBlockIndex = target.ReadGlobal(Constants.Globals.SyncBlockIsHashOrSyncBlockIndex); + uint syncBlockIsHashCode = target.ReadGlobal(Constants.Globals.SyncBlockIsHashCode); + uint syncBlockIndexMask = target.ReadGlobal(Constants.Globals.SyncBlockIndexMask); + return version switch + { + 1 => new ObjectiveCMarshal_1(target, syncTableEntries, syncBlockIsHashOrSyncBlockIndex, syncBlockIsHashCode, syncBlockIndexMask), + _ => default(ObjectiveCMarshal), + }; + } +} diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ObjectiveCMarshal_1.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ObjectiveCMarshal_1.cs new file mode 100644 index 00000000000000..0065cf8794dc30 --- /dev/null +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ObjectiveCMarshal_1.cs @@ -0,0 +1,43 @@ +// 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.Contracts; + +internal readonly struct ObjectiveCMarshal_1 : IObjectiveCMarshal +{ + private readonly Target _target; + private readonly TargetPointer _syncTableEntries; + private readonly uint _syncBlockIsHashOrSyncBlockIndex; + private readonly uint _syncBlockIsHashCode; + private readonly uint _syncBlockIndexMask; + + internal ObjectiveCMarshal_1(Target target, TargetPointer syncTableEntries, + uint syncBlockIsHashOrSyncBlockIndex, uint syncBlockIsHashCode, uint syncBlockIndexMask) + { + _target = target; + _syncTableEntries = syncTableEntries; + _syncBlockIsHashOrSyncBlockIndex = syncBlockIsHashOrSyncBlockIndex; + _syncBlockIsHashCode = syncBlockIsHashCode; + _syncBlockIndexMask = syncBlockIndexMask; + } + + public TargetPointer GetTaggedMemory(TargetPointer address, out TargetNUInt size) + { + size = new TargetNUInt(2 * (ulong)_target.PointerSize); + + ulong objectHeaderSize = _target.GetTypeInfo(DataType.ObjectHeader).Size!.Value; + Data.ObjectHeader header = _target.ProcessedData.GetOrAdd(address - objectHeaderSize); + uint syncBlockValue = header.SyncBlockValue; + + // Check if the sync block value represents a sync block index + if ((syncBlockValue & (_syncBlockIsHashCode | _syncBlockIsHashOrSyncBlockIndex)) + != _syncBlockIsHashOrSyncBlockIndex) + return TargetPointer.Null; + + uint index = syncBlockValue & _syncBlockIndexMask; + ulong offsetInSyncTableEntries = index * (ulong)_target.GetTypeInfo(DataType.SyncTableEntry).Size!; + Data.SyncTableEntry entry = _target.ProcessedData.GetOrAdd(_syncTableEntries + offsetInSyncTableEntries); + + return entry.SyncBlock?.InteropInfo?.TaggedMemory ?? TargetPointer.Null; + } +} 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 62279bd90c1172..2bceb16f0400a7 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeTypeSystem_1.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeTypeSystem_1.cs @@ -565,6 +565,7 @@ public ushort GetNumVtableSlots(TypeHandle typeHandle) public ushort GetNumStaticFields(TypeHandle typeHandle) => !typeHandle.IsMethodTable() ? (ushort)0 : GetClassData(typeHandle).NumStaticFields; public ushort GetNumThreadStaticFields(TypeHandle typeHandle) => !typeHandle.IsMethodTable() ? (ushort)0 : GetClassData(typeHandle).NumThreadStaticFields; public TargetPointer GetFieldDescList(TypeHandle typeHandle) => !typeHandle.IsMethodTable() ? TargetPointer.Null : GetClassData(typeHandle).FieldDescList; + public bool IsTrackedReferenceWithFinalizer(TypeHandle typeHandle) => typeHandle.IsMethodTable() && _methodTables[typeHandle.Address].Flags.IsTrackedReferenceWithFinalizer; private TargetPointer GetDynamicStaticsInfo(TypeHandle typeHandle) { if (!typeHandle.IsMethodTable()) diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/InteropSyncBlockInfo.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/InteropSyncBlockInfo.cs index a38a57f1688603..e0791156275acc 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/InteropSyncBlockInfo.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/InteropSyncBlockInfo.cs @@ -21,9 +21,13 @@ public InteropSyncBlockInfo(Target target, TargetPointer address) CCF = type.Fields.TryGetValue(nameof(CCF), out Target.FieldInfo ccfField) ? target.ReadPointer(address + (ulong)ccfField.Offset) : TargetPointer.Null; + TaggedMemory = type.Fields.TryGetValue(nameof(TaggedMemory), out Target.FieldInfo taggedMemoryField) + ? target.ReadPointer(address + (ulong)taggedMemoryField.Offset) + : TargetPointer.Null; } public TargetPointer RCW { get; init; } public TargetPointer CCW { get; init; } public TargetPointer CCF { get; init; } + public TargetPointer TaggedMemory { get; init; } } diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/RuntimeTypeSystemHelpers/MethodTableFlags_1.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/RuntimeTypeSystemHelpers/MethodTableFlags_1.cs index 308430979da49b..dff68f70334a53 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/RuntimeTypeSystemHelpers/MethodTableFlags_1.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/RuntimeTypeSystemHelpers/MethodTableFlags_1.cs @@ -47,6 +47,7 @@ internal enum WFLAGS_HIGH : uint Collectible = 0x00200000, // GC depends on this bit. ContainsGCPointers = 0x01000000, + IsTrackedReferenceWithFinalizer = 0x04000000, HasComponentSize = 0x80000000, // This is set if lower 16 bits is used for the component size, // otherwise the lower bits are used for WFLAGS_LOW } @@ -99,6 +100,7 @@ private bool TestFlagWithMask(WFLAGS2_ENUM mask, WFLAGS2_ENUM flag) public bool HasInstantiation => !TestFlagWithMask(WFLAGS_LOW.GenericsMask, WFLAGS_LOW.GenericsMask_NonGeneric); public bool ContainsGCPointers => GetFlag(WFLAGS_HIGH.ContainsGCPointers) != 0; public bool IsCollectible => GetFlag(WFLAGS_HIGH.Collectible) != 0; + public bool IsTrackedReferenceWithFinalizer => GetFlag(WFLAGS_HIGH.IsTrackedReferenceWithFinalizer) != 0; public bool IsDynamicStatics => GetFlag(WFLAGS2_ENUM.DynamicStatics) != 0; public bool IsGenericTypeDefinition => TestFlagWithMask(WFLAGS_LOW.GenericsMask, WFLAGS_LOW.GenericsMask_TypicalInstantiation); diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/SOSDacImpl.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/SOSDacImpl.cs index 33cea4425e5c30..cd65af48716828 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/SOSDacImpl.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/SOSDacImpl.cs @@ -5663,9 +5663,107 @@ int ISOSDacInterface10.GetComWrappersRCWData(ClrDataAddress rcw, ClrDataAddress* #region ISOSDacInterface11 int ISOSDacInterface11.IsTrackedType(ClrDataAddress objAddr, Interop.BOOL* isTrackedType, Interop.BOOL* hasTaggedMemory) - => _legacyImpl11 is not null ? _legacyImpl11.IsTrackedType(objAddr, isTrackedType, hasTaggedMemory) : HResults.E_NOTIMPL; + { + int hr = HResults.S_OK; + if (objAddr == 0 || isTrackedType == null || hasTaggedMemory == null) + { + hr = HResults.E_INVALIDARG; + } + else + { + try + { + *isTrackedType = Interop.BOOL.FALSE; + *hasTaggedMemory = Interop.BOOL.FALSE; + IObject objectContract = _target.Contracts.Object; + IRuntimeTypeSystem rtsContract = _target.Contracts.RuntimeTypeSystem; + IObjectiveCMarshal objcContract = _target.Contracts.ObjectiveCMarshal; + TargetPointer objPtr = objAddr.ToTargetPointer(_target); + TargetPointer mt = objectContract.GetMethodTableAddress(objPtr); + if (mt == TargetPointer.Null) + { + hr = HResults.E_INVALIDARG; + } + else + { + TypeHandle mtHandle = rtsContract.GetTypeHandle(mt); + if (rtsContract.IsTrackedReferenceWithFinalizer(mtHandle)) + *isTrackedType = Interop.BOOL.TRUE; + hr = (*isTrackedType == Interop.BOOL.TRUE) ? HResults.S_OK : HResults.S_FALSE; + TargetPointer taggedMemoryPtr = objcContract.GetTaggedMemory(objPtr, out TargetNUInt _); + if (taggedMemoryPtr != TargetPointer.Null) + *hasTaggedMemory = Interop.BOOL.TRUE; + } + } + catch (System.Exception ex) + { + hr = ex.HResult; + } + } +#if DEBUG + if (_legacyImpl11 is not null) + { + Interop.BOOL isTrackedTypeLocal; + Interop.BOOL hasTaggedMemoryLocal; + int hrLocal = _legacyImpl11.IsTrackedType(objAddr, &isTrackedTypeLocal, &hasTaggedMemoryLocal); + Debug.Assert(hrLocal == hr, $"cDAC: {hr:x}, DAC: {hrLocal:x}"); + if (hr == HResults.S_OK || hr == HResults.S_FALSE) + { + Debug.Assert(*isTrackedType == isTrackedTypeLocal); + Debug.Assert(*hasTaggedMemory == hasTaggedMemoryLocal); + } + } +#endif + return hr; + } + int ISOSDacInterface11.GetTaggedMemory(ClrDataAddress objAddr, ClrDataAddress* taggedMemory, nuint* taggedMemorySizeInBytes) - => _legacyImpl11 is not null ? _legacyImpl11.GetTaggedMemory(objAddr, taggedMemory, taggedMemorySizeInBytes) : HResults.E_NOTIMPL; + { + int hr = HResults.S_OK; + if (objAddr == 0 || taggedMemory == null || taggedMemorySizeInBytes == null) + { + hr = HResults.E_INVALIDARG; + } + else + { + *taggedMemory = 0; + *taggedMemorySizeInBytes = 0; + try + { + IObjectiveCMarshal objcContract = _target.Contracts.ObjectiveCMarshal; + TargetPointer objPtr = objAddr.ToTargetPointer(_target); + TargetPointer taggedMemoryPtr = objcContract.GetTaggedMemory(objPtr, out TargetNUInt taggedMemorySizeNUInt); + if (taggedMemoryPtr != TargetPointer.Null) + { + *taggedMemory = taggedMemoryPtr.ToClrDataAddress(_target); + *taggedMemorySizeInBytes = (nuint)taggedMemorySizeNUInt.Value; + } + else + { + hr = HResults.S_FALSE; + } + } + catch (System.Exception ex) + { + hr = ex.HResult; + } + } +#if DEBUG + if (_legacyImpl11 is not null) + { + ClrDataAddress taggedMemoryLocal; + nuint taggedMemorySizeInBytesLocal; + int hrLocal = _legacyImpl11.GetTaggedMemory(objAddr, &taggedMemoryLocal, &taggedMemorySizeInBytesLocal); + Debug.Assert(hrLocal == hr, $"cDAC: {hr:x}, DAC: {hrLocal:x}"); + if (hr == HResults.S_OK || hr == HResults.S_FALSE) + { + Debug.Assert(*taggedMemory == taggedMemoryLocal); + Debug.Assert(*taggedMemorySizeInBytes == taggedMemorySizeInBytesLocal); + } + } +#endif + return hr; + } #endregion ISOSDacInterface11 #region ISOSDacInterface12 diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader/CachingContractRegistry.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader/CachingContractRegistry.cs index bc4766f0e019a5..031e4da8c5e7d2 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader/CachingContractRegistry.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader/CachingContractRegistry.cs @@ -49,6 +49,7 @@ public CachingContractRegistry(Target target, TryGetContractVersionDelegate tryG [typeof(ISignatureDecoder)] = new SignatureDecoderFactory(), [typeof(ISyncBlock)] = new SyncBlockFactory(), [typeof(IBuiltInCOM)] = new BuiltInCOMFactory(), + [typeof(IObjectiveCMarshal)] = new ObjectiveCMarshalFactory(), [typeof(IConditionalWeakTable)] = new ConditionalWeakTableFactory(), }; From c418119897073982b2bffe7c3d8cee02f917b850 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 21 Mar 2026 21:13:01 +0000 Subject: [PATCH 2/3] Fix GetTaggedMemory to set size only on success, clarify comment Co-authored-by: rcj1 <77995559+rcj1@users.noreply.github.com> Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/b1b4479b-2345-4f90-b6b6-ea9f33340c64 --- .../Contracts/IObjectiveCMarshal.cs | 2 +- .../Contracts/ObjectiveCMarshal_1.cs | 7 +++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IObjectiveCMarshal.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IObjectiveCMarshal.cs index 58506bf50f9c7e..f87270fd0b3c4e 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IObjectiveCMarshal.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IObjectiveCMarshal.cs @@ -11,7 +11,7 @@ public interface IObjectiveCMarshal : IContract // Get the tagged memory for an Objective-C tracked reference object. // Returns TargetPointer.Null if the object does not have tagged memory. - // On success, size is set to the size of the tagged memory in bytes. + // On success, size is set to the size of the tagged memory in bytes; otherwise size is set to default. TargetPointer GetTaggedMemory(TargetPointer address, out TargetNUInt size) => throw new NotImplementedException(); } diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ObjectiveCMarshal_1.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ObjectiveCMarshal_1.cs index 0065cf8794dc30..c71fe508e4de20 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ObjectiveCMarshal_1.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ObjectiveCMarshal_1.cs @@ -23,7 +23,7 @@ internal ObjectiveCMarshal_1(Target target, TargetPointer syncTableEntries, public TargetPointer GetTaggedMemory(TargetPointer address, out TargetNUInt size) { - size = new TargetNUInt(2 * (ulong)_target.PointerSize); + size = default; ulong objectHeaderSize = _target.GetTypeInfo(DataType.ObjectHeader).Size!.Value; Data.ObjectHeader header = _target.ProcessedData.GetOrAdd(address - objectHeaderSize); @@ -38,6 +38,9 @@ public TargetPointer GetTaggedMemory(TargetPointer address, out TargetNUInt size ulong offsetInSyncTableEntries = index * (ulong)_target.GetTypeInfo(DataType.SyncTableEntry).Size!; Data.SyncTableEntry entry = _target.ProcessedData.GetOrAdd(_syncTableEntries + offsetInSyncTableEntries); - return entry.SyncBlock?.InteropInfo?.TaggedMemory ?? TargetPointer.Null; + TargetPointer taggedMemory = entry.SyncBlock?.InteropInfo?.TaggedMemory ?? TargetPointer.Null; + if (taggedMemory != TargetPointer.Null) + size = new TargetNUInt(2 * (ulong)_target.PointerSize); + return taggedMemory; } } From 3f77676cfd291b6c2c409dae9d4c4e88d0ce9a5c Mon Sep 17 00:00:00 2001 From: rcj1 Date: Tue, 24 Mar 2026 15:01:05 -0700 Subject: [PATCH 3/3] fixing logic and adding tests --- .../design/datacontracts/ObjectiveCMarshal.md | 32 ++-- src/coreclr/vm/methodtable.h | 2 +- src/coreclr/vm/syncblk.h | 1 + .../Contracts/IObjectiveCMarshal.cs | 4 +- .../Contracts/ObjectiveCMarshalFactory.cs | 4 +- .../Contracts/ObjectiveCMarshal_1.cs | 17 +-- .../SOSDacImpl.cs | 90 +++++++----- .../MockDescriptors/MockDescriptors.Object.cs | 13 +- .../MockDescriptors.SyncBlock.cs | 4 +- .../tests/MockDescriptors/MockDescriptors.cs | 1 + .../cdac/tests/ObjectiveCMarshalTests.cs | 139 ++++++++++++++++++ 11 files changed, 236 insertions(+), 71 deletions(-) create mode 100644 src/native/managed/cdac/tests/ObjectiveCMarshalTests.cs diff --git a/docs/design/datacontracts/ObjectiveCMarshal.md b/docs/design/datacontracts/ObjectiveCMarshal.md index 692493f57932da..2a520ccdf4136b 100644 --- a/docs/design/datacontracts/ObjectiveCMarshal.md +++ b/docs/design/datacontracts/ObjectiveCMarshal.md @@ -6,9 +6,9 @@ This contract is for getting information related to Objective-C interop marshali ``` csharp // Get the tagged memory for an Objective-C tracked reference object. -// Returns TargetPointer.Null if the object does not have tagged memory. -// On success, size is set to the size of the tagged memory in bytes. -TargetPointer GetTaggedMemory(TargetPointer address, out TargetNUInt size); +// Returns false if the object does not have tagged memory. +// On success, size is set to the size of the tagged memory in bytes; otherwise size is set to default. +bool TryGetTaggedMemory(TargetPointer address, out TargetNUInt size, out TargetPointer taggedMemory); ``` ## Version 1 @@ -19,20 +19,24 @@ Data descriptors used: | `InteropSyncBlockInfo` | `TaggedMemory` | Pointer to the tagged memory for the object (if it exists) | | `Object` | `m_pMethTab` | Method table for the object | | `ObjectHeader` | `SyncBlockValue` | Sync block value for the object header | -| `SyncTableEntry` | `SyncBlock` | Pointer to the sync block for the entry | + +Contracts used: +| Contract Name | +| --- | +| `SyncBlock` | Globals used: | Global Name | Type | Purpose | | --- | --- | --- | -| `SyncTableEntries` | pointer | Pointer to the sync table entries array | | `SyncBlockIsHashOrSyncBlockIndex` | uint32 | Bitmask: set if sync block value is a hash code or sync block index | | `SyncBlockIsHashCode` | uint32 | Bitmask: set if sync block value is a hash code | | `SyncBlockIndexMask` | uint32 | Mask to extract the sync block index from the sync block value | ``` csharp -TargetPointer GetTaggedMemory(TargetPointer address, out TargetNUInt size) +bool TryGetTaggedMemory(TargetPointer address, out TargetNUInt size, out TargetPointer taggedMemory) { - size = new TargetNUInt(2 * target.PointerSize); + size = default; + taggedMemory = TargetPointer.Null; ulong objectHeaderSize = /* ObjectHeader size */; uint syncBlockValue = target.Read(address - objectHeaderSize + /* ObjectHeader::SyncBlockValue offset */); @@ -40,18 +44,18 @@ TargetPointer GetTaggedMemory(TargetPointer address, out TargetNUInt size) // Check if the sync block value represents a sync block index if ((syncBlockValue & (SyncBlockIsHashCode | SyncBlockIsHashOrSyncBlockIndex)) != SyncBlockIsHashOrSyncBlockIndex) - return TargetPointer.Null; + return false; uint index = syncBlockValue & SyncBlockIndexMask; - TargetPointer syncTableEntry = SyncTableEntries + index * /* SyncTableEntry size */; - TargetPointer syncBlockPtr = target.ReadPointer(syncTableEntry + /* SyncTableEntry::SyncBlock offset */); - if (syncBlockPtr == TargetPointer.Null) - return TargetPointer.Null; + TargetPointer syncBlockPtr = target.Contracts.SyncBlock.GetSyncBlock(index); TargetPointer interopInfoPtr = target.ReadPointer(syncBlockPtr + /* SyncBlock::InteropInfo offset */); if (interopInfoPtr == TargetPointer.Null) - return TargetPointer.Null; + return false; - return target.ReadPointer(interopInfoPtr + /* InteropSyncBlockInfo::TaggedMemory offset */); + taggedMemory = target.ReadPointer(interopInfoPtr + /* InteropSyncBlockInfo::TaggedMemory offset */); + if (taggedMemory != TargetPointer.Null) + size = new TargetNUInt(2 * target.PointerSize); + return taggedMemory != TargetPointer.Null; } ``` diff --git a/src/coreclr/vm/methodtable.h b/src/coreclr/vm/methodtable.h index b99dcb293337be..f01b09bcfe0d36 100644 --- a/src/coreclr/vm/methodtable.h +++ b/src/coreclr/vm/methodtable.h @@ -3817,7 +3817,7 @@ public : enum_flag_ContainsGCPointers = 0x01000000, // Contains object references. [cDAC] [RuntimeTypeSystem]: Contract depends on this value enum_flag_HasTypeEquivalence = 0x02000000, // can be equivalent to another type - enum_flag_IsTrackedReferenceWithFinalizer = 0x04000000, + enum_flag_IsTrackedReferenceWithFinalizer = 0x04000000, // [cDAC] [RuntimeTypeSystem]: Contract depends on this value. // unused = 0x08000000, enum_flag_IDynamicInterfaceCastable = 0x10000000, // class implements IDynamicInterfaceCastable interface diff --git a/src/coreclr/vm/syncblk.h b/src/coreclr/vm/syncblk.h index 40d9ed4dcb73ce..f1d5dc5b678840 100644 --- a/src/coreclr/vm/syncblk.h +++ b/src/coreclr/vm/syncblk.h @@ -369,6 +369,7 @@ class InteropSyncBlockInfo // should be updated as well. // See the TAGGED_MEMORY_SIZE_IN_POINTERS constant in // ObjectiveCMarshal.NativeAot.cs + // [cDAC] [ObjectiveCMarshal] : Contract depends on this size. BYTE m_taggedAlloc[2 * sizeof(void*)]; #endif // FEATURE_OBJCMARSHAL diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IObjectiveCMarshal.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IObjectiveCMarshal.cs index f87270fd0b3c4e..0a6629acf01ebc 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IObjectiveCMarshal.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IObjectiveCMarshal.cs @@ -10,9 +10,9 @@ public interface IObjectiveCMarshal : IContract static string IContract.Name { get; } = nameof(ObjectiveCMarshal); // Get the tagged memory for an Objective-C tracked reference object. - // Returns TargetPointer.Null if the object does not have tagged memory. + // Returns false if the object does not have tagged memory. // On success, size is set to the size of the tagged memory in bytes; otherwise size is set to default. - TargetPointer GetTaggedMemory(TargetPointer address, out TargetNUInt size) => throw new NotImplementedException(); + bool TryGetTaggedMemory(TargetPointer address, out TargetNUInt size, out TargetPointer taggedMemory) => throw new NotImplementedException(); } public readonly struct ObjectiveCMarshal : IObjectiveCMarshal diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ObjectiveCMarshalFactory.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ObjectiveCMarshalFactory.cs index ce677409a6855b..ebc9c202105a50 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ObjectiveCMarshalFactory.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ObjectiveCMarshalFactory.cs @@ -7,14 +7,12 @@ public sealed class ObjectiveCMarshalFactory : IContractFactory.CreateContract(Target target, int version) { - TargetPointer syncTableEntries = target.ReadPointer( - target.ReadGlobalPointer(Constants.Globals.SyncTableEntries)); uint syncBlockIsHashOrSyncBlockIndex = target.ReadGlobal(Constants.Globals.SyncBlockIsHashOrSyncBlockIndex); uint syncBlockIsHashCode = target.ReadGlobal(Constants.Globals.SyncBlockIsHashCode); uint syncBlockIndexMask = target.ReadGlobal(Constants.Globals.SyncBlockIndexMask); return version switch { - 1 => new ObjectiveCMarshal_1(target, syncTableEntries, syncBlockIsHashOrSyncBlockIndex, syncBlockIsHashCode, syncBlockIndexMask), + 1 => new ObjectiveCMarshal_1(target, syncBlockIsHashOrSyncBlockIndex, syncBlockIsHashCode, syncBlockIndexMask), _ => default(ObjectiveCMarshal), }; } diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ObjectiveCMarshal_1.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ObjectiveCMarshal_1.cs index c71fe508e4de20..1e35bbe68a37c9 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ObjectiveCMarshal_1.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ObjectiveCMarshal_1.cs @@ -6,24 +6,23 @@ namespace Microsoft.Diagnostics.DataContractReader.Contracts; internal readonly struct ObjectiveCMarshal_1 : IObjectiveCMarshal { private readonly Target _target; - private readonly TargetPointer _syncTableEntries; private readonly uint _syncBlockIsHashOrSyncBlockIndex; private readonly uint _syncBlockIsHashCode; private readonly uint _syncBlockIndexMask; - internal ObjectiveCMarshal_1(Target target, TargetPointer syncTableEntries, + internal ObjectiveCMarshal_1(Target target, uint syncBlockIsHashOrSyncBlockIndex, uint syncBlockIsHashCode, uint syncBlockIndexMask) { _target = target; - _syncTableEntries = syncTableEntries; _syncBlockIsHashOrSyncBlockIndex = syncBlockIsHashOrSyncBlockIndex; _syncBlockIsHashCode = syncBlockIsHashCode; _syncBlockIndexMask = syncBlockIndexMask; } - public TargetPointer GetTaggedMemory(TargetPointer address, out TargetNUInt size) + public bool TryGetTaggedMemory(TargetPointer address, out TargetNUInt size, out TargetPointer taggedMemory) { size = default; + taggedMemory = TargetPointer.Null; ulong objectHeaderSize = _target.GetTypeInfo(DataType.ObjectHeader).Size!.Value; Data.ObjectHeader header = _target.ProcessedData.GetOrAdd(address - objectHeaderSize); @@ -32,15 +31,15 @@ public TargetPointer GetTaggedMemory(TargetPointer address, out TargetNUInt size // Check if the sync block value represents a sync block index if ((syncBlockValue & (_syncBlockIsHashCode | _syncBlockIsHashOrSyncBlockIndex)) != _syncBlockIsHashOrSyncBlockIndex) - return TargetPointer.Null; + return false; uint index = syncBlockValue & _syncBlockIndexMask; - ulong offsetInSyncTableEntries = index * (ulong)_target.GetTypeInfo(DataType.SyncTableEntry).Size!; - Data.SyncTableEntry entry = _target.ProcessedData.GetOrAdd(_syncTableEntries + offsetInSyncTableEntries); + TargetPointer syncBlock = _target.Contracts.SyncBlock.GetSyncBlock(index); + Data.SyncBlock sb = _target.ProcessedData.GetOrAdd(syncBlock); - TargetPointer taggedMemory = entry.SyncBlock?.InteropInfo?.TaggedMemory ?? TargetPointer.Null; + taggedMemory = sb.InteropInfo?.TaggedMemory ?? TargetPointer.Null; if (taggedMemory != TargetPointer.Null) size = new TargetNUInt(2 * (ulong)_target.PointerSize); - return taggedMemory; + return taggedMemory != TargetPointer.Null; } } diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/SOSDacImpl.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/SOSDacImpl.cs index cd65af48716828..636eb967d6f92a 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/SOSDacImpl.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/SOSDacImpl.cs @@ -5665,40 +5665,47 @@ int ISOSDacInterface10.GetComWrappersRCWData(ClrDataAddress rcw, ClrDataAddress* int ISOSDacInterface11.IsTrackedType(ClrDataAddress objAddr, Interop.BOOL* isTrackedType, Interop.BOOL* hasTaggedMemory) { int hr = HResults.S_OK; - if (objAddr == 0 || isTrackedType == null || hasTaggedMemory == null) - { - hr = HResults.E_INVALIDARG; - } - else + try { - try + if (objAddr == 0 || isTrackedType == null || hasTaggedMemory == null) + { + throw new ArgumentException(); + } + else { + *isTrackedType = Interop.BOOL.FALSE; *hasTaggedMemory = Interop.BOOL.FALSE; IObject objectContract = _target.Contracts.Object; - IRuntimeTypeSystem rtsContract = _target.Contracts.RuntimeTypeSystem; - IObjectiveCMarshal objcContract = _target.Contracts.ObjectiveCMarshal; TargetPointer objPtr = objAddr.ToTargetPointer(_target); TargetPointer mt = objectContract.GetMethodTableAddress(objPtr); + IRuntimeTypeSystem rtsContract = _target.Contracts.RuntimeTypeSystem; + TypeHandle mtHandle = rtsContract.GetTypeHandle(mt); if (mt == TargetPointer.Null) { - hr = HResults.E_INVALIDARG; + throw new ArgumentException(); } else { - TypeHandle mtHandle = rtsContract.GetTypeHandle(mt); if (rtsContract.IsTrackedReferenceWithFinalizer(mtHandle)) *isTrackedType = Interop.BOOL.TRUE; hr = (*isTrackedType == Interop.BOOL.TRUE) ? HResults.S_OK : HResults.S_FALSE; - TargetPointer taggedMemoryPtr = objcContract.GetTaggedMemory(objPtr, out TargetNUInt _); - if (taggedMemoryPtr != TargetPointer.Null) - *hasTaggedMemory = Interop.BOOL.TRUE; + try + { + IObjectiveCMarshal objcContract = _target.Contracts.ObjectiveCMarshal; + if (objcContract.TryGetTaggedMemory(objPtr, out _, out _)) + *hasTaggedMemory = Interop.BOOL.TRUE; + } + catch (NotImplementedException) + { + // TryGetTaggedMemory may not be implemented if ObjectiveCMarshal contract is not present + } } } - catch (System.Exception ex) - { - hr = ex.HResult; - } + } + catch (System.Exception ex) + { + hr = ex.HResult; } #if DEBUG if (_legacyImpl11 is not null) @@ -5706,7 +5713,7 @@ int ISOSDacInterface11.IsTrackedType(ClrDataAddress objAddr, Interop.BOOL* isTra Interop.BOOL isTrackedTypeLocal; Interop.BOOL hasTaggedMemoryLocal; int hrLocal = _legacyImpl11.IsTrackedType(objAddr, &isTrackedTypeLocal, &hasTaggedMemoryLocal); - Debug.Assert(hrLocal == hr, $"cDAC: {hr:x}, DAC: {hrLocal:x}"); + Debug.ValidateHResult(hr, hrLocal); if (hr == HResults.S_OK || hr == HResults.S_FALSE) { Debug.Assert(*isTrackedType == isTrackedTypeLocal); @@ -5720,33 +5727,42 @@ int ISOSDacInterface11.IsTrackedType(ClrDataAddress objAddr, Interop.BOOL* isTra int ISOSDacInterface11.GetTaggedMemory(ClrDataAddress objAddr, ClrDataAddress* taggedMemory, nuint* taggedMemorySizeInBytes) { int hr = HResults.S_OK; - if (objAddr == 0 || taggedMemory == null || taggedMemorySizeInBytes == null) - { - hr = HResults.E_INVALIDARG; - } - else + try { - *taggedMemory = 0; - *taggedMemorySizeInBytes = 0; - try + if (objAddr == 0 || taggedMemory == null || taggedMemorySizeInBytes == null) + { + throw new ArgumentException(); + } + else { - IObjectiveCMarshal objcContract = _target.Contracts.ObjectiveCMarshal; + *taggedMemory = 0; + *taggedMemorySizeInBytes = 0; + TargetPointer objPtr = objAddr.ToTargetPointer(_target); - TargetPointer taggedMemoryPtr = objcContract.GetTaggedMemory(objPtr, out TargetNUInt taggedMemorySizeNUInt); - if (taggedMemoryPtr != TargetPointer.Null) + try { - *taggedMemory = taggedMemoryPtr.ToClrDataAddress(_target); - *taggedMemorySizeInBytes = (nuint)taggedMemorySizeNUInt.Value; + IObjectiveCMarshal objcContract = _target.Contracts.ObjectiveCMarshal; + if (objcContract.TryGetTaggedMemory(objPtr, out TargetNUInt taggedMemorySizeNUInt, out TargetPointer taggedMemoryPtr)) + { + *taggedMemory = taggedMemoryPtr.ToClrDataAddress(_target); + *taggedMemorySizeInBytes = (nuint)taggedMemorySizeNUInt.Value; + } + else + { + hr = HResults.S_FALSE; + } } - else + catch (NotImplementedException) { + // TryGetTaggedMemory may not be implemented if ObjectiveCMarshal contract is not present hr = HResults.S_FALSE; } } - catch (System.Exception ex) - { - hr = ex.HResult; - } + } + + catch (System.Exception ex) + { + hr = ex.HResult; } #if DEBUG if (_legacyImpl11 is not null) @@ -5754,7 +5770,7 @@ int ISOSDacInterface11.GetTaggedMemory(ClrDataAddress objAddr, ClrDataAddress* t ClrDataAddress taggedMemoryLocal; nuint taggedMemorySizeInBytesLocal; int hrLocal = _legacyImpl11.GetTaggedMemory(objAddr, &taggedMemoryLocal, &taggedMemorySizeInBytesLocal); - Debug.Assert(hrLocal == hr, $"cDAC: {hr:x}, DAC: {hrLocal:x}"); + Debug.ValidateHResult(hr, hrLocal); if (hr == HResults.S_OK || hr == HResults.S_FALSE) { Debug.Assert(*taggedMemory == taggedMemoryLocal); diff --git a/src/native/managed/cdac/tests/MockDescriptors/MockDescriptors.Object.cs b/src/native/managed/cdac/tests/MockDescriptors/MockDescriptors.Object.cs index d7d18ac6e37d36..2b8590357bd2ed 100644 --- a/src/native/managed/cdac/tests/MockDescriptors/MockDescriptors.Object.cs +++ b/src/native/managed/cdac/tests/MockDescriptors/MockDescriptors.Object.cs @@ -127,7 +127,9 @@ internal TargetPointer AddObject(TargetPointer methodTable, uint prefixSize =0) return fragment.Address + prefixSize; // return pointer to the object, not the prefix; } - internal TargetPointer AddObjectWithSyncBlock(TargetPointer methodTable, uint syncBlockIndex, TargetPointer rcw, TargetPointer ccw, TargetPointer ccf) + internal TargetPointer AddObjectWithSyncBlock(TargetPointer methodTable, uint syncBlockIndex, + TargetPointer rcw, TargetPointer ccw, TargetPointer ccf, + TargetPointer taggedMemory = default) { MockMemorySpace.Builder builder = Builder; TargetTestHelpers targetTestHelpers = builder.TargetTestHelpers; @@ -136,7 +138,8 @@ internal TargetPointer AddObjectWithSyncBlock(TargetPointer methodTable, uint sy if ((syncBlockIndex & SyncBlockIndexMask) != syncBlockIndex) throw new ArgumentOutOfRangeException(nameof(syncBlockIndex), "Invalid sync block index"); - TargetPointer address = AddObject(methodTable, prefixSize: (uint)TestSyncBlockValueToObjectOffset); + uint objectHeaderSize = Types[DataType.ObjectHeader].Size!.Value; + TargetPointer address = AddObject(methodTable, prefixSize: objectHeaderSize); // Add the sync table value before the object uint syncTableValue = IsSyncBlockIndexBits | syncBlockIndex; @@ -145,11 +148,12 @@ internal TargetPointer AddObjectWithSyncBlock(TargetPointer methodTable, uint sy targetTestHelpers.Write(syncTableValueDest, syncTableValue); // Add the actual sync block and associated data - AddSyncBlock(syncBlockIndex, rcw, ccw, ccf); + AddSyncBlock(syncBlockIndex, rcw, ccw, ccf, taggedMemory); return address; } - private void AddSyncBlock(uint index, TargetPointer rcw, TargetPointer ccw, TargetPointer ccf) + private void AddSyncBlock(uint index, TargetPointer rcw, TargetPointer ccw, TargetPointer ccf, + TargetPointer taggedMemory = default) { Dictionary types = Types; MockMemorySpace.Builder builder = Builder; @@ -182,6 +186,7 @@ private void AddSyncBlock(uint index, TargetPointer rcw, TargetPointer ccw, Targ targetTestHelpers.WritePointer(interopInfoData.Slice(interopSyncBlockTypeInfo.Fields[nameof(Data.InteropSyncBlockInfo.RCW)].Offset), rcw); targetTestHelpers.WritePointer(interopInfoData.Slice(interopSyncBlockTypeInfo.Fields[nameof(Data.InteropSyncBlockInfo.CCW)].Offset), ccw); targetTestHelpers.WritePointer(interopInfoData.Slice(interopSyncBlockTypeInfo.Fields[nameof(Data.InteropSyncBlockInfo.CCF)].Offset), ccf); + targetTestHelpers.WritePointer(interopInfoData.Slice(interopSyncBlockTypeInfo.Fields[nameof(Data.InteropSyncBlockInfo.TaggedMemory)].Offset), taggedMemory); builder.AddHeapFragments([syncTableEntry, syncBlock]); } diff --git a/src/native/managed/cdac/tests/MockDescriptors/MockDescriptors.SyncBlock.cs b/src/native/managed/cdac/tests/MockDescriptors/MockDescriptors.SyncBlock.cs index 139df9cbe074ba..f63b6a44527754 100644 --- a/src/native/managed/cdac/tests/MockDescriptors/MockDescriptors.SyncBlock.cs +++ b/src/native/managed/cdac/tests/MockDescriptors/MockDescriptors.SyncBlock.cs @@ -74,7 +74,8 @@ public SyncBlock(MockMemorySpace.Builder builder, (ulong Start, ulong End) alloc /// When false, the InteropInfo pointer in the SyncBlock is left null. /// The address of the newly allocated SyncBlock. internal TargetPointer AddSyncBlockToCleanupList( - TargetPointer rcw, TargetPointer ccw, TargetPointer ccf, bool hasInteropInfo = true) + TargetPointer rcw, TargetPointer ccw, TargetPointer ccf, + bool hasInteropInfo = true, TargetPointer taggedMemory = default) { TargetTestHelpers helpers = Builder.TargetTestHelpers; Target.TypeInfo syncBlockTypeInfo = Types[DataType.SyncBlock]; @@ -95,6 +96,7 @@ internal TargetPointer AddSyncBlockToCleanupList( helpers.WritePointer(interopData.Slice(interopTypeInfo.Fields[nameof(Data.InteropSyncBlockInfo.RCW)].Offset), rcw); helpers.WritePointer(interopData.Slice(interopTypeInfo.Fields[nameof(Data.InteropSyncBlockInfo.CCW)].Offset), ccw); helpers.WritePointer(interopData.Slice(interopTypeInfo.Fields[nameof(Data.InteropSyncBlockInfo.CCF)].Offset), ccf); + helpers.WritePointer(interopData.Slice(interopTypeInfo.Fields[nameof(Data.InteropSyncBlockInfo.TaggedMemory)].Offset), taggedMemory); helpers.WritePointer(syncBlockData.Slice(syncBlockTypeInfo.Fields[nameof(Data.SyncBlock.InteropInfo)].Offset), interopAddr); } diff --git a/src/native/managed/cdac/tests/MockDescriptors/MockDescriptors.cs b/src/native/managed/cdac/tests/MockDescriptors/MockDescriptors.cs index 104a6e0c22bd59..161f80157e124a 100644 --- a/src/native/managed/cdac/tests/MockDescriptors/MockDescriptors.cs +++ b/src/native/managed/cdac/tests/MockDescriptors/MockDescriptors.cs @@ -137,6 +137,7 @@ internal record TypeFields new(nameof(Data.InteropSyncBlockInfo.RCW), DataType.pointer), new(nameof(Data.InteropSyncBlockInfo.CCW), DataType.pointer), new(nameof(Data.InteropSyncBlockInfo.CCF), DataType.pointer), + new(nameof(Data.InteropSyncBlockInfo.TaggedMemory), DataType.pointer), ] }; diff --git a/src/native/managed/cdac/tests/ObjectiveCMarshalTests.cs b/src/native/managed/cdac/tests/ObjectiveCMarshalTests.cs new file mode 100644 index 00000000000000..0a4771d143d61a --- /dev/null +++ b/src/native/managed/cdac/tests/ObjectiveCMarshalTests.cs @@ -0,0 +1,139 @@ +// 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 Microsoft.Diagnostics.DataContractReader.Contracts; +using Moq; +using Xunit; + +using MockObject = Microsoft.Diagnostics.DataContractReader.Tests.MockDescriptors.Object; + +namespace Microsoft.Diagnostics.DataContractReader.Tests; + +public class ObjectiveCMarshalTests +{ + private static void ObjectiveCMarshalContractHelper(MockTarget.Architecture arch, Action configure, Action testCase) + { + TargetTestHelpers targetTestHelpers = new(arch); + + MockMemorySpace.Builder builder = new(targetTestHelpers); + MockDescriptors.RuntimeTypeSystem rtsBuilder = new(builder); + MockObject objectBuilder = new(rtsBuilder); + + configure?.Invoke(objectBuilder); + + var target = new TestPlaceholderTarget(arch, builder.GetMemoryContext().ReadFromTarget, objectBuilder.Types, objectBuilder.Globals); + target.SetContracts(Mock.Of( + c => c.ObjectiveCMarshal == ((IContractFactory)new ObjectiveCMarshalFactory()).CreateContract(target, 1) + && c.SyncBlock == ((IContractFactory)new SyncBlockFactory()).CreateContract(target, 1))); + + testCase(target); + } + + [Theory] + [ClassData(typeof(MockTarget.StdArch))] + public void TryGetTaggedMemory_NoSyncBlockIndex_ReturnsFalse(MockTarget.Architecture arch) + { + TargetPointer testObjectAddress = default; + ObjectiveCMarshalContractHelper(arch, + (objectBuilder) => + { + uint objectHeaderSize = objectBuilder.Types[DataType.ObjectHeader].Size!.Value; + testObjectAddress = objectBuilder.AddObject(0, prefixSize: objectHeaderSize); + }, + (target) => + { + IObjectiveCMarshal contract = target.Contracts.ObjectiveCMarshal; + bool result = contract.TryGetTaggedMemory(testObjectAddress, out TargetNUInt size, out TargetPointer taggedMemory); + + Assert.False(result); + Assert.Equal(default, size); + Assert.Equal(TargetPointer.Null, taggedMemory); + }); + } + + [Theory] + [ClassData(typeof(MockTarget.StdArch))] + public void TryGetTaggedMemory_NullTaggedMemory_ReturnsFalse(MockTarget.Architecture arch) + { + TargetPointer testObjectAddress = default; + ObjectiveCMarshalContractHelper(arch, + (objectBuilder) => + { + testObjectAddress = objectBuilder.AddObjectWithSyncBlock( + 0, syncBlockIndex: 0, + rcw: TargetPointer.Null, ccw: TargetPointer.Null, ccf: TargetPointer.Null, + taggedMemory: TargetPointer.Null); + }, + (target) => + { + IObjectiveCMarshal contract = target.Contracts.ObjectiveCMarshal; + bool result = contract.TryGetTaggedMemory(testObjectAddress, out TargetNUInt size, out TargetPointer taggedMemory); + + Assert.False(result); + Assert.Equal(default, size); + Assert.Equal(TargetPointer.Null, taggedMemory); + }); + } + + [Theory] + [ClassData(typeof(MockTarget.StdArch))] + public void TryGetTaggedMemory_HasTaggedMemory_ReturnsTrueWithCorrectValues(MockTarget.Architecture arch) + { + TargetPointer testObjectAddress = default; + TargetPointer expectedTaggedMemory = new TargetPointer(0x5000); + ObjectiveCMarshalContractHelper(arch, + (objectBuilder) => + { + testObjectAddress = objectBuilder.AddObjectWithSyncBlock( + 0, syncBlockIndex: 0, + rcw: TargetPointer.Null, ccw: TargetPointer.Null, ccf: TargetPointer.Null, + taggedMemory: expectedTaggedMemory); + }, + (target) => + { + IObjectiveCMarshal contract = target.Contracts.ObjectiveCMarshal; + bool result = contract.TryGetTaggedMemory(testObjectAddress, out TargetNUInt size, out TargetPointer taggedMemory); + + Assert.True(result); + Assert.Equal(expectedTaggedMemory, taggedMemory); + Assert.Equal(2 * (ulong)target.PointerSize, size.Value); + }); + } + + [Theory] + [ClassData(typeof(MockTarget.StdArch))] + public void TryGetTaggedMemory_MultipleObjects_IndependentResults(MockTarget.Architecture arch) + { + TargetPointer objectWithTaggedMemory = default; + TargetPointer objectWithoutTaggedMemory = default; + TargetPointer expectedTaggedMemory = new TargetPointer(0x7000); + ObjectiveCMarshalContractHelper(arch, + (objectBuilder) => + { + uint syncBlockIndex = 0; + objectWithTaggedMemory = objectBuilder.AddObjectWithSyncBlock( + 0, syncBlockIndex: syncBlockIndex++, + rcw: TargetPointer.Null, ccw: TargetPointer.Null, ccf: TargetPointer.Null, + taggedMemory: expectedTaggedMemory); + objectWithoutTaggedMemory = objectBuilder.AddObjectWithSyncBlock( + 0, syncBlockIndex: syncBlockIndex++, + rcw: TargetPointer.Null, ccw: TargetPointer.Null, ccf: TargetPointer.Null, + taggedMemory: TargetPointer.Null); + }, + (target) => + { + IObjectiveCMarshal contract = target.Contracts.ObjectiveCMarshal; + + bool result1 = contract.TryGetTaggedMemory(objectWithTaggedMemory, out TargetNUInt size1, out TargetPointer tagged1); + Assert.True(result1); + Assert.Equal(expectedTaggedMemory, tagged1); + Assert.Equal(2 * (ulong)target.PointerSize, size1.Value); + + bool result2 = contract.TryGetTaggedMemory(objectWithoutTaggedMemory, out TargetNUInt size2, out TargetPointer tagged2); + Assert.False(result2); + Assert.Equal(default, size2); + Assert.Equal(TargetPointer.Null, tagged2); + }); + } +}