1
0
Fork 0
mirror of https://github.com/VSadov/Satori.git synced 2025-06-08 03:27:04 +09:00

[cdac] Implement ISOSDacInterface::GetObjectData getting COM RCW/CCW (#105846)

- Add `SyncTableEntry`, `SyncBlock`, `InteropSyncBlockInfo` data descriptors, `SyncTableEntries` global
- Get the sync block corresponding to a given object address in `Object` contract
- Add `GetComData` to `Object` contract
- Finish implementation of `ISOSDacInterface::GetObjectData` such that it populates the RCW/CCW
- Add test helpers for mocking out sync blocks
This commit is contained in:
Elinor Fung 2024-08-06 12:38:18 -07:00 committed by GitHub
parent a5635ff00c
commit c5a89d4429
Signed by: github
GPG key ID: B5690EEEBB952194
14 changed files with 360 additions and 14 deletions

View file

@ -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<uint>("ObjectHeaderSize");
return address + dataOffset;
}
bool GetBuiltInComData(TargetPointer address, out TargetPointer rcw, out TargetPointer ccw);
{
uint syncBlockValue = target.Read<uint>(address - _target.ReadGlobal<ushort>("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;
}
```

View file

@ -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)))

View file

@ -41,6 +41,8 @@
// then the field layout can be specified as
// CDAC_TYPE_FIELD(MyClassLayout, pointer, MyField, cdac_data<MyClass>::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<Object>::m_pMethTab)
@ -188,6 +192,24 @@ CDAC_TYPE_SIZE(sizeof(ArrayBase))
CDAC_TYPE_FIELD(Array, /*pointer*/, m_NumComponents, cdac_data<ArrayBase>::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<InteropSyncBlockInfo>::CCW)
CDAC_TYPE_FIELD(InteropSyncBlockInfo, /*pointer*/, RCW, cdac_data<InteropSyncBlockInfo>::RCW)
#endif // FEATURE_COMINTEROP
CDAC_TYPE_END(InteropSyncBlockInfo)
CDAC_TYPE_BEGIN(SyncBlock)
CDAC_TYPE_INDETERMINATE(SyncBlock)
CDAC_TYPE_FIELD(SyncBlock, /*pointer*/, InteropInfo, cdac_data<SyncBlock>::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<ObjHeader>::SyncBlockValue)
CDAC_GLOBAL_POINTER(ArrayBoundsZero, cdac_data<ArrayBase>::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()

View file

@ -969,6 +969,17 @@ private:
// ObjectiveCMarshal.NativeAot.cs
BYTE m_taggedAlloc[2 * sizeof(void*)];
#endif // FEATURE_OBJCMARSHAL
template<typename T> friend struct ::cdac_data;
};
template<>
struct cdac_data<InteropSyncBlockInfo>
{
#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<typename T> friend struct ::cdac_data;
};
template<>
struct cdac_data<SyncBlock>
{
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<typename T> friend struct ::cdac_data;
};
template<>
struct cdac_data<ObjHeader>
{
static constexpr size_t SyncBlockValue = offsetof(ObjHeader, m_SyncBlockValue);
};
typedef DPTR(class ObjHeader) PTR_ObjHeader;

View file

@ -30,6 +30,9 @@ internal static class Constants
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);
}

View file

@ -14,9 +14,11 @@ internal interface IObject : IContract
byte objectToMethodTableUnmask = target.ReadGlobal<byte>(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 @@ internal interface IObject : IContract
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

View file

@ -11,15 +11,32 @@ internal readonly struct Object_1 : IObject
{
private readonly Target _target;
private readonly ulong _methodTableOffset;
private readonly TargetPointer _stringMethodTable;
private readonly byte _objectToMethodTableUnmask;
private readonly TargetPointer _stringMethodTable;
private readonly TargetPointer _syncTableEntries;
internal Object_1(Target target, ulong methodTableOffset, byte objectToMethodTableUnmask, TargetPointer stringMethodTable)
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, TargetPointer syncTableEntries)
{
_target = target;
_methodTableOffset = methodTableOffset;
_stringMethodTable = stringMethodTable;
_objectToMethodTableUnmask = objectToMethodTableUnmask;
_syncTableEntries = syncTableEntries;
}
public TargetPointer GetMethodTableAddress(TargetPointer address)
@ -76,4 +93,37 @@ internal readonly struct Object_1 : IObject
ulong dataOffset = typeSystemContract.GetBaseSize(typeHandle) - _target.ReadGlobal<uint>(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<uint>(address - _target.ReadGlobal<ushort>(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<Data.SyncTableEntry>(_syncTableEntries + offsetInSyncTableEntries);
return entry.SyncBlock;
}
}

View file

@ -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<InteropSyncBlockInfo>
{
static InteropSyncBlockInfo IData<InteropSyncBlockInfo>.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; }
}

View file

@ -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<SyncBlock>
{
static SyncBlock IData<SyncBlock>.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<InteropSyncBlockInfo>(interopInfoPointer);
}
public InteropSyncBlockInfo? InteropInfo { get; init; }
}

View file

@ -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<SyncTableEntry>
{
static SyncTableEntry IData<SyncTableEntry>.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<SyncBlock>(syncBlockPointer);
}
public SyncBlock? SyncBlock { get; init; }
}

View file

@ -42,4 +42,7 @@ public enum DataType
MethodDesc,
MethodDescChunk,
Array,
SyncBlock,
SyncTableEntry,
InteropSyncBlockInfo,
}

View file

@ -374,9 +374,13 @@ internal sealed partial class SOSDacImpl : ISOSDacInterface, ISOSDacInterface2,
data->ObjectType = DacpObjectType.OBJ_OTHER;
}
// TODO: [cdac] Get RCW and CCW from interop info on sync block
if (_target.ReadGlobal<byte>(Constants.Globals.FeatureCOMInterop) != 0)
return HResults.E_NOTIMPL;
// Populate COM data if this is a COM object
if (_target.ReadGlobal<byte>(Constants.Globals.FeatureCOMInterop) != 0
&& objectContract.GetBuiltInComData(objAddr, out TargetPointer rcw, out TargetPointer ccw))
{
data->RCW = rcw;
data->CCW = ccw;
}
}
catch (System.Exception ex)

View file

@ -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 @@ public class MockDescriptors
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;
internal static Dictionary<DataType, Target.TypeInfo> Types(TargetTestHelpers helpers) => RuntimeTypeSystem.Types.Concat(
new Dictionary<DataType, Target.TypeInfo>(){
[DataType.Object] = ObjectTypeInfo,
[DataType.String] = StringTypeInfo,
[DataType.Array] = ArrayTypeInfo with { Size = helpers.ArrayBaseSize }
}).ToDictionary();
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<DataType, Target.TypeInfo> Types(TargetTestHelpers helpers) => RuntimeTypeSystem.Types.Concat(
new Dictionary<DataType, Target.TypeInfo>()
{
[DataType.Object] = ObjectTypeInfo,
[DataType.String] = StringTypeInfo,
[DataType.Array] = ArrayTypeInfo with { Size = helpers.ArrayBaseSize },
[DataType.SyncTableEntry] = SyncTableEntryInfo with { Size = (uint)helpers.SizeOfTypeInfo(SyncTableEntryInfo) },
[DataType.SyncBlock] = SyncBlockTypeInfo,
[DataType.InteropSyncBlockInfo] = InteropSyncBlockTypeInfo,
}).ToDictionary();
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 @@ public class MockDescriptors
]);
}
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 @@ public class MockDescriptors
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<byte> 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<byte> 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<byte> 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);

View file

@ -159,4 +159,42 @@ public unsafe class ObjectTests
}
});
}
[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);
}
});
}
}