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:
parent
a5635ff00c
commit
c5a89d4429
14 changed files with 360 additions and 14 deletions
|
@ -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.
|
// 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);
|
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
|
## Version 1
|
||||||
|
@ -21,9 +24,13 @@ Data descriptors used:
|
||||||
| Data Descriptor Name | Field | Meaning |
|
| Data Descriptor Name | Field | Meaning |
|
||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
| `Array` | `m_NumComponents` | Number of items in the array |
|
| `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 |
|
| `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_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) |
|
| `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 variables used:
|
||||||
| Global Name | Type | Purpose |
|
| Global Name | Type | Purpose |
|
||||||
|
@ -32,6 +39,8 @@ Global variables used:
|
||||||
| `ObjectHeaderSize` | uint32 | Size of the object header (sync block and alignment) |
|
| `ObjectHeaderSize` | uint32 | Size of the object header (sync block and alignment) |
|
||||||
| `ObjectToMethodTableUnmask` | uint8 | Bits to clear for converting to a method table address |
|
| `ObjectToMethodTableUnmask` | uint8 | Bits to clear for converting to a method table address |
|
||||||
| `StringMethodTable` | TargetPointer | The method table for System.String |
|
| `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:
|
Contracts used:
|
||||||
| Contract Name |
|
| Contract Name |
|
||||||
|
@ -91,4 +100,29 @@ TargetPointer GetArrayData(TargetPointer address, out uint count, out TargetPoin
|
||||||
ulong dataOffset = typeSystemContract.GetBaseSize(typeHandle) - _target.ReadGlobal<uint>("ObjectHeaderSize");
|
ulong dataOffset = typeSystemContract.GetBaseSize(typeHandle) - _target.ReadGlobal<uint>("ObjectHeaderSize");
|
||||||
return address + dataOffset;
|
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;
|
||||||
|
}
|
||||||
```
|
```
|
||||||
|
|
|
@ -71,6 +71,7 @@ TADDR DACGetMethodTableFromObjectPointer(TADDR objAddr, ICorDebugDataTarget * ta
|
||||||
ULONG32 returned = 0;
|
ULONG32 returned = 0;
|
||||||
TADDR Value = (TADDR)NULL;
|
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);
|
HRESULT hr = target->ReadVirtual(objAddr, (PBYTE)&Value, sizeof(TADDR), &returned);
|
||||||
|
|
||||||
if ((hr != S_OK) || (returned != sizeof(TADDR)))
|
if ((hr != S_OK) || (returned != sizeof(TADDR)))
|
||||||
|
@ -92,6 +93,8 @@ PTR_SyncBlock DACGetSyncBlockFromObjectPointer(TADDR objAddr, ICorDebugDataTarge
|
||||||
ULONG32 returned = 0;
|
ULONG32 returned = 0;
|
||||||
DWORD Value = 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);
|
HRESULT hr = target->ReadVirtual(objAddr - sizeof(DWORD), (PBYTE)&Value, sizeof(DWORD), &returned);
|
||||||
|
|
||||||
if ((hr != S_OK) || (returned != sizeof(DWORD)))
|
if ((hr != S_OK) || (returned != sizeof(DWORD)))
|
||||||
|
|
|
@ -41,6 +41,8 @@
|
||||||
// then the field layout can be specified as
|
// then the field layout can be specified as
|
||||||
// CDAC_TYPE_FIELD(MyClassLayout, pointer, MyField, cdac_data<MyClass>::MyField)
|
// CDAC_TYPE_FIELD(MyClassLayout, pointer, MyField, cdac_data<MyClass>::MyField)
|
||||||
// There can be zero or more CDAC_TYPE_FIELD entries per type layout
|
// 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
|
// 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_SIZE(sizeof(OBJECTHANDLE))
|
||||||
CDAC_TYPE_END(GCHandle)
|
CDAC_TYPE_END(GCHandle)
|
||||||
|
|
||||||
|
// Object
|
||||||
|
|
||||||
CDAC_TYPE_BEGIN(Object)
|
CDAC_TYPE_BEGIN(Object)
|
||||||
CDAC_TYPE_INDETERMINATE(Object)
|
CDAC_TYPE_INDETERMINATE(Object)
|
||||||
CDAC_TYPE_FIELD(Object, /*pointer*/, m_pMethTab, cdac_data<Object>::m_pMethTab)
|
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_FIELD(Array, /*pointer*/, m_NumComponents, cdac_data<ArrayBase>::m_NumComponents)
|
||||||
CDAC_TYPE_END(Array)
|
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
|
// Loader
|
||||||
|
|
||||||
CDAC_TYPE_BEGIN(Module)
|
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(SOSBreakingChangeVersion, uint8, SOS_BREAKING_CHANGE_VERSION)
|
||||||
CDAC_GLOBAL(MethodDescAlignment, uint64, MethodDesc::ALIGNMENT)
|
CDAC_GLOBAL(MethodDescAlignment, uint64, MethodDesc::ALIGNMENT)
|
||||||
CDAC_GLOBAL(ObjectHeaderSize, uint64, OBJHEADER_SIZE)
|
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(ArrayBoundsZero, cdac_data<ArrayBase>::ArrayBoundsZero)
|
||||||
CDAC_GLOBAL_POINTER(ExceptionMethodTable, &::g_pExceptionClass)
|
CDAC_GLOBAL_POINTER(ExceptionMethodTable, &::g_pExceptionClass)
|
||||||
CDAC_GLOBAL_POINTER(FreeObjectMethodTable, &::g_pFreeObjectMethodTable)
|
CDAC_GLOBAL_POINTER(FreeObjectMethodTable, &::g_pFreeObjectMethodTable)
|
||||||
CDAC_GLOBAL_POINTER(ObjectMethodTable, &::g_pObjectClass)
|
CDAC_GLOBAL_POINTER(ObjectMethodTable, &::g_pObjectClass)
|
||||||
CDAC_GLOBAL_POINTER(ObjectArrayMethodTable, &::g_pPredefinedArrayTypes[ELEMENT_TYPE_OBJECT])
|
CDAC_GLOBAL_POINTER(ObjectArrayMethodTable, &::g_pPredefinedArrayTypes[ELEMENT_TYPE_OBJECT])
|
||||||
CDAC_GLOBAL_POINTER(StringMethodTable, &::g_pStringClass)
|
CDAC_GLOBAL_POINTER(StringMethodTable, &::g_pStringClass)
|
||||||
|
CDAC_GLOBAL_POINTER(SyncTableEntries, &::g_pSyncTable)
|
||||||
CDAC_GLOBAL_POINTER(MiniMetaDataBuffAddress, &::g_MiniMetaDataBuffAddress)
|
CDAC_GLOBAL_POINTER(MiniMetaDataBuffAddress, &::g_MiniMetaDataBuffAddress)
|
||||||
CDAC_GLOBAL_POINTER(MiniMetaDataBuffMaxSize, &::g_MiniMetaDataBuffMaxSize)
|
CDAC_GLOBAL_POINTER(MiniMetaDataBuffMaxSize, &::g_MiniMetaDataBuffMaxSize)
|
||||||
CDAC_GLOBALS_END()
|
CDAC_GLOBALS_END()
|
||||||
|
|
|
@ -969,6 +969,17 @@ private:
|
||||||
// ObjectiveCMarshal.NativeAot.cs
|
// ObjectiveCMarshal.NativeAot.cs
|
||||||
BYTE m_taggedAlloc[2 * sizeof(void*)];
|
BYTE m_taggedAlloc[2 * sizeof(void*)];
|
||||||
#endif // FEATURE_OBJCMARSHAL
|
#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;
|
typedef DPTR(InteropSyncBlockInfo) PTR_InteropSyncBlockInfo;
|
||||||
|
@ -1274,6 +1285,14 @@ class SyncBlock
|
||||||
return m_Monitor.GetPtrForLockContract();
|
return m_Monitor.GetPtrForLockContract();
|
||||||
}
|
}
|
||||||
#endif // defined(ENABLE_CONTRACTS_IMPL)
|
#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
|
class SyncTableEntry
|
||||||
|
@ -1654,8 +1673,15 @@ class ObjHeader
|
||||||
void ReleaseSpinLock();
|
void ReleaseSpinLock();
|
||||||
|
|
||||||
BOOL Validate (BOOL bVerifySyncBlkIndex = TRUE);
|
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;
|
typedef DPTR(class ObjHeader) PTR_ObjHeader;
|
||||||
|
|
||||||
|
|
|
@ -30,6 +30,9 @@ internal static class Constants
|
||||||
|
|
||||||
internal const string MethodDescAlignment = nameof(MethodDescAlignment);
|
internal const string MethodDescAlignment = nameof(MethodDescAlignment);
|
||||||
internal const string ObjectHeaderSize = nameof(ObjectHeaderSize);
|
internal const string ObjectHeaderSize = nameof(ObjectHeaderSize);
|
||||||
|
internal const string SyncBlockValueToObjectOffset = nameof(SyncBlockValueToObjectOffset);
|
||||||
|
|
||||||
|
internal const string SyncTableEntries = nameof(SyncTableEntries);
|
||||||
|
|
||||||
internal const string ArrayBoundsZero = nameof(ArrayBoundsZero);
|
internal const string ArrayBoundsZero = nameof(ArrayBoundsZero);
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,9 +14,11 @@ internal interface IObject : IContract
|
||||||
byte objectToMethodTableUnmask = target.ReadGlobal<byte>(Constants.Globals.ObjectToMethodTableUnmask);
|
byte objectToMethodTableUnmask = target.ReadGlobal<byte>(Constants.Globals.ObjectToMethodTableUnmask);
|
||||||
TargetPointer stringMethodTable = target.ReadPointer(
|
TargetPointer stringMethodTable = target.ReadPointer(
|
||||||
target.ReadGlobalPointer(Constants.Globals.StringMethodTable));
|
target.ReadGlobalPointer(Constants.Globals.StringMethodTable));
|
||||||
|
TargetPointer syncTableEntries = target.ReadPointer(
|
||||||
|
target.ReadGlobalPointer(Constants.Globals.SyncTableEntries));
|
||||||
return version switch
|
return version switch
|
||||||
{
|
{
|
||||||
1 => new Object_1(target, methodTableOffset, objectToMethodTableUnmask, stringMethodTable),
|
1 => new Object_1(target, methodTableOffset, objectToMethodTableUnmask, stringMethodTable, syncTableEntries),
|
||||||
_ => default(Object),
|
_ => default(Object),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -25,6 +27,7 @@ internal interface IObject : IContract
|
||||||
|
|
||||||
public virtual string GetStringValue(TargetPointer address) => throw new NotImplementedException();
|
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 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
|
internal readonly struct Object : IObject
|
||||||
|
|
|
@ -11,15 +11,32 @@ internal readonly struct Object_1 : IObject
|
||||||
{
|
{
|
||||||
private readonly Target _target;
|
private readonly Target _target;
|
||||||
private readonly ulong _methodTableOffset;
|
private readonly ulong _methodTableOffset;
|
||||||
private readonly TargetPointer _stringMethodTable;
|
|
||||||
private readonly byte _objectToMethodTableUnmask;
|
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;
|
_target = target;
|
||||||
_methodTableOffset = methodTableOffset;
|
_methodTableOffset = methodTableOffset;
|
||||||
_stringMethodTable = stringMethodTable;
|
_stringMethodTable = stringMethodTable;
|
||||||
_objectToMethodTableUnmask = objectToMethodTableUnmask;
|
_objectToMethodTableUnmask = objectToMethodTableUnmask;
|
||||||
|
_syncTableEntries = syncTableEntries;
|
||||||
}
|
}
|
||||||
|
|
||||||
public TargetPointer GetMethodTableAddress(TargetPointer address)
|
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);
|
ulong dataOffset = typeSystemContract.GetBaseSize(typeHandle) - _target.ReadGlobal<uint>(Constants.Globals.ObjectHeaderSize);
|
||||||
return address + dataOffset;
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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; }
|
||||||
|
}
|
21
src/native/managed/cdacreader/src/Data/SyncBlock.cs
Normal file
21
src/native/managed/cdacreader/src/Data/SyncBlock.cs
Normal 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; }
|
||||||
|
}
|
21
src/native/managed/cdacreader/src/Data/SyncTableEntry.cs
Normal file
21
src/native/managed/cdacreader/src/Data/SyncTableEntry.cs
Normal 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; }
|
||||||
|
}
|
|
@ -42,4 +42,7 @@ public enum DataType
|
||||||
MethodDesc,
|
MethodDesc,
|
||||||
MethodDescChunk,
|
MethodDescChunk,
|
||||||
Array,
|
Array,
|
||||||
|
SyncBlock,
|
||||||
|
SyncTableEntry,
|
||||||
|
InteropSyncBlockInfo,
|
||||||
}
|
}
|
||||||
|
|
|
@ -374,9 +374,13 @@ internal sealed partial class SOSDacImpl : ISOSDacInterface, ISOSDacInterface2,
|
||||||
data->ObjectType = DacpObjectType.OBJ_OTHER;
|
data->ObjectType = DacpObjectType.OBJ_OTHER;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: [cdac] Get RCW and CCW from interop info on sync block
|
// Populate COM data if this is a COM object
|
||||||
if (_target.ReadGlobal<byte>(Constants.Globals.FeatureCOMInterop) != 0)
|
if (_target.ReadGlobal<byte>(Constants.Globals.FeatureCOMInterop) != 0
|
||||||
return HResults.E_NOTIMPL;
|
&& objectContract.GetBuiltInComData(objAddr, out TargetPointer rcw, out TargetPointer ccw))
|
||||||
|
{
|
||||||
|
data->RCW = rcw;
|
||||||
|
data->CCW = ccw;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
catch (System.Exception ex)
|
catch (System.Exception ex)
|
||||||
|
|
|
@ -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
|
public static class RuntimeTypeSystem
|
||||||
{
|
{
|
||||||
internal const ulong TestFreeObjectMethodTableGlobalAddress = 0x00000000_7a0000a0;
|
internal const ulong TestFreeObjectMethodTableGlobalAddress = 0x00000000_7a0000a0;
|
||||||
|
@ -144,30 +166,42 @@ public class MockDescriptors
|
||||||
|
|
||||||
public static class Object
|
public static class Object
|
||||||
{
|
{
|
||||||
const ulong TestStringMethodTableGlobalAddress = 0x00000000_100000a0;
|
private const ulong TestStringMethodTableGlobalAddress = 0x00000000_100000a0;
|
||||||
const ulong TestStringMethodTableAddress = 0x00000000_100000a8;
|
private const ulong TestStringMethodTableAddress = 0x00000000_100000a8;
|
||||||
internal const ulong TestArrayBoundsZeroGlobalAddress = 0x00000000_100000b0;
|
internal const ulong TestArrayBoundsZeroGlobalAddress = 0x00000000_100000b0;
|
||||||
|
|
||||||
internal static Dictionary<DataType, Target.TypeInfo> Types(TargetTestHelpers helpers) => RuntimeTypeSystem.Types.Concat(
|
private const ulong TestSyncTableEntriesGlobalAddress = 0x00000000_100000c0;
|
||||||
new Dictionary<DataType, Target.TypeInfo>(){
|
private const ulong TestSyncTableEntriesAddress = 0x00000000_f0000000;
|
||||||
[DataType.Object] = ObjectTypeInfo,
|
|
||||||
[DataType.String] = StringTypeInfo,
|
|
||||||
[DataType.Array] = ArrayTypeInfo with { Size = helpers.ArrayBaseSize }
|
|
||||||
}).ToDictionary();
|
|
||||||
|
|
||||||
internal const ulong TestObjectToMethodTableUnmask = 0x7;
|
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(
|
internal static (string Name, ulong Value, string? Type)[] Globals(TargetTestHelpers helpers) => RuntimeTypeSystem.Globals.Concat(
|
||||||
[
|
[
|
||||||
(nameof(Constants.Globals.ObjectToMethodTableUnmask), TestObjectToMethodTableUnmask, "uint8"),
|
(nameof(Constants.Globals.ObjectToMethodTableUnmask), TestObjectToMethodTableUnmask, "uint8"),
|
||||||
(nameof(Constants.Globals.StringMethodTable), TestStringMethodTableGlobalAddress, null),
|
(nameof(Constants.Globals.StringMethodTable), TestStringMethodTableGlobalAddress, null),
|
||||||
(nameof(Constants.Globals.ArrayBoundsZero), TestArrayBoundsZeroGlobalAddress, null),
|
(nameof(Constants.Globals.ArrayBoundsZero), TestArrayBoundsZeroGlobalAddress, null),
|
||||||
|
(nameof(Constants.Globals.SyncTableEntries), TestSyncTableEntriesGlobalAddress, null),
|
||||||
(nameof(Constants.Globals.ObjectHeaderSize), helpers.ObjHeaderSize, "uint32"),
|
(nameof(Constants.Globals.ObjectHeaderSize), helpers.ObjHeaderSize, "uint32"),
|
||||||
|
(nameof(Constants.Globals.SyncBlockValueToObjectOffset), TestSyncBlockValueToObjectOffset, "uint16"),
|
||||||
]).ToArray();
|
]).ToArray();
|
||||||
|
|
||||||
internal static MockMemorySpace.Builder AddGlobalPointers(TargetTestHelpers targetTestHelpers, MockMemorySpace.Builder builder)
|
internal static MockMemorySpace.Builder AddGlobalPointers(TargetTestHelpers targetTestHelpers, MockMemorySpace.Builder builder)
|
||||||
{
|
{
|
||||||
builder = RuntimeTypeSystem.AddGlobalPointers(targetTestHelpers, builder);
|
builder = RuntimeTypeSystem.AddGlobalPointers(targetTestHelpers, builder);
|
||||||
builder = AddStringMethodTablePointer(targetTestHelpers, builder);
|
builder = AddStringMethodTablePointer(targetTestHelpers, builder);
|
||||||
|
builder = AddSyncTableEntriesPointer(targetTestHelpers, builder);
|
||||||
return 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)
|
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)] };
|
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);
|
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)
|
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);
|
int size = targetTestHelpers.SizeOfTypeInfo(ObjectTypeInfo) + targetTestHelpers.SizeOfTypeInfo(StringTypeInfo) + value.Length * sizeof(char);
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue