Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Merged
merged 8 commits into from
Aug 6, 2024
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 COM data for the object if available. Returns false, if address does not represent a COM object
bool GetCOMData(TargetPointer address, out TargetPointer rcw, out TargetPointer ccw);
elinor-fung marked this conversation as resolved.
Show resolved Hide resolved
```

## 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 GetCOMData(TargetPointer address, out TargetPointer rcw, out TargetPointer ccw);
elinor-fung marked this conversation as resolved.
Show resolved Hide resolved
{
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 the methad table at offset 0
elinor-fung marked this conversation as resolved.
Show resolved Hide resolved
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);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I assume this is only for built-in. Are we going to defer ComWrappers support for later? Same for RCW.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah. I deferred for later.

I went back and forth on object contract should have

  1. GetComData for built-in and GetComWrappersData for ComWrappers
  2. GetComData for both built-in and ComWrappers
  3. Separate functions for each (trying to thing of names was a struggle) - GetRCW/CCW (built-in), GetComWrappersRCW, GetComWrappersManagedObjectWrapperList

I ended up with (1) - definitely open to thoughts.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about if we call it "BuiltInComData" to match how our docs and current code talk about built-in COM vs ComWrappers?

Then in the future, the contract to pull data about built-in RCWs and CCWs could be called "BuiltInCom" and the ComWrappers one could be "ComWrappers".

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Renamed to GetBuiltInComData

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 GetComData(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 ore sync block index (bits 0-25)
elinor-fung marked this conversation as resolved.
Show resolved Hide resolved
// - 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 GetComData(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.GetComData(objAddr, out TargetPointer rcw, out TargetPointer ccw))
{
data->RCW = rcw;
data->CCW = ccw;
}

}
catch (System.Exception ex)
Expand Down
Loading
Loading