diff --git a/docs/design/datacontracts/Object.md b/docs/design/datacontracts/Object.md index 18747df027df3c..87b8b96981e83a 100644 --- a/docs/design/datacontracts/Object.md +++ b/docs/design/datacontracts/Object.md @@ -16,6 +16,12 @@ TargetPointer GetArrayData(TargetPointer address, out uint count, out TargetPoin // Get built-in COM data for the object if available. Returns false, if address does not represent a COM object using built-in COM bool GetBuiltInComData(TargetPointer address, out TargetPointer rcw, out TargetPointer ccw); + +// Get the object's tagged memory (if it exists). +TargetPointer TaggedMemory(TargetPointer address); + +// Get the tagged memory size (if applicable). +nuint GetTaggedMemorySize(); ``` ## Version 1 @@ -26,6 +32,7 @@ Data descriptors used: | `Array` | `m_NumComponents` | Number of items in the array | | `InteropSyncBlockInfo` | `RCW` | Pointer to the RCW for the object (if it exists) | | `InteropSyncBlockInfo` | `CCW` | Pointer to the CCW for the object (if it exists) | +| `InteropSyncBlockInfo` | `TaggedMemory` | Pointer to the tagged memory for the object (if it exists) | | `Object` | `m_pMethTab` | Method table for the object | | `String` | `m_FirstChar` | First character of the string - `m_StringLength` can be used to read the full string (encoded in UTF-16) | | `String` | `m_StringLength` | Length of the string in characters (encoded in UTF-16) | @@ -93,17 +100,17 @@ TargetPointer GetArrayData(TargetPointer address, out uint count, out TargetPoin { // Single-dimensional, zero-based - doesn't have bounds boundsStart = address + /* Array::m_NumComponents offset */; - lowerBounds = _target.ReadGlobalPointer("ArrayBoundsZero"); + lowerBounds = target.ReadGlobalPointer("ArrayBoundsZero"); } // Sync block is before `this` pointer, so substract the object header size - ulong dataOffset = typeSystemContract.GetBaseSize(typeHandle) - _target.ReadGlobal("ObjectHeaderSize"); + ulong dataOffset = typeSystemContract.GetBaseSize(typeHandle) - target.ReadGlobal("ObjectHeaderSize"); return address + dataOffset; } bool GetBuiltInComData(TargetPointer address, out TargetPointer rcw, out TargetPointer ccw); { - uint syncBlockValue = target.Read(address - _target.ReadGlobal("SyncBlockValueToObjectOffset")); + uint syncBlockValue = target.Read(address - target.ReadGlobal("SyncBlockValueToObjectOffset")); // Check if the sync block value represents a sync block index if ((syncBlockValue & (uint)(SyncBlockValue.Bits.IsHashCodeOrSyncBlockIndex | SyncBlockValue.Bits.IsHashCode)) != (uint)SyncBlockValue.Bits.IsHashCodeOrSyncBlockIndex) @@ -125,4 +132,32 @@ bool GetBuiltInComData(TargetPointer address, out TargetPointer rcw, out TargetP ccw = target.ReadPointer(interopInfo + /* InteropSyncBlockInfo::CCW offset */); return rcw != TargetPointer.Null && ccw != TargetPointer.Null; } + +TargetPointer TaggedMemory(TargetPointer address) +{ + uint syncBlockValue = target.Read(address - target.ReadGlobal("SyncBlockValueToObjectOffset")); + + // Check if the sync block value represents a sync block index + if ((syncBlockValue & (uint)(SyncBlockValue.Bits.IsHashCodeOrSyncBlockIndex | SyncBlockValue.Bits.IsHashCode)) != (uint)SyncBlockValue.Bits.IsHashCodeOrSyncBlockIndex) + return TargetPointer.Null; + + // Get the offset into the sync table entries + uint index = syncBlockValue & SyncBlockValue.SyncBlockIndexMask; + ulong offsetInSyncTableEntries = index * /* SyncTableEntry size */; + + TargetPointer syncBlock = target.ReadPointer(_syncTableEntries + offsetInSyncTableEntries + /* SyncTableEntry::SyncBlock offset */); + if (syncBlock == TargetPointer.Null) + return TargetPointer.Null; + + TargetPointer interopInfo = target.ReadPointer(syncBlock + /* SyncTableEntry::InteropInfo offset */); + if (interopInfo == TargetPointer.Null) + return TargetPointer.Null; + + return target.ReadPointer(interopInfo + /* InteropSyncBlockInfo::TaggedMemory offset */); +} + +nuint GetTaggedMemorySize() +{ + return 2 * (nuint)target.PointerSize; +} ``` diff --git a/docs/design/datacontracts/RuntimeTypeSystem.md b/docs/design/datacontracts/RuntimeTypeSystem.md index 77980940d829ee..172fd5dc2375de 100644 --- a/docs/design/datacontracts/RuntimeTypeSystem.md +++ b/docs/design/datacontracts/RuntimeTypeSystem.md @@ -63,6 +63,7 @@ partial interface IRuntimeTypeSystem : IContract public ushort GetNumStaticFields(TypeHandle typeHandle); public ushort GetNumThreadStaticFields(TypeHandle typeHandle); public TargetPointer GetFieldDescList(TypeHandle typeHandle); + public bool IsTrackedReferenceWithFinalizer(TypeHandle typeHandle); public virtual ReadOnlySpan GetInstantiation(TypeHandle typeHandle); public virtual bool IsGenericTypeDefinition(TypeHandle typeHandle); @@ -459,6 +460,8 @@ The contract additionally depends on these data descriptors 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() ? false : _methodTables[typeHandle.Address].Flags.GetFlag(MethodTableFlags_1.WFLAGS_HIGH.IsTrackedReferenceWithFinalizer) != 0; + public ReadOnlySpan GetInstantiation(TypeHandle TypeHandle) { diff --git a/src/coreclr/debug/runtimeinfo/datadescriptor.inc b/src/coreclr/debug/runtimeinfo/datadescriptor.inc index 7a07b544704be0..4cebe5e1c1355d 100644 --- a/src/coreclr/debug/runtimeinfo/datadescriptor.inc +++ b/src/coreclr/debug/runtimeinfo/datadescriptor.inc @@ -201,6 +201,9 @@ CDAC_TYPE_INDETERMINATE(InteropSyncBlockInfo) CDAC_TYPE_FIELD(InteropSyncBlockInfo, /*pointer*/, CCW, cdac_data::CCW) CDAC_TYPE_FIELD(InteropSyncBlockInfo, /*pointer*/, RCW, cdac_data::RCW) #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) diff --git a/src/coreclr/vm/syncblk.h b/src/coreclr/vm/syncblk.h index efd0c7c5437e78..cd18826a50de3e 100644 --- a/src/coreclr/vm/syncblk.h +++ b/src/coreclr/vm/syncblk.h @@ -832,6 +832,9 @@ struct cdac_data #ifdef FEATURE_COMINTEROP static constexpr size_t CCW = offsetof(InteropSyncBlockInfo, m_pCCW); static constexpr size_t RCW = offsetof(InteropSyncBlockInfo, m_pRCW); +#endif +#ifdef FEATURE_OBJCMARSHAL + static constexpr size_t TaggedMemory = offsetof(InteropSyncBlockInfo, m_taggedMemory); #endif // FEATURE_COMINTEROP }; diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IObject.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IObject.cs index 7566ddacc1e488..64f31cdd906240 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IObject.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IObject.cs @@ -12,6 +12,8 @@ public interface IObject : IContract string GetStringValue(TargetPointer address) => throw new NotImplementedException(); TargetPointer GetArrayData(TargetPointer address, out uint count, out TargetPointer boundsStart, out TargetPointer lowerBounds) => throw new NotImplementedException(); bool GetBuiltInComData(TargetPointer address, out TargetPointer rcw, out TargetPointer ccw) => throw new NotImplementedException(); + TargetPointer TaggedMemory(TargetPointer address) => throw new NotImplementedException(); + nuint GetTaggedMemorySize() => throw new NotImplementedException(); } public readonly struct Object : IObject 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 0fb29e318c1d42..7ddf48d3c008dd 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 @@ -110,7 +110,7 @@ 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(); - + bool IsTrackedReferenceWithFinalizer(TypeHandle typeHandle) => throw new NotImplementedException(); ReadOnlySpan GetInstantiation(TypeHandle typeHandle) => throw new NotImplementedException(); bool IsGenericTypeDefinition(TypeHandle typeHandle) => throw new NotImplementedException(); diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Object_1.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Object_1.cs index d35b683f028fb1..4f87b0ac4801b2 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Object_1.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Object_1.cs @@ -14,6 +14,7 @@ namespace Microsoft.Diagnostics.DataContractReader.Contracts; private readonly byte _objectToMethodTableUnmask; private readonly TargetPointer _stringMethodTable; private readonly TargetPointer _syncTableEntries; + private readonly nuint _taggedMemorySize; private static class SyncBlockValue { @@ -37,6 +38,7 @@ internal Object_1(Target target, ulong methodTableOffset, byte objectToMethodTab _stringMethodTable = stringMethodTable; _objectToMethodTableUnmask = objectToMethodTableUnmask; _syncTableEntries = syncTableEntries; + _taggedMemorySize = 2 * (nuint)target.PointerSize; } public TargetPointer GetMethodTableAddress(TargetPointer address) @@ -115,6 +117,17 @@ public bool GetBuiltInComData(TargetPointer address, out TargetPointer rcw, out return rcw != TargetPointer.Null || ccw != TargetPointer.Null; } + public TargetPointer TaggedMemory(TargetPointer address) + { + Data.SyncBlock? syncBlock = GetSyncBlock(address); + return syncBlock?.InteropInfo?.TaggedMemory ?? TargetPointer.Null; + } + + public nuint GetTaggedMemorySize() + { + return _taggedMemorySize; + } + private Data.SyncBlock? GetSyncBlock(TargetPointer address) { uint syncBlockValue = _target.Read(address - _target.ReadGlobal(Constants.Globals.SyncBlockValueToObjectOffset)); 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 7386d59628fcbd..d5eff239504aec 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 @@ -393,6 +393,7 @@ public uint GetTypeDefToken(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() ? false : _methodTables[typeHandle.Address].Flags.GetFlag(MethodTableFlags_1.WFLAGS_HIGH.IsTrackedReferenceWithFinalizer) != 0; public ReadOnlySpan GetInstantiation(TypeHandle typeHandle) 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 cffa804296de30..0520095cd12351 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 @@ -18,8 +18,12 @@ public InteropSyncBlockInfo(Target target, TargetPointer address) CCW = type.Fields.TryGetValue(nameof(CCW), out Target.FieldInfo ccwField) ? target.ReadPointer(address + (ulong)ccwField.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 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 0998d5c22c5d7b..53e3da505819c4 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 @@ -45,8 +45,9 @@ internal enum WFLAGS_HIGH : uint Category_TruePrimitive = 0x00070000, Category_Interface = 0x000C0000, 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 + // otherwise the lower bits are used for WFLAGS_LOW } [Flags] diff --git a/src/native/managed/cdac/mscordaccore_universal/Legacy/SOSDacImpl.cs b/src/native/managed/cdac/mscordaccore_universal/Legacy/SOSDacImpl.cs index 39506ca13fd373..449f545e47a697 100644 --- a/src/native/managed/cdac/mscordaccore_universal/Legacy/SOSDacImpl.cs +++ b/src/native/managed/cdac/mscordaccore_universal/Legacy/SOSDacImpl.cs @@ -2350,9 +2350,99 @@ 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; + Contracts.IObject objectContract = _target.Contracts.Object; + Contracts.IRuntimeTypeSystem rtsContract = _target.Contracts.RuntimeTypeSystem; + 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 taggedMemory = objectContract.TaggedMemory(objPtr); + if (taggedMemory != 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 + { + Contracts.IObject objectContract = _target.Contracts.Object; + TargetPointer objPtr = objAddr.ToTargetPointer(_target); + TargetPointer taggedMemoryPtr = objectContract.TaggedMemory(objPtr); + if (taggedMemoryPtr != TargetPointer.Null) + { + *taggedMemory = taggedMemoryPtr.ToClrDataAddress(_target); + *taggedMemorySizeInBytes = objectContract.GetTaggedMemorySize(); + } + 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