From bb7061832bc847b620bcebc2cb0ac470257843b2 Mon Sep 17 00:00:00 2001 From: Max Charlamb <44248479+max-charlamb@users.noreply.github.com> Date: Tue, 2 Sep 2025 14:34:44 -0400 Subject: [PATCH 1/7] general structure --- .../ContractRegistry.cs | 4 +++ .../Contracts/ISyncBlock.cs | 24 ++++++++++++++ .../Contracts/SyncBlockFactory.cs | 18 ++++++++++ .../Contracts/SyncBlock_1.cs | 19 +++++++++++ .../CachingContractRegistry.cs | 2 ++ .../Legacy/ISOSDacInterface.cs | 33 +++++++++++++++++-- .../Legacy/SOSDacImpl.cs | 4 +-- 7 files changed, 100 insertions(+), 4 deletions(-) create mode 100644 src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/ISyncBlock.cs create mode 100644 src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/SyncBlockFactory.cs create mode 100644 src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/SyncBlock_1.cs 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 4dd941006db544..99d57180063adb 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/ContractRegistry.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/ContractRegistry.cs @@ -74,4 +74,8 @@ public abstract class ContractRegistry /// Gets an instance of the GC contract for the target. /// public abstract IGC GC { get; } + /// + /// Gets an instance of the SyncBlock contract for the target. + /// + public abstract ISyncBlock SyncBlock { get; } } diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/ISyncBlock.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/ISyncBlock.cs new file mode 100644 index 00000000000000..9f8d75e770910f --- /dev/null +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/ISyncBlock.cs @@ -0,0 +1,24 @@ +// 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 struct SyncBlockData +{ + +} + +public interface ISyncBlock : IContract +{ + static string IContract.Name { get; } = nameof(SyncBlock); + + uint GetSyncBlockCount() => throw new NotImplementedException(); + SyncBlockData GetSyncBlockData(uint index) => throw new NotImplementedException(); +} + +public readonly struct SyncBlock : ISyncBlock +{ + // Everything throws NotImplementedException +} diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/SyncBlockFactory.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/SyncBlockFactory.cs new file mode 100644 index 00000000000000..84dbbdfdd33ebb --- /dev/null +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/SyncBlockFactory.cs @@ -0,0 +1,18 @@ +// 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 sealed class SyncBlockFactory : IContractFactory +{ + ISyncBlock IContractFactory.CreateContract(Target target, int version) + { + return version switch + { + 1 => new SyncBlock_1(target), + _ => default(SyncBlock), + }; + } +} diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/SyncBlock_1.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/SyncBlock_1.cs new file mode 100644 index 00000000000000..91df6c44ffeb1c --- /dev/null +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/SyncBlock_1.cs @@ -0,0 +1,19 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; + +namespace Microsoft.Diagnostics.DataContractReader.Contracts; + +internal readonly struct SyncBlock_1 : ISyncBlock +{ + private readonly Target _target; + + internal SyncBlock_1(Target target) + { + _target = target; + } + + public uint GetSyncBlockCount() => throw new NotImplementedException(); + public SyncBlockData GetSyncBlockData(uint index) => throw new NotImplementedException(); +} diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader/CachingContractRegistry.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader/CachingContractRegistry.cs index 715aa19b202b72..085a5c5d37b712 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader/CachingContractRegistry.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader/CachingContractRegistry.cs @@ -42,6 +42,7 @@ public CachingContractRegistry(Target target, TryGetContractVersionDelegate tryG [typeof(IRuntimeInfo)] = new RuntimeInfoFactory(), [typeof(IDebugInfo)] = new DebugInfoFactory(), [typeof(IGC)] = new GCFactory(), + [typeof(ISyncBlock)] = new SyncBlockFactory() }; configureFactories?.Invoke(_factories); } @@ -62,6 +63,7 @@ public CachingContractRegistry(Target target, TryGetContractVersionDelegate tryG public override IRuntimeInfo RuntimeInfo => GetContract(); public override IDebugInfo DebugInfo => GetContract(); public override IGC GC => GetContract(); + public override ISyncBlock SyncBlock => GetContract(); private TContract GetContract() where TContract : IContract { diff --git a/src/native/managed/cdac/mscordaccore_universal/Legacy/ISOSDacInterface.cs b/src/native/managed/cdac/mscordaccore_universal/Legacy/ISOSDacInterface.cs index eabf1d516e7565..45bf23de1f2967 100644 --- a/src/native/managed/cdac/mscordaccore_universal/Legacy/ISOSDacInterface.cs +++ b/src/native/managed/cdac/mscordaccore_universal/Legacy/ISOSDacInterface.cs @@ -286,6 +286,35 @@ internal struct DacpGcHeapData public uint g_max_generation; } +internal struct DacpSyncBlockData +{ + public ClrDataAddress Object; + public int bFree; // if set, no other fields are useful + + // fields below provide data from this, so it's just for display + public ClrDataAddress SyncBlockPointer; + public uint COMFlags; + public uint MonitorHeld; + public uint Recursion; + public ClrDataAddress HoldingThread; + public uint AdditionalThreadCount; + public ClrDataAddress appDomainPtr; + + // SyncBlockCount will always be filled in with the number of SyncBlocks. + // SyncBlocks may be requested from [1,SyncBlockCount] + public uint SyncBlockCount; +} + +internal struct DacpSyncBlockCleanupData +{ + public ClrDataAddress SyncBlockPointer; + + public ClrDataAddress nextSyncBlock; + public ClrDataAddress blockRCW; + public ClrDataAddress blockClassFactory; + public ClrDataAddress blockCCW; +} + [GeneratedComInterface] [Guid("436f00f2-b42a-4b9f-870c-e73db66ae930")] internal unsafe partial interface ISOSDacInterface @@ -441,9 +470,9 @@ internal unsafe partial interface ISOSDacInterface // SyncBlock [PreserveSig] - int GetSyncBlockData(uint number, /*struct DacpSyncBlockData */ void* data); + int GetSyncBlockData(uint number, DacpSyncBlockData* data); [PreserveSig] - int GetSyncBlockCleanupData(ClrDataAddress addr, /*struct DacpSyncBlockCleanupData */ void* data); + int GetSyncBlockCleanupData(ClrDataAddress addr, DacpSyncBlockCleanupData* data); // Handles [PreserveSig] diff --git a/src/native/managed/cdac/mscordaccore_universal/Legacy/SOSDacImpl.cs b/src/native/managed/cdac/mscordaccore_universal/Legacy/SOSDacImpl.cs index ea56a52440ee2e..fb6d2a4ab550c6 100644 --- a/src/native/managed/cdac/mscordaccore_universal/Legacy/SOSDacImpl.cs +++ b/src/native/managed/cdac/mscordaccore_universal/Legacy/SOSDacImpl.cs @@ -2200,9 +2200,9 @@ int ISOSDacInterface.GetStressLogAddress(ClrDataAddress* stressLog) return HResults.S_OK; } - int ISOSDacInterface.GetSyncBlockCleanupData(ClrDataAddress addr, void* data) + int ISOSDacInterface.GetSyncBlockCleanupData(ClrDataAddress addr, DacpSyncBlockCleanupData* data) => _legacyImpl is not null ? _legacyImpl.GetSyncBlockCleanupData(addr, data) : HResults.E_NOTIMPL; - int ISOSDacInterface.GetSyncBlockData(uint number, void* data) + int ISOSDacInterface.GetSyncBlockData(uint number, DacpSyncBlockData* data) => _legacyImpl is not null ? _legacyImpl.GetSyncBlockData(number, data) : HResults.E_NOTIMPL; int ISOSDacInterface.GetThreadAllocData(ClrDataAddress thread, void* data) => _legacyImpl is not null ? _legacyImpl.GetThreadAllocData(thread, data) : HResults.E_NOTIMPL; From 15e75dcacb2c16ad64d225cb7d642ea6ecb0d8eb Mon Sep 17 00:00:00 2001 From: Max Charlamb <44248479+max-charlamb@users.noreply.github.com> Date: Tue, 2 Sep 2025 19:09:33 -0400 Subject: [PATCH 2/7] wip --- .../vm/datadescriptor/datadescriptor.inc | 16 ++++ src/coreclr/vm/syncblk.h | 21 +++++ .../Contracts/ISyncBlock.cs | 12 ++- .../DataType.cs | 2 + .../Constants.cs | 1 + .../Contracts/Object_1.cs | 23 ++--- .../Contracts/SyncBlockFactory.cs | 5 +- .../Contracts/SyncBlock_1.cs | 86 ++++++++++++++++++- .../Data/AwareLock.cs | 23 +++++ .../Data/SyncBlock.cs | 10 ++- .../Data/SyncBlockCache.cs | 19 ++++ .../Data/SyncTableEntry.cs | 8 +- .../Legacy/ISOSDacInterface.cs | 11 ++- .../Legacy/SOSDacImpl.cs | 80 ++++++++++++++++- .../MockDescriptors/MockDescriptors.Object.cs | 1 + .../tests/MockDescriptors/MockDescriptors.cs | 13 +++ src/native/managed/cdac/tests/ObjectTests.cs | 3 +- 17 files changed, 303 insertions(+), 31 deletions(-) create mode 100644 src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/AwareLock.cs create mode 100644 src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/SyncBlockCache.cs diff --git a/src/coreclr/vm/datadescriptor/datadescriptor.inc b/src/coreclr/vm/datadescriptor/datadescriptor.inc index 672f1ba6dbc63d..f1ff939bf9dded 100644 --- a/src/coreclr/vm/datadescriptor/datadescriptor.inc +++ b/src/coreclr/vm/datadescriptor/datadescriptor.inc @@ -141,14 +141,29 @@ CDAC_TYPE_END(InteropSyncBlockInfo) CDAC_TYPE_BEGIN(SyncBlock) CDAC_TYPE_INDETERMINATE(SyncBlock) +CDAC_TYPE_FIELD(SyncBlock, /*AwareLock*/, Monitor, cdac_data::Monitor) CDAC_TYPE_FIELD(SyncBlock, /*pointer*/, InteropInfo, cdac_data::InteropInfo) +CDAC_TYPE_FIELD(SyncBlock, /*pointer*/, Link, cdac_data::Link) CDAC_TYPE_END(SyncBlock) CDAC_TYPE_BEGIN(SyncTableEntry) CDAC_TYPE_SIZE(sizeof(SyncTableEntry)) CDAC_TYPE_FIELD(SyncTableEntry, /*pointer*/, SyncBlock, offsetof(SyncTableEntry, m_SyncBlock)) +CDAC_TYPE_FIELD(SyncTableEntry, /*pointer*/, Object, offsetof(SyncTableEntry, m_Object)) CDAC_TYPE_END(SyncTableEntry) +CDAC_TYPE_BEGIN(SyncBlockCache) +CDAC_TYPE_INDETERMINATE(SyncBlockCache) +CDAC_TYPE_FIELD(SyncBlockCache, /*uint32*/, FreeSyncTableIndex, cdac_data::FreeSyncTableIndex) +CDAC_TYPE_END(SyncBlockCache) + +CDAC_TYPE_BEGIN(AwareLock) +CDAC_TYPE_INDETERMINATE(AwareLock) +CDAC_TYPE_FIELD(AwareLock, /*uint32*/, LockState, cdac_data::LockState) +CDAC_TYPE_FIELD(AwareLock, /*uint32*/, RecursionLevel, cdac_data::RecursionLevel) +CDAC_TYPE_FIELD(AwareLock, /*uint32*/, HoldingThreadId, cdac_data::HoldingThreadId) +CDAC_TYPE_END(AwareLock) + // Loader CDAC_TYPE_BEGIN(Module) @@ -981,6 +996,7 @@ CDAC_GLOBAL_POINTER(ObjectMethodTable, &::g_pObjectClass) CDAC_GLOBAL_POINTER(ObjectArrayMethodTable, &::g_pPredefinedArrayTypes[ELEMENT_TYPE_OBJECT]) CDAC_GLOBAL_POINTER(StringMethodTable, &::g_pStringClass) CDAC_GLOBAL_POINTER(SyncTableEntries, &::g_pSyncTable) +CDAC_GLOBAL_POINTER(SyncBlockCache, &SyncBlockCache::s_pSyncBlockCache) CDAC_GLOBAL_POINTER(MiniMetaDataBuffAddress, &::g_MiniMetaDataBuffAddress) CDAC_GLOBAL_POINTER(MiniMetaDataBuffMaxSize, &::g_MiniMetaDataBuffMaxSize) CDAC_GLOBAL_POINTER(DacNotificationFlags, &::g_dacNotificationFlags) diff --git a/src/coreclr/vm/syncblk.h b/src/coreclr/vm/syncblk.h index 0e24261dd5be7b..7570854610b00c 100644 --- a/src/coreclr/vm/syncblk.h +++ b/src/coreclr/vm/syncblk.h @@ -600,6 +600,16 @@ class AwareLock LIMITED_METHOD_CONTRACT; return (int)offsetof(AwareLock, m_HoldingOSThreadId); } + + friend struct ::cdac_data; +}; + +template<> +struct cdac_data +{ + static constexpr size_t LockState = offsetof(AwareLock, m_lockState); + static constexpr size_t RecursionLevel = offsetof(AwareLock, m_Recursion); + static constexpr size_t HoldingThreadId = offsetof(AwareLock, m_HoldingThreadId); }; class UMEntryThunkData; @@ -1132,7 +1142,10 @@ class SyncBlock template<> struct cdac_data { + static constexpr size_t Monitor = offsetof(SyncBlock, m_Monitor); static constexpr size_t InteropInfo = offsetof(SyncBlock, m_pInteropInfo); + static constexpr size_t Link = offsetof(SyncBlock, m_Link); + }; class SyncTableEntry @@ -1291,6 +1304,14 @@ class SyncBlockCache #ifdef VERIFY_HEAP void VerifySyncTableEntry(); #endif + + friend struct ::cdac_data; +}; + +template<> +struct cdac_data +{ + static constexpr size_t FreeSyncTableIndex = offsetof(SyncBlockCache, m_FreeSyncTableIndex); }; // See code:#SyncBlockOverView for more diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/ISyncBlock.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/ISyncBlock.cs index 9f8d75e770910f..8c2fbe9411740b 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/ISyncBlock.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/ISyncBlock.cs @@ -2,12 +2,18 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; +using System.Diagnostics.CodeAnalysis; namespace Microsoft.Diagnostics.DataContractReader.Contracts; -public struct SyncBlockData +public class SyncBlockData { - + public bool IsFree { get; init; } + public TargetPointer SyncBlock { get; init; } + public TargetPointer Object { get; init; } + public uint RecursionLevel { get; init; } + public uint HoldingThreadId { get; init; } + public uint MonitorHeldState { get; init; } } public interface ISyncBlock : IContract @@ -16,6 +22,8 @@ public interface ISyncBlock : IContract uint GetSyncBlockCount() => throw new NotImplementedException(); SyncBlockData GetSyncBlockData(uint index) => throw new NotImplementedException(); + bool TryGetSyncBlockData(uint index, [NotNullWhen(true)] out SyncBlockData? data) => throw new NotImplementedException(); + bool TryGetBuiltInComData(uint index, out TargetPointer rcw, out TargetPointer ccw) => throw new NotImplementedException(); } public readonly struct SyncBlock : ISyncBlock diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/DataType.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/DataType.cs index 834aad423c4169..0c7922c3bf1527 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/DataType.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/DataType.cs @@ -76,6 +76,8 @@ public enum DataType Array, SyncBlock, SyncTableEntry, + SyncBlockCache, + AwareLock, InteropSyncBlockInfo, InstantiatedMethodDesc, DynamicMethodDesc, diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Constants.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Constants.cs index 5d184703853b41..c46aa12d53e64d 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Constants.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Constants.cs @@ -48,6 +48,7 @@ public static class Globals public const string SyncBlockValueToObjectOffset = nameof(SyncBlockValueToObjectOffset); public const string SyncTableEntries = nameof(SyncTableEntries); + public const string SyncBlockCache = nameof(SyncBlockCache); public const string ArrayBoundsZero = nameof(ArrayBoundsZero); 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..09ce349eb96518 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 @@ -102,31 +102,26 @@ public bool GetBuiltInComData(TargetPointer address, out TargetPointer rcw, out rcw = TargetPointer.Null; ccw = TargetPointer.Null; - Data.SyncBlock? syncBlock = GetSyncBlock(address); - if (syncBlock == null) + if (!TryGetSyncBlockIndex(address, out uint index)) return false; - Data.InteropSyncBlockInfo? interopInfo = syncBlock.InteropInfo; - if (interopInfo == null) - return false; + ISyncBlock sync = _target.Contracts.SyncBlock; - rcw = interopInfo.RCW; - ccw = interopInfo.CCW; - return rcw != TargetPointer.Null || ccw != TargetPointer.Null; + return sync.TryGetBuiltInComData(index, out rcw, out ccw); } - private Data.SyncBlock? GetSyncBlock(TargetPointer address) + private bool TryGetSyncBlockIndex(TargetPointer address, out uint index) { + index = default; + uint syncBlockValue = _target.Read(address - _target.ReadGlobal(Constants.Globals.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 null; + return false; // Get the offset into the sync table entries - uint index = syncBlockValue & SyncBlockValue.SyncBlockIndexMask; - ulong offsetInSyncTableEntries = index * (ulong)_target.GetTypeInfo(DataType.SyncTableEntry).Size!; - Data.SyncTableEntry entry = _target.ProcessedData.GetOrAdd(_syncTableEntries + offsetInSyncTableEntries); - return entry.SyncBlock; + index = syncBlockValue & SyncBlockValue.SyncBlockIndexMask; + return true; } } diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/SyncBlockFactory.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/SyncBlockFactory.cs index 84dbbdfdd33ebb..5ef10fcc15a9b2 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/SyncBlockFactory.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/SyncBlockFactory.cs @@ -9,9 +9,12 @@ public sealed class SyncBlockFactory : IContractFactory { ISyncBlock IContractFactory.CreateContract(Target target, int version) { + TargetPointer syncTableEntries = target.ReadPointer( + target.ReadGlobalPointer(Constants.Globals.SyncTableEntries)); + return version switch { - 1 => new SyncBlock_1(target), + 1 => new SyncBlock_1(target, syncTableEntries), _ => default(SyncBlock), }; } diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/SyncBlock_1.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/SyncBlock_1.cs index 91df6c44ffeb1c..2d2440a2d78ca0 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/SyncBlock_1.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/SyncBlock_1.cs @@ -2,18 +2,98 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using Microsoft.Diagnostics.DataContractReader.Data; namespace Microsoft.Diagnostics.DataContractReader.Contracts; internal readonly struct SyncBlock_1 : ISyncBlock { + private const uint WAITER_COUNT_SHIFT = 0x6; + private const uint IS_LOCKED_MASK = 0x1; + private readonly Target _target; + private readonly TargetPointer _syncTableEntries; - internal SyncBlock_1(Target target) + internal SyncBlock_1(Target target, TargetPointer syncTableEntries) { _target = target; + _syncTableEntries = syncTableEntries; + } + + public uint GetSyncBlockCount() + { + TargetPointer syncBlockCacheAddr = _target.ReadPointer( + _target.ReadGlobalPointer(Constants.Globals.SyncBlockCache)); + SyncBlockCache syncBlockCache = _target.ProcessedData.GetOrAdd(syncBlockCacheAddr); + + // Return the count of sync blocks which have ever been used + return syncBlockCache.FreeSyncTableIndex; + } + + SyncBlockData ISyncBlock.GetSyncBlockData(uint index) + { + Data.SyncTableEntry entry = GetSyncTableEntry(index); + + if (IsSyncBlockFree(index)) + return new SyncBlockData { IsFree = true }; + + if (entry.SyncBlock != TargetPointer.Null) + { + Data.SyncBlock syncBlock = _target.ProcessedData.GetOrAdd(entry.SyncBlock); + return new SyncBlockData + { + IsFree = false, + Object = entry.Object, + SyncBlock = entry.SyncBlock, + RecursionLevel = syncBlock.Monitor.RecursionLevel, + HoldingThreadId = syncBlock.Monitor.HoldingThreadId, + MonitorHeldState = (syncBlock.Monitor.LockState & IS_LOCKED_MASK) | ((syncBlock.Monitor.LockState >> (int)WAITER_COUNT_SHIFT) << 1) + }; + } + + return new SyncBlockData + { + IsFree = false, + Object = entry.Object, + SyncBlock = entry.SyncBlock, + RecursionLevel = 0, + HoldingThreadId = 0, + MonitorHeldState = 0 + }; } - public uint GetSyncBlockCount() => throw new NotImplementedException(); - public SyncBlockData GetSyncBlockData(uint index) => throw new NotImplementedException(); + bool ISyncBlock.TryGetBuiltInComData(uint index, out TargetPointer rcw, out TargetPointer ccw) + { + rcw = TargetPointer.Null; + ccw = TargetPointer.Null; + + Data.SyncTableEntry entry = GetSyncTableEntry(index); + + if (entry.SyncBlock == TargetPointer.Null) + return false; + Data.SyncBlock syncBlock = _target.ProcessedData.GetOrAdd(entry.SyncBlock); + + if (syncBlock.InteropInfo == TargetPointer.Null) + return false; + Data.InteropSyncBlockInfo interopInfo = _target.ProcessedData.GetOrAdd(syncBlock.InteropInfo); + + rcw = interopInfo.RCW; + ccw = interopInfo.CCW; + return rcw != TargetPointer.Null || ccw != TargetPointer.Null; + } + + private bool IsSyncBlockFree(uint index) + { + Data.SyncTableEntry entry = GetSyncTableEntry(index); + // Lowest bit is set if this entry is free + return (entry.Object & 0x1) == 0x1; + } + + private Data.SyncTableEntry GetSyncTableEntry(uint index) + { + ulong offsetInSyncTableEntries = index * (ulong)_target.GetTypeInfo(DataType.SyncTableEntry).Size!; + return _target.ProcessedData.GetOrAdd(_syncTableEntries + offsetInSyncTableEntries); + } } diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/AwareLock.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/AwareLock.cs new file mode 100644 index 00000000000000..9844a495a8e93c --- /dev/null +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/AwareLock.cs @@ -0,0 +1,23 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.Diagnostics.DataContractReader.Data; + +internal sealed class AwareLock : IData +{ + static AwareLock IData.Create(Target target, TargetPointer address) + => new AwareLock(target, address); + + public AwareLock(Target target, TargetPointer address) + { + Target.TypeInfo type = target.GetTypeInfo(DataType.AwareLock); + + LockState = target.Read(address + (ulong)type.Fields[nameof(LockState)].Offset); + RecursionLevel = target.Read(address + (ulong)type.Fields[nameof(RecursionLevel)].Offset); + HoldingThreadId = target.Read(address + (ulong)type.Fields[nameof(HoldingThreadId)].Offset); + } + + public uint LockState { get; } + public uint RecursionLevel { get; } + public uint HoldingThreadId { 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 f045954fce0bf6..865631badc9480 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,10 +12,12 @@ public SyncBlock(Target target, TargetPointer address) { Target.TypeInfo type = target.GetTypeInfo(DataType.SyncBlock); - TargetPointer interopInfoPointer = target.ReadPointer(address + (ulong)type.Fields[nameof(InteropInfo)].Offset); - if (interopInfoPointer != TargetPointer.Null) - InteropInfo = target.ProcessedData.GetOrAdd(interopInfoPointer); + Monitor = target.ProcessedData.GetOrAdd(address + (ulong)type.Fields[nameof(Monitor)].Offset); + InteropInfo = target.ReadPointer(address + (ulong)type.Fields[nameof(InteropInfo)].Offset); + Link = target.ReadPointer(address + (ulong)type.Fields[nameof(Link)].Offset); } - public InteropSyncBlockInfo? InteropInfo { get; init; } + public AwareLock Monitor { get; } + public TargetPointer InteropInfo { get; } + public TargetPointer Link { get; } } diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/SyncBlockCache.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/SyncBlockCache.cs new file mode 100644 index 00000000000000..f57b7c6e32992a --- /dev/null +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/SyncBlockCache.cs @@ -0,0 +1,19 @@ +// 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; + +internal sealed class SyncBlockCache : IData +{ + static SyncBlockCache IData.Create(Target target, TargetPointer address) + => new SyncBlockCache(target, address); + + public SyncBlockCache(Target target, TargetPointer address) + { + Target.TypeInfo type = target.GetTypeInfo(DataType.SyncBlockCache); + + FreeSyncTableIndex = target.Read(address + (ulong)type.Fields[nameof(FreeSyncTableIndex)].Offset); + } + + public uint FreeSyncTableIndex { get; } +} diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/SyncTableEntry.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/SyncTableEntry.cs index b0c032b07379ce..a8008653904fb5 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/SyncTableEntry.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/SyncTableEntry.cs @@ -12,10 +12,10 @@ public SyncTableEntry(Target target, TargetPointer address) { Target.TypeInfo type = target.GetTypeInfo(DataType.SyncTableEntry); - TargetPointer syncBlockPointer = target.ReadPointer(address + (ulong)type.Fields[nameof(SyncBlock)].Offset); - if (syncBlockPointer != TargetPointer.Null) - SyncBlock = target.ProcessedData.GetOrAdd(syncBlockPointer); + SyncBlock = target.ReadPointer(address + (ulong)type.Fields[nameof(SyncBlock)].Offset); + Object = target.ReadPointer(address + (ulong)type.Fields[nameof(Object)].Offset); } - public SyncBlock? SyncBlock { get; init; } + public TargetPointer SyncBlock { get; } + public TargetPointer Object { get; } } diff --git a/src/native/managed/cdac/mscordaccore_universal/Legacy/ISOSDacInterface.cs b/src/native/managed/cdac/mscordaccore_universal/Legacy/ISOSDacInterface.cs index 45bf23de1f2967..46f89d925f3a05 100644 --- a/src/native/managed/cdac/mscordaccore_universal/Legacy/ISOSDacInterface.cs +++ b/src/native/managed/cdac/mscordaccore_universal/Legacy/ISOSDacInterface.cs @@ -288,12 +288,21 @@ internal struct DacpGcHeapData internal struct DacpSyncBlockData { + [Flags] + internal enum COMFlag : uint + { + None = 0x0, + CCW = 0x1, + RCW = 0x2, + CF = 0x4, + } + public ClrDataAddress Object; public int bFree; // if set, no other fields are useful // fields below provide data from this, so it's just for display public ClrDataAddress SyncBlockPointer; - public uint COMFlags; + public COMFlag COMFlags; public uint MonitorHeld; public uint Recursion; public ClrDataAddress HoldingThread; diff --git a/src/native/managed/cdac/mscordaccore_universal/Legacy/SOSDacImpl.cs b/src/native/managed/cdac/mscordaccore_universal/Legacy/SOSDacImpl.cs index fb6d2a4ab550c6..723264995e759a 100644 --- a/src/native/managed/cdac/mscordaccore_universal/Legacy/SOSDacImpl.cs +++ b/src/native/managed/cdac/mscordaccore_universal/Legacy/SOSDacImpl.cs @@ -2203,7 +2203,85 @@ int ISOSDacInterface.GetStressLogAddress(ClrDataAddress* stressLog) int ISOSDacInterface.GetSyncBlockCleanupData(ClrDataAddress addr, DacpSyncBlockCleanupData* data) => _legacyImpl is not null ? _legacyImpl.GetSyncBlockCleanupData(addr, data) : HResults.E_NOTIMPL; int ISOSDacInterface.GetSyncBlockData(uint number, DacpSyncBlockData* data) - => _legacyImpl is not null ? _legacyImpl.GetSyncBlockData(number, data) : HResults.E_NOTIMPL; + { + int hr = HResults.S_OK; + + try + { + ISyncBlock sync = _target.Contracts.SyncBlock; + + if (data == null) + throw new ArgumentException(); + + *data = default; + + uint syncBlockCount = sync.GetSyncBlockCount(); + + data->SyncBlockCount = syncBlockCount; + data->bFree = (int)Interop.BOOL.TRUE; + + if (syncBlockCount > 0 && number <= syncBlockCount) + { + SyncBlockData syncBlockData = sync.GetSyncBlockData(number); + if (!syncBlockData.IsFree) + { + data->bFree = (int)Interop.BOOL.FALSE; + data->Object = syncBlockData.Object.ToClrDataAddress(_target); + + data->SyncBlockPointer = syncBlockData.SyncBlock.ToClrDataAddress(_target); + + if (data->SyncBlockPointer != 0) + { + if (_target.ReadGlobal(Constants.Globals.FeatureCOMInterop) != 0) + { + sync.TryGetBuiltInComData(number, out TargetPointer rcw, out TargetPointer ccw); + if (rcw != TargetPointer.Null) + data->COMFlags |= DacpSyncBlockData.COMFlag.RCW; + if (ccw != TargetPointer.Null) + data->COMFlags |= DacpSyncBlockData.COMFlag.CCW; + // todo + } + + data->MonitorHeld = syncBlockData.MonitorHeldState; + data->Recursion = syncBlockData.RecursionLevel; + IThread thread = _target.Contracts.Thread; + data->HoldingThread = thread.IdToThread(syncBlockData.HoldingThreadId).ToClrDataAddress(_target); + data->appDomainPtr = _target.ReadPointer( + _target.ReadGlobalPointer(Constants.Globals.AppDomain)) + .ToClrDataAddress(_target); + } + } + } + } + catch (System.Exception ex) + { + hr = ex.HResult; + } + +#if DEBUG + if (_legacyImpl is not null) + { + DacpSyncBlockData dataLocal = default; + int hrLocal = _legacyImpl.GetSyncBlockData(number, &dataLocal); + Debug.Assert(hrLocal == hr, $"cDAC: {hr:x}, DAC: {hrLocal:x}"); + if (hr == HResults.S_OK) + { + Debug.Assert(data->Object == dataLocal.Object, $"cDAC: {data->Object:x}, DAC: {dataLocal.Object:x}"); + Debug.Assert(data->bFree == dataLocal.bFree, $"cDAC: {data->bFree}, DAC: {dataLocal.bFree}"); + Debug.Assert(data->SyncBlockPointer == dataLocal.SyncBlockPointer, $"cDAC: {data->SyncBlockPointer:x}, DAC: {dataLocal.SyncBlockPointer:x}"); + Debug.Assert(data->COMFlags == dataLocal.COMFlags, $"cDAC: {data->COMFlags}, DAC: {dataLocal.COMFlags}"); + Debug.Assert(data->MonitorHeld == dataLocal.MonitorHeld, $"cDAC: {data->MonitorHeld}, DAC: {dataLocal.MonitorHeld}"); + Debug.Assert(data->Recursion == dataLocal.Recursion, $"cDAC: {data->Recursion}, DAC: {dataLocal.Recursion}"); + Debug.Assert(data->HoldingThread == dataLocal.HoldingThread, $"cDAC: {data->HoldingThread:x}, DAC: {dataLocal.HoldingThread:x}"); + // Debug.Assert(data->AdditionalThreadCount == dataLocal.AdditionalThreadCount, $"cDAC: {data->AdditionalThreadCount}, DAC: {dataLocal.AdditionalThreadCount}"); + Debug.Assert(data->appDomainPtr == dataLocal.appDomainPtr, $"cDAC: {data->appDomainPtr:x}, DAC: {dataLocal.appDomainPtr:x}"); + Debug.Assert(data->SyncBlockCount == dataLocal.SyncBlockCount, $"cDAC: {data->SyncBlockCount}, DAC: {dataLocal.SyncBlockCount}"); + } + } +#endif + + return hr; + } int ISOSDacInterface.GetThreadAllocData(ClrDataAddress thread, void* data) => _legacyImpl is not null ? _legacyImpl.GetThreadAllocData(thread, data) : HResults.E_NOTIMPL; diff --git a/src/native/managed/cdac/tests/MockDescriptors/MockDescriptors.Object.cs b/src/native/managed/cdac/tests/MockDescriptors/MockDescriptors.Object.cs index 918726dfb50a5d..8ceaf943723035 100644 --- a/src/native/managed/cdac/tests/MockDescriptors/MockDescriptors.Object.cs +++ b/src/native/managed/cdac/tests/MockDescriptors/MockDescriptors.Object.cs @@ -76,6 +76,7 @@ public Object(RuntimeTypeSystem rtsBuilder, (ulong Start, ulong End) allocationR ArrayFields, SyncTableEntryFields, SyncBlockFields, + AwareLockFields, InteropSyncBlockFields, ]); Debug.Assert(types[DataType.Array].Size == Builder.TargetTestHelpers.ArrayBaseSize); diff --git a/src/native/managed/cdac/tests/MockDescriptors/MockDescriptors.cs b/src/native/managed/cdac/tests/MockDescriptors/MockDescriptors.cs index 73d301858691c3..46cf62bf67cd6c 100644 --- a/src/native/managed/cdac/tests/MockDescriptors/MockDescriptors.cs +++ b/src/native/managed/cdac/tests/MockDescriptors/MockDescriptors.cs @@ -107,6 +107,7 @@ internal record TypeFields Fields = [ new(nameof(Data.SyncTableEntry.SyncBlock), DataType.pointer), + new(nameof(Data.SyncTableEntry.Object), DataType.pointer) ] }; @@ -115,10 +116,22 @@ internal record TypeFields DataType = DataType.SyncBlock, Fields = [ + new(nameof(Data.SyncBlock.Monitor), DataType.uint32), new(nameof(Data.SyncBlock.InteropInfo), DataType.pointer), + new(nameof(Data.SyncBlock.Link), DataType.pointer), ] }; + private static readonly TypeFields AwareLockFields = new TypeFields() + { + DataType = DataType.AwareLock, + Fields = + [ + new(nameof(Data.AwareLock.LockState), DataType.uint32), + new(nameof(Data.AwareLock.RecursionLevel), DataType.uint32), + new(nameof(Data.AwareLock.HoldingThreadID), DataType.uint32), + ] + }; private static readonly TypeFields InteropSyncBlockFields = new TypeFields() { DataType = DataType.InteropSyncBlockInfo, diff --git a/src/native/managed/cdac/tests/ObjectTests.cs b/src/native/managed/cdac/tests/ObjectTests.cs index 7ccc5960a96507..e7ebec0847f724 100644 --- a/src/native/managed/cdac/tests/ObjectTests.cs +++ b/src/native/managed/cdac/tests/ObjectTests.cs @@ -25,7 +25,8 @@ private static void ObjectContractHelper(MockTarget.Architecture arch, Action( c => c.Object == ((IContractFactory)new ObjectFactory()).CreateContract(target, 1) - && c.RuntimeTypeSystem == ((IContractFactory)new RuntimeTypeSystemFactory()).CreateContract(target, 1))); + && c.RuntimeTypeSystem == ((IContractFactory)new RuntimeTypeSystemFactory()).CreateContract(target, 1) + && c.SyncBlock == ((IContractFactory)new SyncBlockFactory()).CreateContract(target, 1))); testCase(target); } From 092b889794719015d997dab2a5cdf3c1cf1624b4 Mon Sep 17 00:00:00 2001 From: Max Charlamb <44248479+max-charlamb@users.noreply.github.com> Date: Tue, 2 Sep 2025 21:18:34 -0400 Subject: [PATCH 3/7] fixes --- src/coreclr/vm/datadescriptor/contracts.jsonc | 3 ++- .../Contracts/SyncBlock_1.cs | 2 +- .../managed/cdac/tests/MockDescriptors/MockDescriptors.cs | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/coreclr/vm/datadescriptor/contracts.jsonc b/src/coreclr/vm/datadescriptor/contracts.jsonc index 63d61aeda40baf..4271804f7b47e5 100644 --- a/src/coreclr/vm/datadescriptor/contracts.jsonc +++ b/src/coreclr/vm/datadescriptor/contracts.jsonc @@ -24,5 +24,6 @@ "RuntimeTypeSystem": 1, "StackWalk": 1, "StressLog": 2, + "SyncBlock": 1, "Thread": 1 -} \ No newline at end of file +} diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/SyncBlock_1.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/SyncBlock_1.cs index 2d2440a2d78ca0..9d6dcd124cd023 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/SyncBlock_1.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/SyncBlock_1.cs @@ -29,7 +29,7 @@ public uint GetSyncBlockCount() SyncBlockCache syncBlockCache = _target.ProcessedData.GetOrAdd(syncBlockCacheAddr); // Return the count of sync blocks which have ever been used - return syncBlockCache.FreeSyncTableIndex; + return syncBlockCache.FreeSyncTableIndex - 1; } SyncBlockData ISyncBlock.GetSyncBlockData(uint index) diff --git a/src/native/managed/cdac/tests/MockDescriptors/MockDescriptors.cs b/src/native/managed/cdac/tests/MockDescriptors/MockDescriptors.cs index 46cf62bf67cd6c..e6ee019c6ea0dd 100644 --- a/src/native/managed/cdac/tests/MockDescriptors/MockDescriptors.cs +++ b/src/native/managed/cdac/tests/MockDescriptors/MockDescriptors.cs @@ -129,7 +129,7 @@ internal record TypeFields [ new(nameof(Data.AwareLock.LockState), DataType.uint32), new(nameof(Data.AwareLock.RecursionLevel), DataType.uint32), - new(nameof(Data.AwareLock.HoldingThreadID), DataType.uint32), + new(nameof(Data.AwareLock.HoldingThreadId), DataType.uint32), ] }; private static readonly TypeFields InteropSyncBlockFields = new TypeFields() From 9b3051b571df1e46f24053be17626ffd3a52ed9e Mon Sep 17 00:00:00 2001 From: Max Charlamb <44248479+max-charlamb@users.noreply.github.com> Date: Wed, 3 Sep 2025 10:18:46 -0400 Subject: [PATCH 4/7] finish implementation --- .../vm/datadescriptor/datadescriptor.inc | 5 +++ src/coreclr/vm/syncblk.h | 3 ++ .../Contracts/ISyncBlock.cs | 4 +-- .../Contracts/Object_1.cs | 2 +- .../Contracts/SyncBlock_1.cs | 33 ++++++++++++++++--- .../Data/InteropSyncBlockInfo.cs | 8 +++-- .../Legacy/SOSDacImpl.cs | 25 +++++++------- 7 files changed, 59 insertions(+), 21 deletions(-) diff --git a/src/coreclr/vm/datadescriptor/datadescriptor.inc b/src/coreclr/vm/datadescriptor/datadescriptor.inc index f1ff939bf9dded..450c82a58f8245 100644 --- a/src/coreclr/vm/datadescriptor/datadescriptor.inc +++ b/src/coreclr/vm/datadescriptor/datadescriptor.inc @@ -134,6 +134,9 @@ CDAC_TYPE_END(Array) CDAC_TYPE_BEGIN(InteropSyncBlockInfo) CDAC_TYPE_INDETERMINATE(InteropSyncBlockInfo) #ifdef FEATURE_COMINTEROP +#ifdef FEATURE_COMINTEROP_UNMANAGED_ACTIVATION +CDAC_TYPE_FIELD(InteropSyncBlockInfo, /*pointer*/, ClassFactory, cdac_data::ClassFactory) +#endif // FEATURE_COMINTEROP_UNMANAGED_ACTIVATION CDAC_TYPE_FIELD(InteropSyncBlockInfo, /*pointer*/, CCW, cdac_data::CCW) CDAC_TYPE_FIELD(InteropSyncBlockInfo, /*pointer*/, RCW, cdac_data::RCW) #endif // FEATURE_COMINTEROP @@ -164,6 +167,8 @@ CDAC_TYPE_FIELD(AwareLock, /*uint32*/, RecursionLevel, cdac_data::Rec CDAC_TYPE_FIELD(AwareLock, /*uint32*/, HoldingThreadId, cdac_data::HoldingThreadId) CDAC_TYPE_END(AwareLock) +CDAC_TYPE_BEGIN(SLink) + // Loader CDAC_TYPE_BEGIN(Module) diff --git a/src/coreclr/vm/syncblk.h b/src/coreclr/vm/syncblk.h index 7570854610b00c..04102d5dba7144 100644 --- a/src/coreclr/vm/syncblk.h +++ b/src/coreclr/vm/syncblk.h @@ -840,6 +840,9 @@ template<> struct cdac_data { #ifdef FEATURE_COMINTEROP +#ifdef FEATURE_COMINTEROP_UNMANAGED_ACTIVATION + static constexpr size_t ClassFactory = offsetof(InteropSyncBlockInfo, m_pCCF); +#endif // FEATURE_COMINTEROP_UNMANAGED_ACTIVATION static constexpr size_t CCW = offsetof(InteropSyncBlockInfo, m_pCCW); static constexpr size_t RCW = offsetof(InteropSyncBlockInfo, m_pRCW); #endif // FEATURE_COMINTEROP diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/ISyncBlock.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/ISyncBlock.cs index 8c2fbe9411740b..62cfaf8c5535dd 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/ISyncBlock.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/ISyncBlock.cs @@ -22,8 +22,8 @@ public interface ISyncBlock : IContract uint GetSyncBlockCount() => throw new NotImplementedException(); SyncBlockData GetSyncBlockData(uint index) => throw new NotImplementedException(); - bool TryGetSyncBlockData(uint index, [NotNullWhen(true)] out SyncBlockData? data) => throw new NotImplementedException(); - bool TryGetBuiltInComData(uint index, out TargetPointer rcw, out TargetPointer ccw) => throw new NotImplementedException(); + uint GetAdditionalThreadCount(uint index, uint maximumIterations = 1000) => throw new NotImplementedException(); + bool TryGetBuiltInComData(uint index, out TargetPointer rcw, out TargetPointer ccw, out TargetPointer cf) => throw new NotImplementedException(); } public readonly struct SyncBlock : ISyncBlock 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 09ce349eb96518..1ff830a0234e9a 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 @@ -107,7 +107,7 @@ public bool GetBuiltInComData(TargetPointer address, out TargetPointer rcw, out ISyncBlock sync = _target.Contracts.SyncBlock; - return sync.TryGetBuiltInComData(index, out rcw, out ccw); + return sync.TryGetBuiltInComData(index, out rcw, out ccw, out TargetPointer _); } private bool TryGetSyncBlockIndex(TargetPointer address, out uint index) diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/SyncBlock_1.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/SyncBlock_1.cs index 9d6dcd124cd023..3729976e09b5e4 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/SyncBlock_1.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/SyncBlock_1.cs @@ -36,7 +36,7 @@ SyncBlockData ISyncBlock.GetSyncBlockData(uint index) { Data.SyncTableEntry entry = GetSyncTableEntry(index); - if (IsSyncBlockFree(index)) + if (IsSyncBlockFree(entry)) return new SyncBlockData { IsFree = true }; if (entry.SyncBlock != TargetPointer.Null) @@ -64,10 +64,33 @@ SyncBlockData ISyncBlock.GetSyncBlockData(uint index) }; } - bool ISyncBlock.TryGetBuiltInComData(uint index, out TargetPointer rcw, out TargetPointer ccw) + uint ISyncBlock.GetAdditionalThreadCount(uint index, uint maximumIterations) + { + Data.SyncTableEntry entry = GetSyncTableEntry(index); + if (entry.SyncBlock == TargetPointer.Null) + return 0; + + uint additionalThreadCount = 0; + Data.SyncBlock syncBlock = _target.ProcessedData.GetOrAdd(entry.SyncBlock); + if (syncBlock.Link != TargetPointer.Null) + { + TargetPointer pLink = syncBlock.Link; + do + { + additionalThreadCount += 1; + pLink = _target.ReadPointer(pLink); + } + while (pLink != TargetPointer.Null && additionalThreadCount < maximumIterations); + } + + return additionalThreadCount; + } + + bool ISyncBlock.TryGetBuiltInComData(uint index, out TargetPointer rcw, out TargetPointer ccw, out TargetPointer classFactory) { rcw = TargetPointer.Null; ccw = TargetPointer.Null; + classFactory = TargetPointer.Null; Data.SyncTableEntry entry = GetSyncTableEntry(index); @@ -81,12 +104,12 @@ bool ISyncBlock.TryGetBuiltInComData(uint index, out TargetPointer rcw, out Targ rcw = interopInfo.RCW; ccw = interopInfo.CCW; - return rcw != TargetPointer.Null || ccw != TargetPointer.Null; + classFactory = interopInfo.ClassFactory; + return rcw != TargetPointer.Null || ccw != TargetPointer.Null || classFactory != TargetPointer.Null; } - private bool IsSyncBlockFree(uint index) + private static bool IsSyncBlockFree(Data.SyncTableEntry entry) { - Data.SyncTableEntry entry = GetSyncTableEntry(index); // Lowest bit is set if this entry is free return (entry.Object & 0x1) == 0x1; } 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..3ce447142ff75b 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 @@ -12,6 +12,9 @@ public InteropSyncBlockInfo(Target target, TargetPointer address) { Target.TypeInfo type = target.GetTypeInfo(DataType.InteropSyncBlockInfo); + ClassFactory = type.Fields.TryGetValue(nameof(ClassFactory), out Target.FieldInfo classFactoryField) + ? target.ReadPointer(address + (ulong)classFactoryField.Offset) + : TargetPointer.Null; RCW = type.Fields.TryGetValue(nameof(RCW), out Target.FieldInfo rcwField) ? target.ReadPointer(address + (ulong)rcwField.Offset) : TargetPointer.Null; @@ -20,6 +23,7 @@ public InteropSyncBlockInfo(Target target, TargetPointer address) : TargetPointer.Null; } - public TargetPointer RCW { get; init; } - public TargetPointer CCW { get; init; } + public TargetPointer ClassFactory { get; } + public TargetPointer RCW { get; } + public TargetPointer CCW { get; } } diff --git a/src/native/managed/cdac/mscordaccore_universal/Legacy/SOSDacImpl.cs b/src/native/managed/cdac/mscordaccore_universal/Legacy/SOSDacImpl.cs index 723264995e759a..4938ac74cfd1f8 100644 --- a/src/native/managed/cdac/mscordaccore_universal/Legacy/SOSDacImpl.cs +++ b/src/native/managed/cdac/mscordaccore_universal/Legacy/SOSDacImpl.cs @@ -2209,6 +2209,7 @@ int ISOSDacInterface.GetSyncBlockData(uint number, DacpSyncBlockData* data) try { ISyncBlock sync = _target.Contracts.SyncBlock; + IThread thread = _target.Contracts.Thread; if (data == null) throw new ArgumentException(); @@ -2232,23 +2233,25 @@ int ISOSDacInterface.GetSyncBlockData(uint number, DacpSyncBlockData* data) if (data->SyncBlockPointer != 0) { - if (_target.ReadGlobal(Constants.Globals.FeatureCOMInterop) != 0) - { - sync.TryGetBuiltInComData(number, out TargetPointer rcw, out TargetPointer ccw); - if (rcw != TargetPointer.Null) - data->COMFlags |= DacpSyncBlockData.COMFlag.RCW; - if (ccw != TargetPointer.Null) - data->COMFlags |= DacpSyncBlockData.COMFlag.CCW; - // todo - } + // In the runtime this block is under FEATURE_COMINTEROP / FEATURE_COMINTEROP_UNMANAGED_ACTIVATION + // feature flags. The checks are not needed here as the API will report 0 if no data is available. + sync.TryGetBuiltInComData(number, out TargetPointer rcw, out TargetPointer ccw, out TargetPointer cf); + if (rcw != TargetPointer.Null) + data->COMFlags |= DacpSyncBlockData.COMFlag.RCW; + if (ccw != TargetPointer.Null) + data->COMFlags |= DacpSyncBlockData.COMFlag.CCW; + if (cf != TargetPointer.Null) + data->COMFlags |= DacpSyncBlockData.COMFlag.CF; data->MonitorHeld = syncBlockData.MonitorHeldState; data->Recursion = syncBlockData.RecursionLevel; - IThread thread = _target.Contracts.Thread; data->HoldingThread = thread.IdToThread(syncBlockData.HoldingThreadId).ToClrDataAddress(_target); data->appDomainPtr = _target.ReadPointer( _target.ReadGlobalPointer(Constants.Globals.AppDomain)) .ToClrDataAddress(_target); + + // maximumIterations set to 1000 to match the DAC behavior + data->AdditionalThreadCount = sync.GetAdditionalThreadCount(number, maximumIterations: 1000); } } } @@ -2273,7 +2276,7 @@ int ISOSDacInterface.GetSyncBlockData(uint number, DacpSyncBlockData* data) Debug.Assert(data->MonitorHeld == dataLocal.MonitorHeld, $"cDAC: {data->MonitorHeld}, DAC: {dataLocal.MonitorHeld}"); Debug.Assert(data->Recursion == dataLocal.Recursion, $"cDAC: {data->Recursion}, DAC: {dataLocal.Recursion}"); Debug.Assert(data->HoldingThread == dataLocal.HoldingThread, $"cDAC: {data->HoldingThread:x}, DAC: {dataLocal.HoldingThread:x}"); - // Debug.Assert(data->AdditionalThreadCount == dataLocal.AdditionalThreadCount, $"cDAC: {data->AdditionalThreadCount}, DAC: {dataLocal.AdditionalThreadCount}"); + Debug.Assert(data->AdditionalThreadCount == dataLocal.AdditionalThreadCount, $"cDAC: {data->AdditionalThreadCount}, DAC: {dataLocal.AdditionalThreadCount}"); Debug.Assert(data->appDomainPtr == dataLocal.appDomainPtr, $"cDAC: {data->appDomainPtr:x}, DAC: {dataLocal.appDomainPtr:x}"); Debug.Assert(data->SyncBlockCount == dataLocal.SyncBlockCount, $"cDAC: {data->SyncBlockCount}, DAC: {dataLocal.SyncBlockCount}"); } From 15ad6cc55a8006200357a0ac220355b64bf79281 Mon Sep 17 00:00:00 2001 From: Max Charlamb <44248479+max-charlamb@users.noreply.github.com> Date: Wed, 3 Sep 2025 11:11:29 -0400 Subject: [PATCH 5/7] add syncblock docs --- docs/design/datacontracts/SyncBlock.md | 165 ++++++++++++++++++ src/coreclr/debug/daccess/request.cpp | 2 +- .../Contracts/ISyncBlock.cs | 2 +- .../Contracts/SyncBlockFactory.cs | 5 +- .../Contracts/SyncBlock_1.cs | 31 ++-- 5 files changed, 187 insertions(+), 18 deletions(-) create mode 100644 docs/design/datacontracts/SyncBlock.md diff --git a/docs/design/datacontracts/SyncBlock.md b/docs/design/datacontracts/SyncBlock.md new file mode 100644 index 00000000000000..e4fb023901c065 --- /dev/null +++ b/docs/design/datacontracts/SyncBlock.md @@ -0,0 +1,165 @@ +# Contract Object + +This contract is for getting information about SyncBlocks. + +## APIs of contract + +```csharp +public readonly struct SyncBlockData +{ + public bool IsFree { get; init; } + public TargetPointer SyncBlock { get; init; } + public TargetPointer Object { get; init; } + public uint RecursionLevel { get; init; } + public uint HoldingThreadId { get; init; } + public uint MonitorHeldState { get; init; } +} +``` + +``` csharp +// Get the number of syncblocks that have ever been used in the target runtime. +uint GetSyncBlockCount(); + +// Get information about a syncblock at a given index. +SyncBlockData GetSyncBlockData(uint index); + +// Get the number of threads waiting on the syncblock at a given index (up to the maximumIteration). +uint GetAdditionalThreadCount(uint index, uint maximumIterations = 1000); + +// Get built-in COM data for the syncblock if available. Returns false if the syncblock at the given index does not have COM data. +bool TryGetBuiltInComData(uint index, out TargetPointer rcw, out TargetPointer ccw, out TargetPointer cf); +``` + +## Version 1 + +Data descriptors used: +| Data Descriptor Name | Field | Meaning | +| --- | --- | --- | +| `InteropSyncBlockInfo` | `ClassFactory` | Pointer to the ClassFactory for the object (if it exists) | +| `InteropSyncBlockInfo` | `RCW` | Pointer to the RCW for the object (if it exists) | +| `InteropSyncBlockInfo` | `CCW` | Pointer to the CCW for the object (if it exists) | +| `SyncBlock` | `Monitor` | `AwareLock` storing the `SyncBlock`s locking state | +| `SyncBlock` | `InteropInfo` | Pointer to an optional `InteropSyncBlockInfo` for the sync block | +| `SyncBlock` | `Link` | `SLink` to list of threads waiting on the `SyncBlock` | +| `SyncTableEntry` | `SyncBlock` | `SyncBlock` corresponding to the entry | +| `SyncTableEntry` | `Object` | `Object` corresponding to the entry | +| `SyncBlockCache` | `FreeSyncTableIndex` | Pointer to the first syncblock index which has never been used in the target runtime | +| `AwareLock` | `RecursionLevel` | RecursionLevel of the `AwareLock` | +| `AwareLock` | `HoldingThreadId` | ThreadId currently holding the `AwareLock` | +| `AwareLock` | `LockState` | `uint` flag mask containing lock state information | + +Global variables used: +| Global Name | Type | Purpose | +| --- | --- | --- | +| `SyncTableEntries` | TargetPointer | The `SyncTableEntry` list | +| `SyncBlockCache` | TargetPointer | The global `SyncBlockCache` | + +Constants used: +| Name | Type | Purpose | Value | +| --- | --- | --- | --- | +| `WAITER_COUNT_SHIFT` | uint | AwareLock LockState shift to get waiter count | `0x6` | +| `IS_LOCKED_MASK` | uint | AwareLock LockState mask to determine if locked | `0x1` | + + +Contracts used: +| Contract Name | +| --- | +| _(none)_ | + +``` csharp +uint GetSyncBlockCount() +{ + TargetPointer syncBlockCacheAddr = target.ReadPointer(target.ReadGlobalPointer("SyncBlockCache")); + uint freeSyncTableIndex = target.Read(syncBlockCacheAddr + /* SyncBlockCache::FreeSyncTableIndex offset */); + // freeSyncTableIndex points to the first unused SyncBlock + return freeSyncTableIndex - 1; +} + +SyncBlockData GetSyncBlockData(uint index) +{ + TargetPointer syncTableEntry = target.ReadPointer(GetSyncTableEntryAddress(index)); + + TargetPointer pObject = target.ReadPointer(syncTableEntry + /* SyncTableEntry::Object offset */); + TargetPointer pSyncBlock = target.ReadPointer(syncTableEntry + /* SyncTableEntry::SyncBlock offset */); + + // SyncBlock is free if lowest bit of the Object field is set. + if ((pObject & 0x1) == 0x1) + return new SyncBlockData { IsFree = true }; + + if (pSyncBlock != TargetPointer.Null) + { + TargetPointer pMonitor = target.ReadPointer(pSyncBlock + /* SyncBlock::Monitor offset */); + uint lockState = target.Read(pMonitor + /* AwareLock::LockState offset */) + bool locked = (lockState & IS_LOCKED_MASK) == IS_LOCKED_MASK; + uint waiterCount = lockState >> WAITER_COUNT_SHIFT; + + // monitorHeldState lsb is 1 if locked, 0 if unlocked + // the higher bits contain the waiter count shifted up + uint monitorHeldState = (locked ? 0x1 : 0x0) | (waiterCount << 1); + return new SyncBlockData + { + IsFree = false, + Object = pObject, + SyncBlock = pSyncBlock, + RecursionLevel = target.Read(pMonitor + /* AwareLock::RecursionLevel offset */), + HoldingThreadId = target.Read(pMonitor + /* AwareLock::HoldingThreadId offset */), + MonitorHeldState = monitorHeldState, + }; + } + + return new SyncBlockData + { + IsFree = false, + Object = pObject, + SyncBlock = pSyncBlock, + RecursionLevel = 0, + HoldingThreadId = 0, + MonitorHeldState = 0 + }; +} + +uint GetAdditionalThreadCount(uint index, uint maximumIterations) +{ + TargetPointer syncTableEntry = target.ReadPointer(GetSyncTableEntryAddress(index)); + TargetPointer syncBlock = target.ReadPointer(syncTableEntry + /* SyncTableEntry::SyncBlock offset */); + if (syncBlock == TargetPointer.Null) + return 0; + + uint additionalThreadCount = 0; + TargetPointer pLink = target.ReadPointer(syncBlock + /* SyncBlock::Link offset */); + while (pLink != TargetPointer.Null && additionalThreadCount < maximumIterations) + { + additionalThreadCount += 1; + pLink = target.ReadPointer(pLink); + } + + return additionalThreadCount; +} + +bool GetBuiltInComData(uint index, out TargetPointer rcw, out TargetPointer ccw, out TargetPointer cf); +{ + TargetPointer syncTableEntry = target.ReadPointer(GetSyncTableEntryAddress(index)); + TargetPointer syncBlock = target.ReadPointer(syncTableEntry + /* SyncTableEntry::SyncBlock offset */); + if (syncBlock == TargetPointer.Null) + return false; + + TargetPointer interopInfo = target.ReadPointer(syncBlock + /* SyncBlock::InteropInfo offset */); + if (interopInfo == TargetPointer.Null) + return false; + + rcw = target.ReadPointer(interopInfo + /* InteropSyncBlockInfo::RCW offset */); + ccw = target.ReadPointer(interopInfo + /* InteropSyncBlockInfo::CCW offset */); + cf = target.ReadPointer(interopInfo + /* InteropSyncBlockInfo::ClassFactory offset */); + return rcw != TargetPointer.Null || ccw != TargetPointer.Null || cf != TargetPointer.Null; +} +``` + +Helpers: +```csharp +private TargetPointer GetSyncTableEntryAddress(uint index) +{ + TargetPointer syncTableEntries = target.ReadPointer(target.ReadGlobalPointer("SyncTableEntries")); + uint syncTableEntrySize = target.GetTypeInfo(DataType.SyncTableEntry).Size; + return syncTableEntries + (syncTableEntrySize * index) +} +``` \ No newline at end of file diff --git a/src/coreclr/debug/daccess/request.cpp b/src/coreclr/debug/daccess/request.cpp index c6900689384220..730bdcddaffb80 100644 --- a/src/coreclr/debug/daccess/request.cpp +++ b/src/coreclr/debug/daccess/request.cpp @@ -3815,7 +3815,7 @@ ClrDataAccess::GetSyncBlockData(unsigned int SBNumber, struct DacpSyncBlockData do { pSyncBlockData->AdditionalThreadCount++; - pLink = pBlock->m_Link.m_pNext; + pLink = pLink->m_pNext; } while ((pLink != NULL) && (pSyncBlockData->AdditionalThreadCount < 1000)); diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/ISyncBlock.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/ISyncBlock.cs index 62cfaf8c5535dd..30f8d1a1f9c29a 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/ISyncBlock.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/ISyncBlock.cs @@ -6,7 +6,7 @@ namespace Microsoft.Diagnostics.DataContractReader.Contracts; -public class SyncBlockData +public readonly struct SyncBlockData { public bool IsFree { get; init; } public TargetPointer SyncBlock { get; init; } diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/SyncBlockFactory.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/SyncBlockFactory.cs index 5ef10fcc15a9b2..ef0f3fc7bd7b30 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/SyncBlockFactory.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/SyncBlockFactory.cs @@ -12,9 +12,12 @@ ISyncBlock IContractFactory.CreateContract(Target target, int versio TargetPointer syncTableEntries = target.ReadPointer( target.ReadGlobalPointer(Constants.Globals.SyncTableEntries)); + TargetPointer syncBlockCacheAddr = target.ReadPointer( + target.ReadGlobalPointer(Constants.Globals.SyncBlockCache)); + return version switch { - 1 => new SyncBlock_1(target, syncTableEntries), + 1 => new SyncBlock_1(target, syncTableEntries, syncBlockCacheAddr), _ => default(SyncBlock), }; } diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/SyncBlock_1.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/SyncBlock_1.cs index 3729976e09b5e4..3d0bbc088551b4 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/SyncBlock_1.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/SyncBlock_1.cs @@ -2,8 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; -using System.Diagnostics; -using System.Diagnostics.CodeAnalysis; using Microsoft.Diagnostics.DataContractReader.Data; namespace Microsoft.Diagnostics.DataContractReader.Contracts; @@ -15,18 +13,18 @@ namespace Microsoft.Diagnostics.DataContractReader.Contracts; private readonly Target _target; private readonly TargetPointer _syncTableEntries; + private readonly TargetPointer _syncBlockCache; - internal SyncBlock_1(Target target, TargetPointer syncTableEntries) + internal SyncBlock_1(Target target, TargetPointer syncTableEntries, TargetPointer syncBlockCache) { _target = target; _syncTableEntries = syncTableEntries; + _syncBlockCache = syncBlockCache; } public uint GetSyncBlockCount() { - TargetPointer syncBlockCacheAddr = _target.ReadPointer( - _target.ReadGlobalPointer(Constants.Globals.SyncBlockCache)); - SyncBlockCache syncBlockCache = _target.ProcessedData.GetOrAdd(syncBlockCacheAddr); + SyncBlockCache syncBlockCache = _target.ProcessedData.GetOrAdd(_syncBlockCache); // Return the count of sync blocks which have ever been used return syncBlockCache.FreeSyncTableIndex - 1; @@ -42,6 +40,13 @@ SyncBlockData ISyncBlock.GetSyncBlockData(uint index) if (entry.SyncBlock != TargetPointer.Null) { Data.SyncBlock syncBlock = _target.ProcessedData.GetOrAdd(entry.SyncBlock); + + // monitorHeldState lsb is 1 if locked, 0 if unlocked + // the higher bits contain the waiter count shifted up + bool locked = (syncBlock.Monitor.LockState & IS_LOCKED_MASK) == IS_LOCKED_MASK; + uint waiterCount = syncBlock.Monitor.LockState >> (int)WAITER_COUNT_SHIFT; + uint monitorHeldState = (locked ? 0x1u : 0x0u) | (waiterCount << 1); + return new SyncBlockData { IsFree = false, @@ -49,7 +54,7 @@ SyncBlockData ISyncBlock.GetSyncBlockData(uint index) SyncBlock = entry.SyncBlock, RecursionLevel = syncBlock.Monitor.RecursionLevel, HoldingThreadId = syncBlock.Monitor.HoldingThreadId, - MonitorHeldState = (syncBlock.Monitor.LockState & IS_LOCKED_MASK) | ((syncBlock.Monitor.LockState >> (int)WAITER_COUNT_SHIFT) << 1) + MonitorHeldState = monitorHeldState, }; } @@ -72,15 +77,11 @@ uint ISyncBlock.GetAdditionalThreadCount(uint index, uint maximumIterations) uint additionalThreadCount = 0; Data.SyncBlock syncBlock = _target.ProcessedData.GetOrAdd(entry.SyncBlock); - if (syncBlock.Link != TargetPointer.Null) + TargetPointer pLink = syncBlock.Link; + while (pLink != TargetPointer.Null && additionalThreadCount < maximumIterations) { - TargetPointer pLink = syncBlock.Link; - do - { - additionalThreadCount += 1; - pLink = _target.ReadPointer(pLink); - } - while (pLink != TargetPointer.Null && additionalThreadCount < maximumIterations); + additionalThreadCount += 1; + pLink = _target.ReadPointer(pLink); } return additionalThreadCount; From 182c10730116c504032e546f478ab56f4c907cf7 Mon Sep 17 00:00:00 2001 From: Max Charlamb <44248479+max-charlamb@users.noreply.github.com> Date: Wed, 3 Sep 2025 11:14:58 -0400 Subject: [PATCH 6/7] update object contract --- docs/design/datacontracts/Object.md | 18 +++--------------- .../Contracts/ObjectFactory.cs | 4 +--- .../Contracts/Object_1.cs | 4 +--- 3 files changed, 5 insertions(+), 21 deletions(-) diff --git a/docs/design/datacontracts/Object.md b/docs/design/datacontracts/Object.md index 18747df027df3c..59f4c5261758d6 100644 --- a/docs/design/datacontracts/Object.md +++ b/docs/design/datacontracts/Object.md @@ -29,8 +29,6 @@ Data descriptors used: | `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) | -| `SyncBlock` | `InteropInfo` | Optional `InteropSyncBlockInfo` for the sync block | -| `SyncTableEntry` | `SyncBlock` | `SyncBlock` corresponding to the entry | Global variables used: | Global Name | Type | Purpose | @@ -39,13 +37,13 @@ Global variables used: | `ObjectHeaderSize` | uint32 | Size of the object header (sync block and alignment) | | `ObjectToMethodTableUnmask` | uint8 | Bits to clear for converting to a method table address | | `StringMethodTable` | TargetPointer | The method table for System.String | -| `SyncTableEntries` | TargetPointer | The `SyncTableEntry` list | | `SyncBlockValueToObjectOffset` | uint16 | Offset from the sync block value (in the object header) to the object itself | Contracts used: | Contract Name | | --- | | `RuntimeTypeSystem` | +| `SyncBlock` | ``` csharp TargetPointer GetMethodTableAddress(TargetPointer address) @@ -111,18 +109,8 @@ bool GetBuiltInComData(TargetPointer address, out TargetPointer rcw, out TargetP // 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 false; - - TargetPointer interopInfo = target.ReadPointer(syncBlock + /* SyncTableEntry::InteropInfo offset */); - if (interopInfo == TargetPointer.Null) - return false; - - rcw = target.ReadPointer(interopInfo + /* InteropSyncBlockInfo::RCW offset */); - ccw = target.ReadPointer(interopInfo + /* InteropSyncBlockInfo::CCW offset */); - return rcw != TargetPointer.Null && ccw != TargetPointer.Null; + Contracts.ISyncBlock sync = target.Contracts.SyncBlock; + return sync.TryGetBuiltInComData(index, out rcw, out ccw, out TargetPointer _); } ``` diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ObjectFactory.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ObjectFactory.cs index d659837c51251e..ae1a251700a157 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ObjectFactory.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ObjectFactory.cs @@ -13,11 +13,9 @@ IObject IContractFactory.CreateContract(Target target, int version) byte objectToMethodTableUnmask = target.ReadGlobal(Constants.Globals.ObjectToMethodTableUnmask); TargetPointer stringMethodTable = target.ReadPointer( target.ReadGlobalPointer(Constants.Globals.StringMethodTable)); - TargetPointer syncTableEntries = target.ReadPointer( - target.ReadGlobalPointer(Constants.Globals.SyncTableEntries)); return version switch { - 1 => new Object_1(target, methodTableOffset, objectToMethodTableUnmask, stringMethodTable, syncTableEntries), + 1 => new Object_1(target, methodTableOffset, objectToMethodTableUnmask, stringMethodTable), _ => default(Object), }; } 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 1ff830a0234e9a..0c52ea4024e360 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 @@ -13,7 +13,6 @@ namespace Microsoft.Diagnostics.DataContractReader.Contracts; private readonly ulong _methodTableOffset; private readonly byte _objectToMethodTableUnmask; private readonly TargetPointer _stringMethodTable; - private readonly TargetPointer _syncTableEntries; private static class SyncBlockValue { @@ -30,13 +29,12 @@ public enum Bits public const uint SyncBlockIndexMask = (1 << 26) - 1; } - internal Object_1(Target target, ulong methodTableOffset, byte objectToMethodTableUnmask, TargetPointer stringMethodTable, TargetPointer syncTableEntries) + internal Object_1(Target target, ulong methodTableOffset, byte objectToMethodTableUnmask, TargetPointer stringMethodTable) { _target = target; _methodTableOffset = methodTableOffset; _stringMethodTable = stringMethodTable; _objectToMethodTableUnmask = objectToMethodTableUnmask; - _syncTableEntries = syncTableEntries; } public TargetPointer GetMethodTableAddress(TargetPointer address) From 405f85d34c970a3ba296f754a6b9f280ad71010e Mon Sep 17 00:00:00 2001 From: Max Charlamb <44248479+max-charlamb@users.noreply.github.com> Date: Wed, 3 Sep 2025 11:41:03 -0400 Subject: [PATCH 7/7] fixes --- src/coreclr/debug/daccess/request.cpp | 2 +- src/coreclr/vm/datadescriptor/datadescriptor.inc | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/coreclr/debug/daccess/request.cpp b/src/coreclr/debug/daccess/request.cpp index 730bdcddaffb80..c6900689384220 100644 --- a/src/coreclr/debug/daccess/request.cpp +++ b/src/coreclr/debug/daccess/request.cpp @@ -3815,7 +3815,7 @@ ClrDataAccess::GetSyncBlockData(unsigned int SBNumber, struct DacpSyncBlockData do { pSyncBlockData->AdditionalThreadCount++; - pLink = pLink->m_pNext; + pLink = pBlock->m_Link.m_pNext; } while ((pLink != NULL) && (pSyncBlockData->AdditionalThreadCount < 1000)); diff --git a/src/coreclr/vm/datadescriptor/datadescriptor.inc b/src/coreclr/vm/datadescriptor/datadescriptor.inc index a8e719f7b70b5a..97e3aef20ec4c3 100644 --- a/src/coreclr/vm/datadescriptor/datadescriptor.inc +++ b/src/coreclr/vm/datadescriptor/datadescriptor.inc @@ -167,8 +167,6 @@ CDAC_TYPE_FIELD(AwareLock, /*uint32*/, RecursionLevel, cdac_data::Rec CDAC_TYPE_FIELD(AwareLock, /*uint32*/, HoldingThreadId, cdac_data::HoldingThreadId) CDAC_TYPE_END(AwareLock) -CDAC_TYPE_BEGIN(SLink) - // Loader CDAC_TYPE_BEGIN(Module)