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)