Skip to content

Commit

Permalink
[cdac] Implement ISOSDacInterface::GetObjectData getting COM RCW/CCW (#…
Browse files Browse the repository at this point in the history
…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
  • Loading branch information
elinor-fung authored Aug 6, 2024
1 parent a5635ff commit c5a89d4
Show file tree
Hide file tree
Showing 14 changed files with 357 additions and 11 deletions.
34 changes: 34 additions & 0 deletions docs/design/datacontracts/Object.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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 |
Expand All @@ -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 |
Expand Down Expand Up @@ -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;
}
```
3 changes: 3 additions & 0 deletions src/coreclr/debug/daccess/request.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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)))
Expand All @@ -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)))
Expand Down
24 changes: 24 additions & 0 deletions src/coreclr/debug/runtimeinfo/datadescriptor.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
//
Expand Down Expand Up @@ -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)
Expand All @@ -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)
Expand Down Expand Up @@ -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()
Expand Down
26 changes: 26 additions & 0 deletions src/coreclr/vm/syncblk.h
Original file line number Diff line number Diff line change
Expand Up @@ -969,6 +969,17 @@ class InteropSyncBlockInfo
// 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;
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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;

Expand Down
3 changes: 3 additions & 0 deletions src/native/managed/cdacreader/src/Constants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down
5 changes: 4 additions & 1 deletion src/native/managed/cdacreader/src/Contracts/Object.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,11 @@ static IContract IContract.Create(Target target, int version)
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),
};
}
Expand All @@ -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
Expand Down
54 changes: 52 additions & 2 deletions src/native/managed/cdacreader/src/Contracts/Object_1.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -76,4 +93,37 @@ public TargetPointer GetArrayData(TargetPointer address, out uint count, out Tar
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;
}
}
25 changes: 25 additions & 0 deletions src/native/managed/cdacreader/src/Data/InteropSyncBlockInfo.cs
Original file line number Diff line number Diff line change
@@ -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 changes: 21 additions & 0 deletions src/native/managed/cdacreader/src/Data/SyncBlock.cs
Original file line number Diff line number Diff line change
@@ -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 changes: 21 additions & 0 deletions src/native/managed/cdacreader/src/Data/SyncTableEntry.cs
Original file line number Diff line number Diff line change
@@ -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; }
}
3 changes: 3 additions & 0 deletions src/native/managed/cdacreader/src/DataType.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,4 +42,7 @@ public enum DataType
MethodDesc,
MethodDescChunk,
Array,
SyncBlock,
SyncTableEntry,
InteropSyncBlockInfo,
}
10 changes: 7 additions & 3 deletions src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<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)
Expand Down
Loading

0 comments on commit c5a89d4

Please sign in to comment.