diff --git a/docs/design/datacontracts/Object.md b/docs/design/datacontracts/Object.md index b04c2643b24e7..18747df027df3 100644 --- a/docs/design/datacontracts/Object.md +++ b/docs/design/datacontracts/Object.md @@ -13,6 +13,9 @@ 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); ``` ## Version 1 @@ -21,9 +24,13 @@ Data descriptors used: | Data Descriptor Name | Field | Meaning | | --- | --- | --- | | `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) | | `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 | @@ -32,6 +39,8 @@ 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 | @@ -91,4 +100,29 @@ TargetPointer GetArrayData(TargetPointer address, out uint count, out TargetPoin ulong dataOffset = typeSystemContract.GetBaseSize(typeHandle) - _target.ReadGlobal("ObjectHeaderSize"); return address + dataOffset; } + +bool GetBuiltInComData(TargetPointer address, out TargetPointer rcw, out TargetPointer ccw); +{ + uint syncBlockValue = target.Read(address - _target.ReadGlobal("SyncBlockValueToObjectOffset")); + + // 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 false; + + // 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; +} ``` diff --git a/src/coreclr/debug/daccess/request.cpp b/src/coreclr/debug/daccess/request.cpp index 6f75908675211..9200ba074dc64 100644 --- a/src/coreclr/debug/daccess/request.cpp +++ b/src/coreclr/debug/daccess/request.cpp @@ -71,6 +71,7 @@ TADDR DACGetMethodTableFromObjectPointer(TADDR objAddr, ICorDebugDataTarget * ta ULONG32 returned = 0; TADDR Value = (TADDR)NULL; + // Every object has a pointer to its method table at offset 0 HRESULT hr = target->ReadVirtual(objAddr, (PBYTE)&Value, sizeof(TADDR), &returned); if ((hr != S_OK) || (returned != sizeof(TADDR))) @@ -92,6 +93,8 @@ PTR_SyncBlock DACGetSyncBlockFromObjectPointer(TADDR objAddr, ICorDebugDataTarge ULONG32 returned = 0; DWORD Value = 0; + // Every object has an object header right before it. The sync block value (DWORD) is the last member + // of the object header. Read the DWORD right before the object address to get the sync block value. HRESULT hr = target->ReadVirtual(objAddr - sizeof(DWORD), (PBYTE)&Value, sizeof(DWORD), &returned); if ((hr != S_OK) || (returned != sizeof(DWORD))) diff --git a/src/coreclr/debug/runtimeinfo/datadescriptor.h b/src/coreclr/debug/runtimeinfo/datadescriptor.h index b890bd16c720d..e301dc57ee253 100644 --- a/src/coreclr/debug/runtimeinfo/datadescriptor.h +++ b/src/coreclr/debug/runtimeinfo/datadescriptor.h @@ -41,6 +41,8 @@ // then the field layout can be specified as // CDAC_TYPE_FIELD(MyClassLayout, pointer, MyField, cdac_data::MyField) // There can be zero or more CDAC_TYPE_FIELD entries per type layout +// For types mapping to managed objects, use exact managed type field names in the descriptor, as +// field names often can't change due to binary serialization or implicit diagnostic contracts // // CDAC_TYPE_END(cdacTypeIdentifier) specifies the end of the type layout for cdacTypeIdentifier // @@ -172,6 +174,8 @@ CDAC_TYPE_BEGIN(GCHandle) CDAC_TYPE_SIZE(sizeof(OBJECTHANDLE)) CDAC_TYPE_END(GCHandle) +// Object + CDAC_TYPE_BEGIN(Object) CDAC_TYPE_INDETERMINATE(Object) CDAC_TYPE_FIELD(Object, /*pointer*/, m_pMethTab, cdac_data::m_pMethTab) @@ -188,6 +192,24 @@ CDAC_TYPE_SIZE(sizeof(ArrayBase)) CDAC_TYPE_FIELD(Array, /*pointer*/, m_NumComponents, cdac_data::m_NumComponents) CDAC_TYPE_END(Array) +CDAC_TYPE_BEGIN(InteropSyncBlockInfo) +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) +#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_END(SyncBlock) + +CDAC_TYPE_BEGIN(SyncTableEntry) +CDAC_TYPE_SIZE(sizeof(SyncTableEntry)) +CDAC_TYPE_FIELD(SyncTableEntry, /*pointer*/, SyncBlock, offsetof(SyncTableEntry, m_SyncBlock)) +CDAC_TYPE_END(SyncTableEntry) + // Loader CDAC_TYPE_BEGIN(Module) @@ -310,12 +332,14 @@ CDAC_GLOBAL(ObjectToMethodTableUnmask, uint8, 1 | 1 << 1) CDAC_GLOBAL(SOSBreakingChangeVersion, uint8, SOS_BREAKING_CHANGE_VERSION) CDAC_GLOBAL(MethodDescAlignment, uint64, MethodDesc::ALIGNMENT) CDAC_GLOBAL(ObjectHeaderSize, uint64, OBJHEADER_SIZE) +CDAC_GLOBAL(SyncBlockValueToObjectOffset, uint16, OBJHEADER_SIZE - cdac_data::SyncBlockValue) CDAC_GLOBAL_POINTER(ArrayBoundsZero, cdac_data::ArrayBoundsZero) CDAC_GLOBAL_POINTER(ExceptionMethodTable, &::g_pExceptionClass) CDAC_GLOBAL_POINTER(FreeObjectMethodTable, &::g_pFreeObjectMethodTable) 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(MiniMetaDataBuffAddress, &::g_MiniMetaDataBuffAddress) CDAC_GLOBAL_POINTER(MiniMetaDataBuffMaxSize, &::g_MiniMetaDataBuffMaxSize) CDAC_GLOBALS_END() diff --git a/src/coreclr/vm/syncblk.h b/src/coreclr/vm/syncblk.h index 029ee9337d7aa..e3eb90b663fd3 100644 --- a/src/coreclr/vm/syncblk.h +++ b/src/coreclr/vm/syncblk.h @@ -969,6 +969,17 @@ class InteropSyncBlockInfo // ObjectiveCMarshal.NativeAot.cs BYTE m_taggedAlloc[2 * sizeof(void*)]; #endif // FEATURE_OBJCMARSHAL + + template friend struct ::cdac_data; +}; + +template<> +struct cdac_data +{ +#ifdef FEATURE_COMINTEROP + static constexpr size_t CCW = offsetof(InteropSyncBlockInfo, m_pCCW); + static constexpr size_t RCW = offsetof(InteropSyncBlockInfo, m_pRCW); +#endif // FEATURE_COMINTEROP }; typedef DPTR(InteropSyncBlockInfo) PTR_InteropSyncBlockInfo; @@ -1274,6 +1285,14 @@ class SyncBlock return m_Monitor.GetPtrForLockContract(); } #endif // defined(ENABLE_CONTRACTS_IMPL) + + template friend struct ::cdac_data; +}; + +template<> +struct cdac_data +{ + static constexpr size_t InteropInfo = offsetof(SyncBlock, m_pInteropInfo); }; class SyncTableEntry @@ -1654,8 +1673,15 @@ class ObjHeader void ReleaseSpinLock(); BOOL Validate (BOOL bVerifySyncBlkIndex = TRUE); + + template friend struct ::cdac_data; }; +template<> +struct cdac_data +{ + static constexpr size_t SyncBlockValue = offsetof(ObjHeader, m_SyncBlockValue); +}; typedef DPTR(class ObjHeader) PTR_ObjHeader; diff --git a/src/native/managed/cdacreader/src/Constants.cs b/src/native/managed/cdacreader/src/Constants.cs index 28953df35d46f..bd29ab0aeb8d9 100644 --- a/src/native/managed/cdacreader/src/Constants.cs +++ b/src/native/managed/cdacreader/src/Constants.cs @@ -30,6 +30,9 @@ internal static class Globals internal const string MethodDescAlignment = nameof(MethodDescAlignment); internal const string ObjectHeaderSize = nameof(ObjectHeaderSize); + internal const string SyncBlockValueToObjectOffset = nameof(SyncBlockValueToObjectOffset); + + internal const string SyncTableEntries = nameof(SyncTableEntries); internal const string ArrayBoundsZero = nameof(ArrayBoundsZero); } diff --git a/src/native/managed/cdacreader/src/Contracts/Object.cs b/src/native/managed/cdacreader/src/Contracts/Object.cs index 36b5aad17c910..944b97388b69a 100644 --- a/src/native/managed/cdacreader/src/Contracts/Object.cs +++ b/src/native/managed/cdacreader/src/Contracts/Object.cs @@ -14,9 +14,11 @@ static IContract IContract.Create(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), + 1 => new Object_1(target, methodTableOffset, objectToMethodTableUnmask, stringMethodTable, syncTableEntries), _ => default(Object), }; } @@ -25,6 +27,7 @@ static IContract IContract.Create(Target target, int version) public virtual string GetStringValue(TargetPointer address) => throw new NotImplementedException(); public virtual TargetPointer GetArrayData(TargetPointer address, out uint count, out TargetPointer boundsStart, out TargetPointer lowerBounds) => throw new NotImplementedException(); + public virtual bool GetBuiltInComData(TargetPointer address, out TargetPointer rcw, out TargetPointer ccw) => throw new NotImplementedException(); } internal readonly struct Object : IObject diff --git a/src/native/managed/cdacreader/src/Contracts/Object_1.cs b/src/native/managed/cdacreader/src/Contracts/Object_1.cs index a7acee5ec3b75..4428392e4e067 100644 --- a/src/native/managed/cdacreader/src/Contracts/Object_1.cs +++ b/src/native/managed/cdacreader/src/Contracts/Object_1.cs @@ -11,15 +11,32 @@ namespace Microsoft.Diagnostics.DataContractReader.Contracts; { private readonly Target _target; private readonly ulong _methodTableOffset; - private readonly TargetPointer _stringMethodTable; private readonly byte _objectToMethodTableUnmask; + private readonly TargetPointer _stringMethodTable; + private readonly TargetPointer _syncTableEntries; + + private static class SyncBlockValue + { + [Flags] + public enum Bits + { + // Value represents either the hash code or sync block index (bits 0-25) + // - IsHashCodeOrSyncBlockIndex and IsHashCode are set: rest of the value is the hash code. + // - IsHashCodeOrSyncBlockIndex set, IsHashCode not set: rest of the value is the sync block index + IsHashCodeOrSyncBlockIndex = 0x08000000, + IsHashCode = 0x04000000, + } + + public const uint SyncBlockIndexMask = (1 << 26) - 1; + } - internal Object_1(Target target, ulong methodTableOffset, byte objectToMethodTableUnmask, TargetPointer stringMethodTable) + internal Object_1(Target target, ulong methodTableOffset, byte objectToMethodTableUnmask, TargetPointer stringMethodTable, TargetPointer syncTableEntries) { _target = target; _methodTableOffset = methodTableOffset; _stringMethodTable = stringMethodTable; _objectToMethodTableUnmask = objectToMethodTableUnmask; + _syncTableEntries = syncTableEntries; } public TargetPointer GetMethodTableAddress(TargetPointer address) @@ -76,4 +93,37 @@ public TargetPointer GetArrayData(TargetPointer address, out uint count, out Tar ulong dataOffset = typeSystemContract.GetBaseSize(typeHandle) - _target.ReadGlobal(Constants.Globals.ObjectHeaderSize); return address + dataOffset; } + + public bool GetBuiltInComData(TargetPointer address, out TargetPointer rcw, out TargetPointer ccw) + { + rcw = TargetPointer.Null; + ccw = TargetPointer.Null; + + Data.SyncBlock? syncBlock = GetSyncBlock(address); + if (syncBlock == null) + return false; + + Data.InteropSyncBlockInfo? interopInfo = syncBlock.InteropInfo; + if (interopInfo == null) + return false; + + rcw = interopInfo.RCW; + ccw = interopInfo.CCW; + return rcw != TargetPointer.Null || ccw != TargetPointer.Null; + } + + private Data.SyncBlock? GetSyncBlock(TargetPointer address) + { + 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; + + // 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; + } } diff --git a/src/native/managed/cdacreader/src/Data/InteropSyncBlockInfo.cs b/src/native/managed/cdacreader/src/Data/InteropSyncBlockInfo.cs new file mode 100644 index 0000000000000..cffa804296de3 --- /dev/null +++ b/src/native/managed/cdacreader/src/Data/InteropSyncBlockInfo.cs @@ -0,0 +1,25 @@ +// 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 InteropSyncBlockInfo : IData +{ + static InteropSyncBlockInfo IData.Create(Target target, TargetPointer address) + => new InteropSyncBlockInfo(target, address); + + public InteropSyncBlockInfo(Target target, TargetPointer address) + { + Target.TypeInfo type = target.GetTypeInfo(DataType.InteropSyncBlockInfo); + + RCW = type.Fields.TryGetValue(nameof(RCW), out Target.FieldInfo rcwField) + ? target.ReadPointer(address + (ulong)rcwField.Offset) + : TargetPointer.Null; + CCW = type.Fields.TryGetValue(nameof(CCW), out Target.FieldInfo ccwField) + ? target.ReadPointer(address + (ulong)ccwField.Offset) + : TargetPointer.Null; + } + + public TargetPointer RCW { get; init; } + public TargetPointer CCW { get; init; } +} diff --git a/src/native/managed/cdacreader/src/Data/SyncBlock.cs b/src/native/managed/cdacreader/src/Data/SyncBlock.cs new file mode 100644 index 0000000000000..f045954fce0bf --- /dev/null +++ b/src/native/managed/cdacreader/src/Data/SyncBlock.cs @@ -0,0 +1,21 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.Diagnostics.DataContractReader.Data; + +internal sealed class SyncBlock : IData +{ + static SyncBlock IData.Create(Target target, TargetPointer address) + => new SyncBlock(target, address); + + 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); + } + + public InteropSyncBlockInfo? InteropInfo { get; init; } +} diff --git a/src/native/managed/cdacreader/src/Data/SyncTableEntry.cs b/src/native/managed/cdacreader/src/Data/SyncTableEntry.cs new file mode 100644 index 0000000000000..b0c032b07379c --- /dev/null +++ b/src/native/managed/cdacreader/src/Data/SyncTableEntry.cs @@ -0,0 +1,21 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.Diagnostics.DataContractReader.Data; + +internal sealed class SyncTableEntry : IData +{ + static SyncTableEntry IData.Create(Target target, TargetPointer address) + => new SyncTableEntry(target, address); + + 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); + } + + public SyncBlock? SyncBlock { get; init; } +} diff --git a/src/native/managed/cdacreader/src/DataType.cs b/src/native/managed/cdacreader/src/DataType.cs index 2753e67438926..25f60a2fc8af5 100644 --- a/src/native/managed/cdacreader/src/DataType.cs +++ b/src/native/managed/cdacreader/src/DataType.cs @@ -42,4 +42,7 @@ public enum DataType MethodDesc, MethodDescChunk, Array, + SyncBlock, + SyncTableEntry, + InteropSyncBlockInfo, } diff --git a/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs b/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs index 6cc4dc46f6498..2d00d0a021de5 100644 --- a/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs +++ b/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs @@ -374,9 +374,13 @@ public unsafe int GetObjectData(ulong objAddr, DacpObjectData* data) data->ObjectType = DacpObjectType.OBJ_OTHER; } - // TODO: [cdac] Get RCW and CCW from interop info on sync block - if (_target.ReadGlobal(Constants.Globals.FeatureCOMInterop) != 0) - return HResults.E_NOTIMPL; + // Populate COM data if this is a COM object + if (_target.ReadGlobal(Constants.Globals.FeatureCOMInterop) != 0 + && objectContract.GetBuiltInComData(objAddr, out TargetPointer rcw, out TargetPointer ccw)) + { + data->RCW = rcw; + data->CCW = ccw; + } } catch (System.Exception ex) diff --git a/src/native/managed/cdacreader/tests/MockDescriptors.cs b/src/native/managed/cdacreader/tests/MockDescriptors.cs index e09e7160af1cb..3a17381371484 100644 --- a/src/native/managed/cdacreader/tests/MockDescriptors.cs +++ b/src/native/managed/cdacreader/tests/MockDescriptors.cs @@ -66,6 +66,28 @@ public class MockDescriptors }, }; + private static readonly Target.TypeInfo SyncTableEntryInfo = new Target.TypeInfo() + { + Fields = { + { nameof(Data.SyncTableEntry.SyncBlock), new() { Offset = 0, Type = DataType.pointer} }, + }, + }; + + private static readonly Target.TypeInfo SyncBlockTypeInfo = new Target.TypeInfo() + { + Fields = { + { nameof(Data.SyncBlock.InteropInfo), new() { Offset = 0, Type = DataType.pointer} }, + }, + }; + + private static readonly Target.TypeInfo InteropSyncBlockTypeInfo = new Target.TypeInfo() + { + Fields = { + { nameof(Data.InteropSyncBlockInfo.RCW), new() { Offset = 0, Type = DataType.pointer} }, + { nameof(Data.InteropSyncBlockInfo.CCW), new() { Offset = 0x8, Type = DataType.pointer} }, + }, + }; + public static class RuntimeTypeSystem { internal const ulong TestFreeObjectMethodTableGlobalAddress = 0x00000000_7a0000a0; @@ -144,30 +166,42 @@ internal static MockMemorySpace.Builder AddMethodTable(TargetTestHelpers targetT public static class Object { - const ulong TestStringMethodTableGlobalAddress = 0x00000000_100000a0; - const ulong TestStringMethodTableAddress = 0x00000000_100000a8; + private const ulong TestStringMethodTableGlobalAddress = 0x00000000_100000a0; + private const ulong TestStringMethodTableAddress = 0x00000000_100000a8; internal const ulong TestArrayBoundsZeroGlobalAddress = 0x00000000_100000b0; + private const ulong TestSyncTableEntriesGlobalAddress = 0x00000000_100000c0; + private const ulong TestSyncTableEntriesAddress = 0x00000000_f0000000; + + internal const ulong TestObjectToMethodTableUnmask = 0x7; + internal const ulong TestSyncBlockValueToObjectOffset = sizeof(uint); + internal static Dictionary Types(TargetTestHelpers helpers) => RuntimeTypeSystem.Types.Concat( - new Dictionary(){ + new Dictionary() + { [DataType.Object] = ObjectTypeInfo, [DataType.String] = StringTypeInfo, - [DataType.Array] = ArrayTypeInfo with { Size = helpers.ArrayBaseSize } + [DataType.Array] = ArrayTypeInfo with { Size = helpers.ArrayBaseSize }, + [DataType.SyncTableEntry] = SyncTableEntryInfo with { Size = (uint)helpers.SizeOfTypeInfo(SyncTableEntryInfo) }, + [DataType.SyncBlock] = SyncBlockTypeInfo, + [DataType.InteropSyncBlockInfo] = InteropSyncBlockTypeInfo, }).ToDictionary(); - internal const ulong TestObjectToMethodTableUnmask = 0x7; internal static (string Name, ulong Value, string? Type)[] Globals(TargetTestHelpers helpers) => RuntimeTypeSystem.Globals.Concat( [ (nameof(Constants.Globals.ObjectToMethodTableUnmask), TestObjectToMethodTableUnmask, "uint8"), (nameof(Constants.Globals.StringMethodTable), TestStringMethodTableGlobalAddress, null), (nameof(Constants.Globals.ArrayBoundsZero), TestArrayBoundsZeroGlobalAddress, null), + (nameof(Constants.Globals.SyncTableEntries), TestSyncTableEntriesGlobalAddress, null), (nameof(Constants.Globals.ObjectHeaderSize), helpers.ObjHeaderSize, "uint32"), + (nameof(Constants.Globals.SyncBlockValueToObjectOffset), TestSyncBlockValueToObjectOffset, "uint16"), ]).ToArray(); internal static MockMemorySpace.Builder AddGlobalPointers(TargetTestHelpers targetTestHelpers, MockMemorySpace.Builder builder) { builder = RuntimeTypeSystem.AddGlobalPointers(targetTestHelpers, builder); builder = AddStringMethodTablePointer(targetTestHelpers, builder); + builder = AddSyncTableEntriesPointer(targetTestHelpers, builder); return builder; } @@ -181,6 +215,13 @@ private static MockMemorySpace.Builder AddStringMethodTablePointer(TargetTestHel ]); } + private static MockMemorySpace.Builder AddSyncTableEntriesPointer(TargetTestHelpers targetTestHelpers, MockMemorySpace.Builder builder) + { + MockMemorySpace.HeapFragment fragment = new() { Name = "Address of Sync Table Entries", Address = TestSyncTableEntriesGlobalAddress, Data = new byte[targetTestHelpers.PointerSize] }; + targetTestHelpers.WritePointer(fragment.Data, TestSyncTableEntriesAddress); + return builder.AddHeapFragment(fragment); + } + internal static MockMemorySpace.Builder AddObject(TargetTestHelpers targetTestHelpers, MockMemorySpace.Builder builder, TargetPointer address, TargetPointer methodTable) { MockMemorySpace.HeapFragment fragment = new() { Name = $"Object : MT = '{methodTable}'", Address = address, Data = new byte[targetTestHelpers.SizeOfTypeInfo(ObjectTypeInfo)] }; @@ -189,6 +230,56 @@ internal static MockMemorySpace.Builder AddObject(TargetTestHelpers targetTestHe return builder.AddHeapFragment(fragment); } + internal static MockMemorySpace.Builder AddObjectWithSyncBlock(TargetTestHelpers targetTestHelpers, MockMemorySpace.Builder builder, TargetPointer address, TargetPointer methodTable, uint syncBlockIndex, TargetPointer rcw, TargetPointer ccw) + { + const uint IsSyncBlockIndexBits = 0x08000000; + const uint SyncBlockIndexMask = (1 << 26) - 1; + if ((syncBlockIndex & SyncBlockIndexMask) != syncBlockIndex) + throw new ArgumentOutOfRangeException(nameof(syncBlockIndex), "Invalid sync block index"); + + builder = AddObject(targetTestHelpers, builder, address, methodTable); + + // Add the sync table value before the object + uint syncTableValue = IsSyncBlockIndexBits | syncBlockIndex; + TargetPointer syncTableValueAddr = address - TestSyncBlockValueToObjectOffset; + MockMemorySpace.HeapFragment fragment = new() { Name = $"Sync Table Value : index = {syncBlockIndex}", Address = syncTableValueAddr, Data = new byte[sizeof(uint)] }; + targetTestHelpers.Write(fragment.Data, syncTableValue); + builder = builder.AddHeapFragment(fragment); + + // Add the actual sync block and associated data + return AddSyncBlock(targetTestHelpers, builder, syncBlockIndex, rcw, ccw); + } + + private static MockMemorySpace.Builder AddSyncBlock(TargetTestHelpers targetTestHelpers, MockMemorySpace.Builder builder, uint index, TargetPointer rcw, TargetPointer ccw) + { + // Tests write the sync blocks starting at TestSyncBlocksAddress + const ulong TestSyncBlocksAddress = 0x00000000_e0000000; + int syncBlockSize = targetTestHelpers.SizeOfTypeInfo(SyncBlockTypeInfo); + int interopSyncBlockInfoSize = targetTestHelpers.SizeOfTypeInfo(InteropSyncBlockTypeInfo); + ulong syncBlockAddr = TestSyncBlocksAddress + index * (ulong)(syncBlockSize + interopSyncBlockInfoSize); + + // Add the sync table entry - pointing at the sync block + uint syncTableEntrySize = (uint)targetTestHelpers.SizeOfTypeInfo(SyncTableEntryInfo); + ulong syncTableEntryAddr = TestSyncTableEntriesAddress + index * syncTableEntrySize; + MockMemorySpace.HeapFragment syncTableEntry = new() { Name = $"SyncTableEntries[{index}]", Address = syncTableEntryAddr, Data = new byte[syncTableEntrySize] }; + Span syncTableEntryData = syncTableEntry.Data; + targetTestHelpers.WritePointer(syncTableEntryData.Slice(SyncTableEntryInfo.Fields[nameof(Data.SyncTableEntry.SyncBlock)].Offset), syncBlockAddr); + + // Add the sync block - pointing at the interop sync block info + ulong interopInfoAddr = syncBlockAddr + (ulong)syncBlockSize; + MockMemorySpace.HeapFragment syncBlock = new() { Name = $"Sync Block", Address = syncBlockAddr, Data = new byte[syncBlockSize] }; + Span syncBlockData = syncBlock.Data; + targetTestHelpers.WritePointer(syncBlockData.Slice(SyncBlockTypeInfo.Fields[nameof(Data.SyncBlock.InteropInfo)].Offset), interopInfoAddr); + + // Add the interop sync block info + MockMemorySpace.HeapFragment interopInfo = new() { Name = $"Interop Sync Block Info", Address = interopInfoAddr, Data = new byte[interopSyncBlockInfoSize] }; + Span interopInfoData = interopInfo.Data; + targetTestHelpers.WritePointer(interopInfoData.Slice(InteropSyncBlockTypeInfo.Fields[nameof(Data.InteropSyncBlockInfo.RCW)].Offset), rcw); + targetTestHelpers.WritePointer(interopInfoData.Slice(InteropSyncBlockTypeInfo.Fields[nameof(Data.InteropSyncBlockInfo.CCW)].Offset), ccw); + + return builder.AddHeapFragments([syncTableEntry, syncBlock, interopInfo]); + } + internal static MockMemorySpace.Builder AddStringObject(TargetTestHelpers targetTestHelpers, MockMemorySpace.Builder builder, TargetPointer address, string value) { int size = targetTestHelpers.SizeOfTypeInfo(ObjectTypeInfo) + targetTestHelpers.SizeOfTypeInfo(StringTypeInfo) + value.Length * sizeof(char); diff --git a/src/native/managed/cdacreader/tests/ObjectTests.cs b/src/native/managed/cdacreader/tests/ObjectTests.cs index 4adce0f56fa88..4b9fd05b8dfdf 100644 --- a/src/native/managed/cdacreader/tests/ObjectTests.cs +++ b/src/native/managed/cdacreader/tests/ObjectTests.cs @@ -159,4 +159,42 @@ public void ArrayData(MockTarget.Architecture arch) } }); } + + [Theory] + [ClassData(typeof(MockTarget.StdArch))] + public void ComData(MockTarget.Architecture arch) + { + const ulong TestComObjectAddress = 0x00000000_10000010; + const ulong TestNonComObjectAddress = 0x00000000_10000020; + + TargetPointer expectedRCW = 0xaaaa; + TargetPointer expectedCCW = 0xbbbb; + + TargetTestHelpers targetTestHelpers = new(arch); + ObjectContractHelper(arch, + (builder) => + { + uint syncBlockIndex = 0; + builder = MockObject.AddObjectWithSyncBlock(targetTestHelpers, builder, TestComObjectAddress, 0, syncBlockIndex++, expectedRCW, expectedCCW); + builder = MockObject.AddObjectWithSyncBlock(targetTestHelpers, builder, TestNonComObjectAddress, 0, syncBlockIndex++, TargetPointer.Null, TargetPointer.Null); + return builder; + }, + (target) => + { + Contracts.IObject contract = target.Contracts.Object; + Assert.NotNull(contract); + { + bool res = contract.GetBuiltInComData(TestComObjectAddress, out TargetPointer rcw, out TargetPointer ccw); + Assert.True(res); + Assert.Equal(expectedRCW.Value, rcw.Value); + Assert.Equal(expectedCCW.Value, ccw.Value); + } + { + bool res = contract.GetBuiltInComData(TestNonComObjectAddress, out TargetPointer rcw, out TargetPointer ccw); + Assert.False(res); + Assert.Equal(TargetPointer.Null.Value, rcw.Value); + Assert.Equal(TargetPointer.Null.Value, ccw.Value); + } + }); + } }