diff --git a/docs/design/datacontracts/GC.md b/docs/design/datacontracts/GC.md index 023cd2f4bbf825..b8d59f80475369 100644 --- a/docs/design/datacontracts/GC.md +++ b/docs/design/datacontracts/GC.md @@ -718,7 +718,7 @@ private HandleData CreateHandleData(TargetPointer handleAddress, byte uBlock, ui { IObject obj = target.Contracts.Object; TargetPointer handle = target.ReadPointer(handleAddress); - obj.GetBuiltInComData(handle, out _, out TargetPointer ccw); + obj.GetBuiltInComData(handle, out _, out TargetPointer ccw, out _); if (ccw != TargetPointer.Null) { IBuiltInCOM builtInCOM = target.Contracts.BuiltInCOM; diff --git a/docs/design/datacontracts/Object.md b/docs/design/datacontracts/Object.md index 5a23c3089c3781..dfc9c95fa1afc9 100644 --- a/docs/design/datacontracts/Object.md +++ b/docs/design/datacontracts/Object.md @@ -14,8 +14,8 @@ string GetStringValue(TargetPointer address); // Get the pointer to the data corresponding to a managed array object. Error if address does not represent a array. TargetPointer GetArrayData(TargetPointer address, out uint count, out TargetPointer boundsStart, out TargetPointer lowerBounds); -// Get built-in COM data for the object if available. Returns false, if address does not represent a COM object using built-in COM -bool GetBuiltInComData(TargetPointer address, out TargetPointer rcw, out TargetPointer ccw); +// Get built-in COM data for the object if available. Returns false if address does not represent a COM object using built-in COM. +bool GetBuiltInComData(TargetPointer address, out TargetPointer rcw, out TargetPointer ccw, out TargetPointer ccf); ``` ## Version 1 @@ -26,6 +26,7 @@ Data descriptors used: | `Array` | `m_NumComponents` | Number of items in the array | | `InteropSyncBlockInfo` | `RCW` | Pointer to the RCW for the object (if it exists) | | `InteropSyncBlockInfo` | `CCW` | Pointer to the CCW for the object (if it exists) | +| `InteropSyncBlockInfo` | `CCF` | Pointer to the COM class factory for the object (if it exists) | | `Object` | `m_pMethTab` | Method table for the object | | `String` | `m_FirstChar` | First character of the string - `m_StringLength` can be used to read the full string (encoded in UTF-16) | | `String` | `m_StringLength` | Length of the string in characters (encoded in UTF-16) | @@ -105,7 +106,7 @@ TargetPointer GetArrayData(TargetPointer address, out uint count, out TargetPoin return address + dataOffset; } -bool GetBuiltInComData(TargetPointer address, out TargetPointer rcw, out TargetPointer ccw); +bool GetBuiltInComData(TargetPointer address, out TargetPointer rcw, out TargetPointer ccw, out TargetPointer ccf) { uint syncBlockValue = target.Read(address - _target.ReadGlobal("SyncBlockValueToObjectOffset")); @@ -125,8 +126,12 @@ bool GetBuiltInComData(TargetPointer address, out TargetPointer rcw, out TargetP 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; + TargetPointer rcw1 = target.ReadPointer(interopInfo + /* InteropSyncBlockInfo::RCW offset */); + rcw = rcw1 & ~1ul; + TargetPointer ccw1 = target.ReadPointer(interopInfo + /* InteropSyncBlockInfo::CCW offset */); + ccw = (ccw1 == 1) ? TargetPointer.Null : ccw1; + TargetPointer ccf1 = target.ReadPointer(interopInfo + /* InteropSyncBlockInfo::CCF offset */); + ccf = (ccf1 == 1) ? TargetPointer.Null : ccf1; + return rcw != TargetPointer.Null || ccw != TargetPointer.Null || ccf != TargetPointer.Null; } ``` diff --git a/docs/design/datacontracts/SyncBlock.md b/docs/design/datacontracts/SyncBlock.md new file mode 100644 index 00000000000000..e0e5589dc26ceb --- /dev/null +++ b/docs/design/datacontracts/SyncBlock.md @@ -0,0 +1,144 @@ +# Contract SyncBlock + +This contract is for reading sync block table entries and lock state. + +## APIs of contract + +``` csharp +TargetPointer GetSyncBlock(uint index); +TargetPointer GetSyncBlockObject(uint index); +bool IsSyncBlockFree(uint index); +uint GetSyncBlockCount(); +bool TryGetLockInfo(TargetPointer syncBlock, out uint owningThreadId, out uint recursion); +uint GetAdditionalThreadCount(TargetPointer syncBlock); +``` + +## Version 1 + +Data descriptors used: +| Data Descriptor Name | Field | Meaning | +| --- | --- | --- | +| `SyncTableEntry` | `SyncBlock` | Pointer to the sync block for a sync table entry | +| `SyncTableEntry` | `Object` | Pointer to the object associated with a sync table entry | +| `SyncBlockCache` | `FreeSyncTableIndex` | One past the highest sync table entry index allocated | +| `SyncBlock` | `Lock` | Optional pointer to a `System.Threading.Lock` object payload | +| `SyncBlock` | `ThinLock` | Thin-lock state bits | +| `SyncBlock` | `LinkNext` | Head pointer for additional waiting threads list | +| `SLink` | `Next` | Next link for the additional waiting threads list | + +Global variables used: +| Global Name | Type | Purpose | +| --- | --- | --- | +| `SyncTableEntries` | TargetPointer | Pointer to the sync table entries array | +| `SyncBlockCache` | TargetPointer | Pointer to the runtime sync block cache | +| `SyncBlockMaskLockThreadId` | uint32 | Mask for extracting thread id from `SyncBlock.ThinLock` | +| `SyncBlockMaskLockRecursionLevel` | uint32 | Mask for extracting recursion level from `SyncBlock.ThinLock` | +| `SyncBlockRecursionLevelShift` | uint32 | Shift value for `SyncBlock.ThinLock` recursion level | + +### Contract Constants: +| Name | Type | Purpose | Value | +| --- | --- | --- | --- | +| `LockStateName` | string | Field name in `System.Threading.Lock` storing monitor-held state bits. | `_state` | +| `LockOwningThreadIdName` | string | Field name in `System.Threading.Lock` storing owning thread id. | `_owningThreadId` | +| `LockRecursionCountName` | string | Field name in `System.Threading.Lock` storing monitor recursion count. | `_recursionCount` | +| `LockName` | string | Type name used to resolve `System.Threading.Lock`. | `Lock` | +| `LockNamespace` | string | Namespace used to resolve `System.Threading.Lock`. | `System.Threading` | + +Contracts used: +| Contract Name | +| --- | +| `Loader` | +| `RuntimeTypeSystem` | +| `EcmaMetadata` | + +``` csharp +TargetPointer GetSyncBlock(uint index) +{ + TargetPointer syncTableEntries = target.ReadGlobalPointer("SyncTableEntries"); + ulong offsetInSyncTable = index * /* SyncTableEntry size */; + return target.ReadPointer(syncTableEntries + offsetInSyncTable + /* SyncTableEntry::SyncBlock offset */); +} + +TargetPointer GetSyncBlockObject(uint index) +{ + TargetPointer syncTableEntries = target.ReadGlobalPointer("SyncTableEntries"); + ulong offsetInSyncTable = index * /* SyncTableEntry size */; + return target.ReadPointer(syncTableEntries + offsetInSyncTable + /* SyncTableEntry::Object offset */); +} + +bool IsSyncBlockFree(uint index) +{ + TargetPointer syncTableEntries = target.ReadGlobalPointer("SyncTableEntries"); + ulong offsetInSyncTable = index * /* SyncTableEntry size */; + TargetPointer obj = target.ReadPointer(syncTableEntries + offsetInSyncTable + /* SyncTableEntry::Object offset */); + return (obj.Value & 1) != 0; +} + +uint GetSyncBlockCount() +{ + TargetPointer syncBlockCache = target.ReadPointer(target.ReadGlobalPointer("SyncBlockCache")); + uint freeSyncTableIndex = target.Read(syncBlockCache + /* SyncBlockCache::FreeSyncTableIndex offset */); + return freeSyncTableIndex - 1; +} + +bool TryGetLockInfo(TargetPointer syncBlock, out uint owningThreadId, out uint recursion) +{ + owningThreadId = 0; + recursion = 0; + + TargetPointer lockObject = target.ReadPointer(syncBlock + /* SyncBlock::Lock offset */); + + if (lockObject != TargetPointer.Null) + { + // Resolve System.Threading.Lock in System.Private.CoreLib by name using RuntimeTypeSystem contract, LockName and LockNamespace. + uint state = ReadUintField(/* Lock type */, "LockStateName", /* RuntimeTypeSystem contract */, /* MetadataReader for SPC */, lockObject); + bool monitorHeld = (state & 1) != 0; + if (monitorHeld) + { + owningThreadId = ReadUintField(/* Lock type */, "LockOwningThreadIdName", /* contracts */, lockObject); + recursion = ReadUintField(/* Lock type */, "LockRecursionCountName", /* contracts */, lockObject); + } + + return monitorHeld; + } + + uint thinLock = target.Read(syncBlock + /* SyncBlock::ThinLock offset */); + if (thinLock != 0) + { + owningThreadId = thinLock & target.ReadGlobal("SyncBlockMaskLockThreadId"); + bool monitorHeld = owningThreadId != 0; + if (monitorHeld) + { + recursion = (thinLock & target.ReadGlobal("SyncBlockMaskLockRecursionLevel")) + >> (int)target.ReadGlobal("SyncBlockRecursionLevelShift"); + } + + return monitorHeld; + } + + return false; +} + +private uint ReadUintField(TypeHandle enclosingType, string fieldName, IRuntimeTypeSystem rts, MetadataReader mdReader, TargetPointer dataAddr) +{ + TargetPointer field = rts.GetFieldDescByName(enclosingType, fieldName); + uint token = rts.GetFieldDescMemberDef(field); + FieldDefinitionHandle fieldHandle = (FieldDefinitionHandle)MetadataTokens.Handle((int)token); + FieldDefinition fieldDef = mdReader.GetFieldDefinition(fieldHandle); + uint offset = rts.GetFieldDescOffset(field, fieldDef); + return _target.Read(dataAddr + offset); +} + +uint GetAdditionalThreadCount(TargetPointer syncBlock) +{ + uint threadCount = 0; + TargetPointer next = target.ReadPointer(syncBlock + /* SyncBlock::LinkNext offset */); + while (next != TargetPointer.Null && threadCount < 1000) + { + threadCount++; + next = target.ReadPointer(next + /* SLink::Next offset */); + } + + return threadCount; +} +``` diff --git a/src/coreclr/vm/datadescriptor/datadescriptor.inc b/src/coreclr/vm/datadescriptor/datadescriptor.inc index 1f095fb8dee086..816351003e1d6c 100644 --- a/src/coreclr/vm/datadescriptor/datadescriptor.inc +++ b/src/coreclr/vm/datadescriptor/datadescriptor.inc @@ -133,7 +133,7 @@ CDAC_TYPE_END(GCHandle) // Object CDAC_TYPE_BEGIN(Object) -CDAC_TYPE_INDETERMINATE(Object) +CDAC_TYPE_SIZE(sizeof(Object)) CDAC_TYPE_FIELD(Object, /*pointer*/, m_pMethTab, cdac_data::m_pMethTab) CDAC_TYPE_END(Object) @@ -153,14 +153,23 @@ CDAC_TYPE_INDETERMINATE(InteropSyncBlockInfo) #ifdef FEATURE_COMINTEROP CDAC_TYPE_FIELD(InteropSyncBlockInfo, /*pointer*/, CCW, cdac_data::CCW) CDAC_TYPE_FIELD(InteropSyncBlockInfo, /*pointer*/, RCW, cdac_data::RCW) +CDAC_TYPE_FIELD(InteropSyncBlockInfo, /*pointer*/, CCF, cdac_data::CCF) #endif // FEATURE_COMINTEROP CDAC_TYPE_END(InteropSyncBlockInfo) CDAC_TYPE_BEGIN(SyncBlock) CDAC_TYPE_INDETERMINATE(SyncBlock) CDAC_TYPE_FIELD(SyncBlock, /*pointer*/, InteropInfo, cdac_data::InteropInfo) +CDAC_TYPE_FIELD(SyncBlock, /*pointer*/, Lock, cdac_data::Lock) +CDAC_TYPE_FIELD(SyncBlock, /*uint32*/, ThinLock, cdac_data::ThinLock) +CDAC_TYPE_FIELD(SyncBlock, /*pointer*/, LinkNext, cdac_data::LinkNext) CDAC_TYPE_END(SyncBlock) +CDAC_TYPE_BEGIN(SLink) +CDAC_TYPE_INDETERMINATE(SLink) +CDAC_TYPE_FIELD(SLink, /*pointer*/, Next, offsetof(SLink, m_pNext)) +CDAC_TYPE_END(SLink) + #ifdef FEATURE_COMWRAPPERS CDAC_TYPE_BEGIN(NativeObjectWrapperObject) CDAC_TYPE_INDETERMINATE(NativeObjectWrapperObject) @@ -181,6 +190,7 @@ CDAC_TYPE_END(ManagedObjectWrapperLayout) 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) // Loader @@ -1061,6 +1071,11 @@ CDAC_TYPE_SIZE(g_numKnownQueryInterfaceImplementations * sizeof(PCODE)) CDAC_TYPE_END(ComWrappersVtablePtrs) #endif +CDAC_TYPE_BEGIN(SyncBlockCache) +CDAC_TYPE_INDETERMINATE(SyncBlockCache) +CDAC_TYPE_FIELD(SyncBlockCache, /*uint32*/, FreeSyncTableIndex, cdac_data::FreeSyncTableIndex) +CDAC_TYPE_END(SyncBlockCache) + CDAC_TYPES_END() CDAC_GLOBALS_BEGIN() @@ -1210,6 +1225,10 @@ CDAC_GLOBAL(StressLogEnabled, uint8, 0) CDAC_GLOBAL_POINTER(ExecutionManagerCodeRangeMapAddress, cdac_data::CodeRangeMapAddress) CDAC_GLOBAL_POINTER(PlatformMetadata, &::g_cdacPlatformMetadata) CDAC_GLOBAL_POINTER(ProfilerControlBlock, &::g_profControlBlock) +CDAC_GLOBAL_POINTER(SyncBlockCache, &SyncBlockCache::s_pSyncBlockCache) +CDAC_GLOBAL(SyncBlockMaskLockThreadId, uint32, SBLK_MASK_LOCK_THREADID) +CDAC_GLOBAL(SyncBlockMaskLockRecursionLevel, uint32, SBLK_MASK_LOCK_RECLEVEL) +CDAC_GLOBAL(SyncBlockRecursionLevelShift, uint32, SBLK_RECLEVEL_SHIFT) CDAC_GLOBAL_POINTER(GCLowestAddress, &g_lowest_address) CDAC_GLOBAL_POINTER(GCHighestAddress, &g_highest_address) @@ -1247,6 +1266,7 @@ CDAC_GLOBAL_CONTRACT(RuntimeInfo, 1) CDAC_GLOBAL_CONTRACT(RuntimeTypeSystem, 1) CDAC_GLOBAL_CONTRACT(SHash, 1) CDAC_GLOBAL_CONTRACT(SignatureDecoder, 1) +CDAC_GLOBAL_CONTRACT(SyncBlock, 1) CDAC_GLOBAL_CONTRACT(StackWalk, 1) CDAC_GLOBAL_CONTRACT(StressLog, 2) CDAC_GLOBAL_CONTRACT(Thread, 1) diff --git a/src/coreclr/vm/syncblk.h b/src/coreclr/vm/syncblk.h index 19cf57f7b6f9cd..9ecb37be8d5d73 100644 --- a/src/coreclr/vm/syncblk.h +++ b/src/coreclr/vm/syncblk.h @@ -381,6 +381,7 @@ struct cdac_data #ifdef FEATURE_COMINTEROP static constexpr size_t CCW = offsetof(InteropSyncBlockInfo, m_pCCW); static constexpr size_t RCW = offsetof(InteropSyncBlockInfo, m_pRCW); + static constexpr size_t CCF = offsetof(InteropSyncBlockInfo, m_pCCF); #endif // FEATURE_COMINTEROP }; @@ -601,6 +602,10 @@ template<> struct cdac_data { static constexpr size_t InteropInfo = offsetof(SyncBlock, m_pInteropInfo); + static constexpr size_t Lock = offsetof(SyncBlock, m_Lock); + static constexpr size_t ThinLock = offsetof(SyncBlock, m_thinLock); + static constexpr size_t LinkNext = offsetof(SyncBlock, m_Link) + offsetof(SLink, m_pNext); + }; class SyncTableEntry @@ -759,6 +764,13 @@ 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/libraries/System.Private.CoreLib/src/System/Threading/Lock.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/Lock.cs index 543b18c5048316..b27d337e23fabb 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/Lock.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/Lock.cs @@ -37,10 +37,10 @@ public sealed class Lock private static long s_contentionCount; - private int _owningThreadId; + private int _owningThreadId; // cDAC depends on exact name of this field - private uint _state; // see State for layout - private uint _recursionCount; + private uint _state; // see State for layout. cDAC depends on exact name of this field + private uint _recursionCount; // cDAC depends on exact name of this field // This field serves a few purposes currently: // - When positive, it indicates the number of spin-wait iterations that most threads would do upon contention 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 23050e3262147d..a4e33db17c7fcf 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/ContractRegistry.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/ContractRegistry.cs @@ -95,6 +95,10 @@ public abstract class ContractRegistry /// public virtual ISignatureDecoder SignatureDecoder => GetContract(); /// + /// Gets an instance of the SyncBlock contract for the target. + /// + public virtual ISyncBlock SyncBlock => GetContract(); + /// /// Gets an instance of the BuiltInCOM contract for the target. /// public virtual IBuiltInCOM BuiltInCOM => GetContract(); diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IObject.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IObject.cs index 7566ddacc1e488..fa2c1f34fdc973 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IObject.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IObject.cs @@ -11,7 +11,7 @@ public interface IObject : IContract TargetPointer GetMethodTableAddress(TargetPointer address) => throw new NotImplementedException(); string GetStringValue(TargetPointer address) => throw new NotImplementedException(); TargetPointer GetArrayData(TargetPointer address, out uint count, out TargetPointer boundsStart, out TargetPointer lowerBounds) => throw new NotImplementedException(); - bool GetBuiltInComData(TargetPointer address, out TargetPointer rcw, out TargetPointer ccw) => throw new NotImplementedException(); + bool GetBuiltInComData(TargetPointer address, out TargetPointer rcw, out TargetPointer ccw, out TargetPointer ccf) => throw new NotImplementedException(); } public readonly struct Object : IObject diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IRuntimeTypeSystem.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IRuntimeTypeSystem.cs index b750abb30a753c..f318e70e528f04 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IRuntimeTypeSystem.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IRuntimeTypeSystem.cs @@ -210,6 +210,7 @@ public interface IRuntimeTypeSystem : IContract bool IsFieldDescStatic(TargetPointer fieldDescPointer) => throw new NotImplementedException(); CorElementType GetFieldDescType(TargetPointer fieldDescPointer) => throw new NotImplementedException(); uint GetFieldDescOffset(TargetPointer fieldDescPointer, FieldDefinition fieldDef) => throw new NotImplementedException(); + TargetPointer GetFieldDescByName(TypeHandle typeHandle, string fieldName) => throw new NotImplementedException(); #endregion FieldDesc inspection APIs } 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..58beda06b846af --- /dev/null +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/ISyncBlock.cs @@ -0,0 +1,21 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; + +namespace Microsoft.Diagnostics.DataContractReader.Contracts; +public interface ISyncBlock : IContract +{ + static string IContract.Name { get; } = nameof(SyncBlock); + TargetPointer GetSyncBlock(uint index) => throw new NotImplementedException(); + TargetPointer GetSyncBlockObject(uint index) => throw new NotImplementedException(); + bool IsSyncBlockFree(uint index) => throw new NotImplementedException(); + uint GetSyncBlockCount() => throw new NotImplementedException(); + bool TryGetLockInfo(TargetPointer syncBlock, out uint owningThreadId, out uint recursion) => throw new NotImplementedException(); + uint GetAdditionalThreadCount(TargetPointer syncBlock) => throw new NotImplementedException(); +} + +public readonly struct SyncBlock : ISyncBlock +{ + // Everything throws NotImplementedException +} 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 2e9663aedf96f7..7f51c992347079 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/DataType.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/DataType.cs @@ -83,8 +83,10 @@ public enum DataType ThisPtrRetBufPrecodeData, Array, SyncBlock, + SLink, SyncTableEntry, InteropSyncBlockInfo, + SyncBlockCache, InstantiatedMethodDesc, DynamicMethodDesc, StoredSigMethodDesc, 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 d8d6a5bbf8c886..eeb68cfd6e37ea 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Constants.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Constants.cs @@ -56,6 +56,10 @@ 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 SyncBlockMaskLockThreadId = nameof(SyncBlockMaskLockThreadId); + public const string SyncBlockMaskLockRecursionLevel = nameof(SyncBlockMaskLockRecursionLevel); + public const string SyncBlockRecursionLevelShift = nameof(SyncBlockRecursionLevelShift); public const string ArrayBoundsZero = nameof(ArrayBoundsZero); public const string CoreLib = nameof(CoreLib); diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/BuiltInCOM.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/BuiltInCOM_1.cs similarity index 100% rename from src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/BuiltInCOM.cs rename to src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/BuiltInCOM_1.cs diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GC/GC_1.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GC/GC_1.cs index df3afa820a350c..935a224cebf7ce 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GC/GC_1.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GC/GC_1.cs @@ -483,7 +483,7 @@ private HandleData CreateHandleData(TargetPointer handleAddress, byte uBlock, ui { IObject obj = _target.Contracts.Object; TargetPointer handle = _target.ReadPointer(handleAddress); - obj.GetBuiltInComData(handle, out _, out TargetPointer ccw); + obj.GetBuiltInComData(handle, out _, out TargetPointer ccw, out _); if (ccw != TargetPointer.Null) { IBuiltInCOM builtInCOM = _target.Contracts.BuiltInCOM; 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 64da922ef49425..1fd8ceae2fcb66 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 @@ -101,10 +101,11 @@ public TargetPointer GetArrayData(TargetPointer address, out uint count, out Tar return address + dataOffset; } - public bool GetBuiltInComData(TargetPointer address, out TargetPointer rcw, out TargetPointer ccw) + public bool GetBuiltInComData(TargetPointer address, out TargetPointer rcw, out TargetPointer ccw, out TargetPointer ccf) { rcw = TargetPointer.Null; ccw = TargetPointer.Null; + ccf = TargetPointer.Null; Data.SyncBlock? syncBlock = GetSyncBlock(address); if (syncBlock == null) @@ -114,9 +115,10 @@ public bool GetBuiltInComData(TargetPointer address, out TargetPointer rcw, out if (interopInfo == null) return false; - rcw = interopInfo.RCW; - ccw = interopInfo.CCW; - return rcw != TargetPointer.Null || ccw != TargetPointer.Null; + rcw = interopInfo.RCW & ~1ul; + ccw = interopInfo.CCW == 1 ? TargetPointer.Null : interopInfo.CCW; + ccf = interopInfo.CCF == 1 ? TargetPointer.Null : interopInfo.CCF; + return rcw != TargetPointer.Null || ccw != TargetPointer.Null || ccf != TargetPointer.Null; } private Data.SyncBlock? GetSyncBlock(TargetPointer address) diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeTypeSystem_1.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeTypeSystem_1.cs index da10f7f535cac0..fa7df4c3374384 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeTypeSystem_1.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeTypeSystem_1.cs @@ -1685,4 +1685,44 @@ uint IRuntimeTypeSystem.GetFieldDescOffset(TargetPointer fieldDescPointer, Field } return fieldDesc.DWord2 & (uint)FieldDescFlags2.OffsetMask; } + + TargetPointer IRuntimeTypeSystem.GetFieldDescByName(TypeHandle typeHandle, string fieldName) + { + if (!typeHandle.IsMethodTable()) + return TargetPointer.Null; + + TargetPointer modulePtr = GetModule(typeHandle); + if (modulePtr == TargetPointer.Null) + return TargetPointer.Null; + + uint typeDefToken = GetTypeDefToken(typeHandle); + if (typeDefToken == 0) + return TargetPointer.Null; + + EntityHandle entityHandle = MetadataTokens.EntityHandle((int)typeDefToken); + if (entityHandle.Kind != HandleKind.TypeDefinition) + return TargetPointer.Null; + + TypeDefinitionHandle typeDefHandle = (TypeDefinitionHandle)entityHandle; + + ILoader loader = _target.Contracts.Loader; + ModuleHandle moduleHandle = loader.GetModuleHandleFromModulePtr(modulePtr); + MetadataReader? md = _target.Contracts.EcmaMetadata.GetMetadata(moduleHandle); + if (md is null) + return TargetPointer.Null; + + TargetPointer fieldDefToDescMap = loader.GetLookupTables(moduleHandle).FieldDefToDesc; + foreach (FieldDefinitionHandle fieldDefHandle in md.GetTypeDefinition(typeDefHandle).GetFields()) + { + FieldDefinition fieldDef = md.GetFieldDefinition(fieldDefHandle); + if (md.GetString(fieldDef.Name) == fieldName) + { + uint fieldDefToken = (uint)MetadataTokens.GetToken(fieldDefHandle); + TargetPointer fieldDescPtr = loader.GetModuleLookupMapElement(fieldDefToDescMap, fieldDefToken, out _); + return fieldDescPtr; + } + } + + return TargetPointer.Null; + } } 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..4249c1b8c79c7a --- /dev/null +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/SyncBlockFactory.cs @@ -0,0 +1,21 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; + +namespace Microsoft.Diagnostics.DataContractReader.Contracts; + +public 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, 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 new file mode 100644 index 00000000000000..d4ff505e1b7726 --- /dev/null +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/SyncBlock_1.cs @@ -0,0 +1,118 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Reflection.Metadata; +using System.Reflection.Metadata.Ecma335; + +namespace Microsoft.Diagnostics.DataContractReader.Contracts; + +internal readonly struct SyncBlock_1 : ISyncBlock +{ + private const string LockStateName = "_state"; + private const string LockOwningThreadIdName = "_owningThreadId"; + private const string LockRecursionCountName = "_recursionCount"; + private const string LockName = "Lock"; + private const string LockNamespace = "System.Threading"; + private readonly Target _target; + private readonly TargetPointer _syncTableEntries; + + internal SyncBlock_1(Target target, TargetPointer syncTableEntries) + { + _target = target; + _syncTableEntries = syncTableEntries; + } + + public TargetPointer GetSyncBlock(uint index) + { + Data.SyncTableEntry ste = _target.ProcessedData.GetOrAdd(_syncTableEntries + index * _target.GetTypeInfo(DataType.SyncTableEntry).Size!.Value); + return ste.SyncBlock?.Address ?? TargetPointer.Null; + } + + public TargetPointer GetSyncBlockObject(uint index) + { + Data.SyncTableEntry ste = _target.ProcessedData.GetOrAdd(_syncTableEntries + index * _target.GetTypeInfo(DataType.SyncTableEntry).Size!.Value); + return ste.Object?.Address ?? TargetPointer.Null; + } + + public bool IsSyncBlockFree(uint index) + { + Data.SyncTableEntry ste = _target.ProcessedData.GetOrAdd(_syncTableEntries + index * _target.GetTypeInfo(DataType.SyncTableEntry).Size!.Value); + return (ste.Object?.Address & 1) != 0; + } + + public uint GetSyncBlockCount() + { + TargetPointer syncBlockCache = _target.ReadPointer(_target.ReadGlobalPointer(Constants.Globals.SyncBlockCache)); + Data.SyncBlockCache cache = _target.ProcessedData.GetOrAdd(syncBlockCache); + return cache.FreeSyncTableIndex - 1; + } + + public bool TryGetLockInfo(TargetPointer syncBlock, out uint owningThreadId, out uint recursion) + { + owningThreadId = 0; + recursion = 0; + Data.SyncBlock sb = _target.ProcessedData.GetOrAdd(syncBlock); + + if (sb.Lock != null) + { + ILoader loader = _target.Contracts.Loader; + TargetPointer systemAssembly = loader.GetSystemAssembly(); + ModuleHandle moduleHandle = loader.GetModuleHandleFromAssemblyPtr(systemAssembly); + + IRuntimeTypeSystem rts = _target.Contracts.RuntimeTypeSystem; + IEcmaMetadata ecmaMetadataContract = _target.Contracts.EcmaMetadata; + TypeHandle lockType = rts.GetTypeByNameAndModule(LockName, LockNamespace, moduleHandle); + MetadataReader mdReader = ecmaMetadataContract.GetMetadata(moduleHandle)!; + TargetPointer lockObjPtr = sb.Lock.Object; + Data.Object lockObj = _target.ProcessedData.GetOrAdd(lockObjPtr); + TargetPointer dataAddr = lockObj.Data; + uint state = ReadUintField(lockType, LockStateName, rts, mdReader, dataAddr); + bool monitorHeld = (state & 1) != 0; + if (monitorHeld) + { + owningThreadId = ReadUintField(lockType, LockOwningThreadIdName, rts, mdReader, dataAddr); + recursion = ReadUintField(lockType, LockRecursionCountName, rts, mdReader, dataAddr); + } + return monitorHeld; + } + + else if (sb.ThinLock != 0) + { + owningThreadId = sb.ThinLock & _target.ReadGlobal(Constants.Globals.SyncBlockMaskLockThreadId); + bool monitorHeld = owningThreadId != 0; + if (monitorHeld) + { + recursion = (sb.ThinLock & _target.ReadGlobal(Constants.Globals.SyncBlockMaskLockRecursionLevel)) >> (int)_target.ReadGlobal(Constants.Globals.SyncBlockRecursionLevelShift); + } + return monitorHeld; + } + + else + { + return false; + } + } + + public uint GetAdditionalThreadCount(TargetPointer syncBlock) + { + Data.SyncBlock sb = _target.ProcessedData.GetOrAdd(syncBlock); + uint threadCount = 0; + TargetPointer next = sb.LinkNext; + while (next != TargetPointer.Null && threadCount < 1000) + { + threadCount++; + next = _target.ProcessedData.GetOrAdd(next).Next; + } + return threadCount; + } + + private uint ReadUintField(TypeHandle enclosingType, string fieldName, IRuntimeTypeSystem rts, MetadataReader mdReader, TargetPointer dataAddr) + { + TargetPointer field = rts.GetFieldDescByName(enclosingType, fieldName); + uint token = rts.GetFieldDescMemberDef(field); + FieldDefinitionHandle fieldHandle = (FieldDefinitionHandle)MetadataTokens.Handle((int)token); + FieldDefinition fieldDef = mdReader.GetFieldDefinition(fieldHandle); + uint offset = rts.GetFieldDescOffset(field, fieldDef); + return _target.Read(dataAddr + offset); + } +} 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..a38a57f1688603 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/InteropSyncBlockInfo.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/InteropSyncBlockInfo.cs @@ -18,8 +18,12 @@ public InteropSyncBlockInfo(Target target, TargetPointer address) CCW = type.Fields.TryGetValue(nameof(CCW), out Target.FieldInfo ccwField) ? target.ReadPointer(address + (ulong)ccwField.Offset) : TargetPointer.Null; + CCF = type.Fields.TryGetValue(nameof(CCF), out Target.FieldInfo ccfField) + ? target.ReadPointer(address + (ulong)ccfField.Offset) + : TargetPointer.Null; } public TargetPointer RCW { get; init; } public TargetPointer CCW { get; init; } + public TargetPointer CCF { get; init; } } diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Object.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Object.cs index 0fdd0647cbcf93..6b0d32238963c7 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Object.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Object.cs @@ -1,6 +1,8 @@ // 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.Data; internal sealed class Object : IData @@ -10,8 +12,12 @@ public Object(Target target, TargetPointer address) { Target.TypeInfo type = target.GetTypeInfo(DataType.Object); + Address = address; MethodTable = target.ProcessedData.GetOrAdd(target.ReadPointer(address + (ulong)type.Fields["m_pMethTab"].Offset)); + Data = address + (type.Size ?? throw new InvalidOperationException("Object size must be known")); } + public TargetPointer Address { get; init; } public MethodTable MethodTable { get; init; } + public TargetPointer Data { get; init; } } diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/SLink.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/SLink.cs new file mode 100644 index 00000000000000..fb1aa3c787ef12 --- /dev/null +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/SLink.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 SLink : IData +{ + static SLink IData.Create(Target target, TargetPointer address) + => new SLink(target, address); + + public SLink(Target target, TargetPointer address) + { + Target.TypeInfo type = target.GetTypeInfo(DataType.SLink); + + Next = target.ReadPointer(address + (ulong)type.Fields[nameof(Next)].Offset); + } + + public TargetPointer Next { get; init; } +} 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..64795ffa3d7f04 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,21 @@ public SyncBlock(Target target, TargetPointer address) { Target.TypeInfo type = target.GetTypeInfo(DataType.SyncBlock); + Address = address; TargetPointer interopInfoPointer = target.ReadPointer(address + (ulong)type.Fields[nameof(InteropInfo)].Offset); if (interopInfoPointer != TargetPointer.Null) InteropInfo = target.ProcessedData.GetOrAdd(interopInfoPointer); + TargetPointer lockPointer = target.ReadPointer(address + (ulong)type.Fields[nameof(Lock)].Offset); + if (lockPointer != TargetPointer.Null) + Lock = target.ProcessedData.GetOrAdd(lockPointer); + + ThinLock = target.Read(address + (ulong)type.Fields[nameof(ThinLock)].Offset); + LinkNext = target.ReadPointer(address + (ulong)type.Fields[nameof(LinkNext)].Offset); } + public TargetPointer Address { get; init; } public InteropSyncBlockInfo? InteropInfo { get; init; } + public ObjectHandle? Lock { get; init; } + public uint ThinLock { get; init; } + public TargetPointer LinkNext { get; init; } } 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..81ce5ee675dd9e --- /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; init; } +} 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..415d0ae6b4497b 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 @@ -15,7 +15,12 @@ public SyncTableEntry(Target target, TargetPointer address) TargetPointer syncBlockPointer = target.ReadPointer(address + (ulong)type.Fields[nameof(SyncBlock)].Offset); if (syncBlockPointer != TargetPointer.Null) SyncBlock = target.ProcessedData.GetOrAdd(syncBlockPointer); + + TargetPointer objectPointer = target.ReadPointer(address + (ulong)type.Fields[nameof(Object)].Offset); + if (objectPointer != TargetPointer.Null && (objectPointer & 1) == 0) // Defensive check: if the lowest bit is set, this is a free sync block entry and the pointer is not valid. + Object = target.ProcessedData.GetOrAdd(objectPointer); } public SyncBlock? SyncBlock { get; init; } + public Object? Object { get; init; } } diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/ISOSDacInterface.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/ISOSDacInterface.cs index 0a34475562859b..c775b62dddd4af 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/ISOSDacInterface.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/ISOSDacInterface.cs @@ -437,6 +437,26 @@ public struct DacpFieldDescData public ClrDataAddress NextField; }; +public struct DacpSyncBlockData +{ + public enum COMFlagsEnum : uint + { + HasCCW = 0x1, + HasRCW = 0x2, + HasCCF = 0x4 + } + public ClrDataAddress Object; + public Interop.BOOL bFree; + public ClrDataAddress SyncBlockPointer; + public uint COMFlags; + public uint MonitorHeld; + public uint Recursion; + public ClrDataAddress HoldingThread; + public uint AdditionalThreadCount; + public ClrDataAddress appDomainPtr; + public uint SyncBlockCount; +}; + public struct SOSHandleData { public ClrDataAddress AppDomain; @@ -612,7 +632,7 @@ public 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); diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/SOSDacImpl.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/SOSDacImpl.cs index 2205da9963965c..89309e706de509 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/SOSDacImpl.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/SOSDacImpl.cs @@ -2950,9 +2950,9 @@ int ISOSDacInterface.GetObjectData(ClrDataAddress objAddr, DacpObjectData* data) // Populate COM data if this is a COM object if (_target.ReadGlobal(Constants.Globals.FeatureCOMInterop) != 0 - && objectContract.GetBuiltInComData(objPtr, out TargetPointer rcw, out TargetPointer ccw)) + && objectContract.GetBuiltInComData(objPtr, out TargetPointer rcw, out TargetPointer ccw, out _)) { - data->RCW = rcw; + data->RCW = rcw & ~(_rcwMask); data->CCW = ccw; } @@ -3413,8 +3413,86 @@ int ISOSDacInterface.GetStressLogAddress(ClrDataAddress* stressLog) int ISOSDacInterface.GetSyncBlockCleanupData(ClrDataAddress addr, void* data) => _legacyImpl is not null ? _legacyImpl.GetSyncBlockCleanupData(addr, data) : HResults.E_NOTIMPL; - int ISOSDacInterface.GetSyncBlockData(uint number, void* data) - => _legacyImpl is not null ? _legacyImpl.GetSyncBlockData(number, data) : HResults.E_NOTIMPL; + int ISOSDacInterface.GetSyncBlockData(uint number, DacpSyncBlockData* data) + { + int hr = HResults.S_OK; + try + { + if (data == null) + throw new ArgumentException(); + *data = default; + data->bFree = Interop.BOOL.TRUE; + + ISyncBlock syncBlock = _target.Contracts.SyncBlock; + uint syncBlockCount = syncBlock.GetSyncBlockCount(); + data->SyncBlockCount = syncBlockCount; + if (syncBlockCount > 0 && number <= syncBlockCount) + { + data->bFree = syncBlock.IsSyncBlockFree(number) ? Interop.BOOL.TRUE : Interop.BOOL.FALSE; + if (data->bFree == Interop.BOOL.FALSE) + { + TargetPointer obj = syncBlock.GetSyncBlockObject(number); + data->Object = obj.ToClrDataAddress(_target); + if (syncBlock.GetSyncBlock(number) is TargetPointer syncBlockAddr && syncBlockAddr != TargetPointer.Null) + { + data->SyncBlockPointer = syncBlockAddr.ToClrDataAddress(_target); + IObject objContract = _target.Contracts.Object; + if (objContract.GetBuiltInComData(obj, out TargetPointer rcw, out TargetPointer ccw, out TargetPointer ccf)) + { + data->COMFlags = (rcw & ~(_rcwMask)) != TargetPointer.Null ? (uint)DacpSyncBlockData.COMFlagsEnum.HasRCW : 0; + data->COMFlags |= ccw != TargetPointer.Null ? (uint)DacpSyncBlockData.COMFlagsEnum.HasCCW : 0; + data->COMFlags |= ccf != TargetPointer.Null ? (uint)DacpSyncBlockData.COMFlagsEnum.HasCCF : 0; + } + bool monitorHeld = syncBlock.TryGetLockInfo(syncBlockAddr, out uint owningThreadId, out uint recursion); + data->MonitorHeld = monitorHeld ? (uint)1 : 0; + if (monitorHeld) + { + data->Recursion = recursion + 1; + IThread thread = _target.Contracts.Thread; + TargetPointer threadPtr = thread.IdToThread(owningThreadId); + data->HoldingThread = threadPtr.ToClrDataAddress(_target); + } + + TargetPointer appDomainPointer = _target.ReadGlobalPointer(Constants.Globals.AppDomain); + TargetPointer appDomain = _target.ReadPointer(appDomainPointer); + data->appDomainPtr = appDomain.ToClrDataAddress(_target); + + data->AdditionalThreadCount = syncBlock.GetAdditionalThreadCount(syncBlockAddr); + + } + } + } + } + catch (System.Exception ex) + { + hr = ex.HResult; + } +#if DEBUG + if (_legacyImpl is not null) + { + DacpSyncBlockData dataLocal; + 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}"); + if (data->MonitorHeld != 0) + { + 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, DacpAllocData* data) { diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader/CachingContractRegistry.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader/CachingContractRegistry.cs index 46468a7af170c7..1a29993494081f 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader/CachingContractRegistry.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader/CachingContractRegistry.cs @@ -47,6 +47,7 @@ public CachingContractRegistry(Target target, TryGetContractVersionDelegate tryG [typeof(IGCInfo)] = new GCInfoFactory(), [typeof(INotifications)] = new NotificationsFactory(), [typeof(ISignatureDecoder)] = new SignatureDecoderFactory(), + [typeof(ISyncBlock)] = new SyncBlockFactory(), [typeof(IBuiltInCOM)] = new BuiltInCOMFactory(), }; diff --git a/src/native/managed/cdac/tests/DumpTests/Debuggees/SyncBlock/Program.cs b/src/native/managed/cdac/tests/DumpTests/Debuggees/SyncBlock/Program.cs new file mode 100644 index 00000000000000..70257599c3a471 --- /dev/null +++ b/src/native/managed/cdac/tests/DumpTests/Debuggees/SyncBlock/Program.cs @@ -0,0 +1,70 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Threading; + +internal static class Program +{ + private static readonly ManualResetEventSlim OwnerHasLock = new(initialState: false); + private static readonly ManualResetEventSlim ReleaseOwner = new(initialState: false); + private static readonly CountdownEvent WaitersStarted = new(initialCount: 2); + + public static void Main() + { + object gate = new object(); + + Thread owner = new(() => + { + lock (gate) + { + lock (gate) + { + OwnerHasLock.Set(); + ReleaseOwner.Wait(); + } + } + }) + { + IsBackground = true, + Name = "Owner", + }; + + Thread waiter1 = new(() => Waiter(gate)) + { + IsBackground = true, + Name = "Waiter-1", + }; + + Thread waiter2 = new(() => Waiter(gate)) + { + IsBackground = true, + Name = "Waiter-2", + }; + + owner.Start(); + waiter1.Start(); + waiter2.Start(); + + WaitersStarted.Wait(); + _ = SpinWait.SpinUntil( + () => IsThreadWaiting(waiter1) && IsThreadWaiting(waiter2), + TimeSpan.FromMilliseconds(500)); + + Environment.FailFast("Intentional crash to dump threads with blocked waiters."); + } + + private static bool IsThreadWaiting(Thread thread) + { + return (thread.ThreadState & ThreadState.WaitSleepJoin) != 0; + } + + private static void Waiter(object gate) + { + OwnerHasLock.Wait(); + WaitersStarted.Signal(); + lock (gate) + { + } + } +} diff --git a/src/native/managed/cdac/tests/DumpTests/Debuggees/SyncBlock/SyncBlock.csproj b/src/native/managed/cdac/tests/DumpTests/Debuggees/SyncBlock/SyncBlock.csproj new file mode 100644 index 00000000000000..bb776824769fe6 --- /dev/null +++ b/src/native/managed/cdac/tests/DumpTests/Debuggees/SyncBlock/SyncBlock.csproj @@ -0,0 +1,5 @@ + + + Full + + diff --git a/src/native/managed/cdac/tests/DumpTests/README.md b/src/native/managed/cdac/tests/DumpTests/README.md index ba626702c5cf96..7fb673b068077d 100644 --- a/src/native/managed/cdac/tests/DumpTests/README.md +++ b/src/native/managed/cdac/tests/DumpTests/README.md @@ -34,6 +34,7 @@ features and then calls `Environment.FailFast()` to produce a crash dump. | TypeHierarchy | Type inheritance, method tables | Heap | | PInvokeStub | P/Invoke with SetLastError ILStub | Full | | VarargPInvoke | Vararg P/Invoke via __arglist (sprintf) | Full | +| SyncBlock | Sync block locks | Full | The dump type is configured per-debuggee via the `DumpTypes` property in each debuggee's `.csproj` (default: `Heap`, set in `Debuggees/Directory.Build.props`). Debuggees that @@ -56,6 +57,7 @@ use. Tests are `[ConditionalTheory]` methods parameterized by `TestConfiguration | EcmaMetadataDumpTests | EcmaMetadata | MultiModule | | PInvokeStubDumpTests | StackWalk + RTS | PInvokeStub | | VarargPInvokeDumpTests | StackWalk + RTS | VarargPInvoke | +| SyncBlockDumpTests | SyncBlock | SyncBlock | ### Runtime Versions diff --git a/src/native/managed/cdac/tests/DumpTests/SyncBlockDumpTests.cs b/src/native/managed/cdac/tests/DumpTests/SyncBlockDumpTests.cs new file mode 100644 index 00000000000000..6b6fdcc2bb20a7 --- /dev/null +++ b/src/native/managed/cdac/tests/DumpTests/SyncBlockDumpTests.cs @@ -0,0 +1,55 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Microsoft.Diagnostics.DataContractReader.Contracts; +using Xunit; + +namespace Microsoft.Diagnostics.DataContractReader.DumpTests; + +/// +/// Dump-based integration tests for the SyncBlock contract. +/// Uses the SyncBlock debuggee dump, which creates held locks +/// before crashing. +/// +public class SyncBlockDumpTests : DumpTestBase +{ + protected override string DebuggeeName => "SyncBlock"; + protected override string DumpType => "full"; + + [ConditionalTheory] + [MemberData(nameof(TestConfigurations))] + public void SyncBlockContract_CanFindHeldMonitor(TestConfiguration config) + { + InitializeDumpTest(config); + + TargetPointer syncBlock = TargetPointer.Null; + uint ownerThreadId = 0; + uint recursion = 0; + bool found = false; + + ISyncBlock syncBlockContract = Target.Contracts.SyncBlock; + uint syncBlockCount = syncBlockContract.GetSyncBlockCount(); + + for (uint index = 1; index <= syncBlockCount; index++) + { + if (syncBlockContract.IsSyncBlockFree(index)) + continue; + + TargetPointer candidate = syncBlockContract.GetSyncBlock(index); + if (candidate == TargetPointer.Null) + continue; + + if (!syncBlockContract.TryGetLockInfo(candidate, out ownerThreadId, out recursion)) + continue; + + syncBlock = candidate; + found = true; + break; + } + + Assert.True(found, "Expected to find a sync block with a held monitor."); + Assert.True(ownerThreadId != 0, "Expected non-zero lock owner thread id."); + Assert.True(recursion >= 1, "Expected recursion count >= 1."); + Assert.True(syncBlock != TargetPointer.Null, "Expected non-null sync block"); + } +} diff --git a/src/native/managed/cdac/tests/MockDescriptors/MockDescriptors.Object.cs b/src/native/managed/cdac/tests/MockDescriptors/MockDescriptors.Object.cs index 918726dfb50a5d..2b93294557ce16 100644 --- a/src/native/managed/cdac/tests/MockDescriptors/MockDescriptors.Object.cs +++ b/src/native/managed/cdac/tests/MockDescriptors/MockDescriptors.Object.cs @@ -123,7 +123,7 @@ internal TargetPointer AddObject(TargetPointer methodTable, uint prefixSize =0) return fragment.Address + prefixSize; // return pointer to the object, not the prefix; } - internal TargetPointer AddObjectWithSyncBlock(TargetPointer methodTable, uint syncBlockIndex, TargetPointer rcw, TargetPointer ccw) + internal TargetPointer AddObjectWithSyncBlock(TargetPointer methodTable, uint syncBlockIndex, TargetPointer rcw, TargetPointer ccw, TargetPointer ccf) { MockMemorySpace.Builder builder = Builder; TargetTestHelpers targetTestHelpers = builder.TargetTestHelpers; @@ -141,11 +141,11 @@ internal TargetPointer AddObjectWithSyncBlock(TargetPointer methodTable, uint sy targetTestHelpers.Write(syncTableValueDest, syncTableValue); // Add the actual sync block and associated data - AddSyncBlock(syncBlockIndex, rcw, ccw); + AddSyncBlock(syncBlockIndex, rcw, ccw, ccf); return address; } - private void AddSyncBlock(uint index, TargetPointer rcw, TargetPointer ccw) + private void AddSyncBlock(uint index, TargetPointer rcw, TargetPointer ccw, TargetPointer ccf) { Dictionary types = Types; MockMemorySpace.Builder builder = Builder; @@ -177,7 +177,7 @@ private void AddSyncBlock(uint index, TargetPointer rcw, TargetPointer ccw) Span interopInfoData = syncBlock.Data.AsSpan((int)syncBlockSize); targetTestHelpers.WritePointer(interopInfoData.Slice(interopSyncBlockTypeInfo.Fields[nameof(Data.InteropSyncBlockInfo.RCW)].Offset), rcw); targetTestHelpers.WritePointer(interopInfoData.Slice(interopSyncBlockTypeInfo.Fields[nameof(Data.InteropSyncBlockInfo.CCW)].Offset), ccw); - + targetTestHelpers.WritePointer(interopInfoData.Slice(interopSyncBlockTypeInfo.Fields[nameof(Data.InteropSyncBlockInfo.CCF)].Offset), ccf); builder.AddHeapFragments([syncTableEntry, syncBlock]); } diff --git a/src/native/managed/cdac/tests/MockDescriptors/MockDescriptors.cs b/src/native/managed/cdac/tests/MockDescriptors/MockDescriptors.cs index 6c111cee24ef8b..699fb66bb73669 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), ] }; @@ -116,6 +117,9 @@ internal record TypeFields Fields = [ new(nameof(Data.SyncBlock.InteropInfo), DataType.pointer), + new(nameof(Data.SyncBlock.Lock), DataType.pointer), + new(nameof(Data.SyncBlock.ThinLock), DataType.uint32), + new(nameof(Data.SyncBlock.LinkNext), DataType.pointer), ] }; @@ -126,6 +130,7 @@ internal record TypeFields [ new(nameof(Data.InteropSyncBlockInfo.RCW), DataType.pointer), new(nameof(Data.InteropSyncBlockInfo.CCW), DataType.pointer), + new(nameof(Data.InteropSyncBlockInfo.CCF), DataType.pointer), ] }; diff --git a/src/native/managed/cdac/tests/ObjectTests.cs b/src/native/managed/cdac/tests/ObjectTests.cs index ab613f2324f0f8..5a679def4e3447 100644 --- a/src/native/managed/cdac/tests/ObjectTests.cs +++ b/src/native/managed/cdac/tests/ObjectTests.cs @@ -128,29 +128,32 @@ public void ComData(MockTarget.Architecture arch) TargetPointer expectedRCW = 0xaaaa; TargetPointer expectedCCW = 0xbbbb; + TargetPointer expectedCCF = 0xcccc; ObjectContractHelper(arch, (objectBuilder) => { uint syncBlockIndex = 0; - TestComObjectAddress = objectBuilder.AddObjectWithSyncBlock(0, syncBlockIndex++, expectedRCW, expectedCCW); - TestNonComObjectAddress = objectBuilder.AddObjectWithSyncBlock(0, syncBlockIndex++, TargetPointer.Null, TargetPointer.Null); + TestComObjectAddress = objectBuilder.AddObjectWithSyncBlock(0, syncBlockIndex++, expectedRCW, expectedCCW, expectedCCF); + TestNonComObjectAddress = objectBuilder.AddObjectWithSyncBlock(0, syncBlockIndex++, TargetPointer.Null, TargetPointer.Null, TargetPointer.Null); }, (target) => { Contracts.IObject contract = target.Contracts.Object; Assert.NotNull(contract); { - bool res = contract.GetBuiltInComData(TestComObjectAddress, out TargetPointer rcw, out TargetPointer ccw); + bool res = contract.GetBuiltInComData(TestComObjectAddress, out TargetPointer rcw, out TargetPointer ccw, out TargetPointer ccf); Assert.True(res); Assert.Equal(expectedRCW.Value, rcw.Value); Assert.Equal(expectedCCW.Value, ccw.Value); + Assert.Equal(expectedCCF.Value, ccf.Value); } { - bool res = contract.GetBuiltInComData(TestNonComObjectAddress, out TargetPointer rcw, out TargetPointer ccw); + bool res = contract.GetBuiltInComData(TestNonComObjectAddress, out TargetPointer rcw, out TargetPointer ccw, out TargetPointer ccf); Assert.False(res); Assert.Equal(TargetPointer.Null.Value, rcw.Value); Assert.Equal(TargetPointer.Null.Value, ccw.Value); + Assert.Equal(TargetPointer.Null.Value, ccf.Value); } }); }