Skip to content

[cdac] Add GetArrayData to Object contract, partially implement ISOSDacInterface::GetObjectData #105534

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

Merged
merged 9 commits into from
Jul 29, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
51 changes: 46 additions & 5 deletions docs/design/datacontracts/Object.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,27 +10,38 @@ TargetPointer GetMethodTableAddress(TargetPointer address);

// Get the string corresponding to a managed string object. Error if address does not represent a string.
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);
```

## Version 1

Data descriptors used:
| Data Descriptor Name | Field | Meaning |
| --- | --- | --- |
| `Array` | `m_NumComponents` | Number of items in the array |
| `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) |

Global variables used:
| Global Name | Type | Purpose |
| --- | --- | --- |
| `ArrayBoundsZero` | TargetPointer | Known value for single dimensional, zero-lower-bound array |
| `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 |

Contracts used:
| Contract Name |
| --- |
| `RuntimeTypeSystem` |

``` csharp
TargetPointer GetMethodTableAddress(TargetPointer address)
{
TargetPointer mt = _targetPointer.ReadPointer(address + /* Object::m_pMethTab offset */);
TargetPointer mt = target.ReadPointer(address + /* Object::m_pMethTab offset */);
return mt.Value & ~target.ReadGlobal<byte>("ObjectToMethodTableUnmask");
}

Expand All @@ -41,13 +52,43 @@ string GetStringValue(TargetPointer address)
if (mt != stringMethodTable)
throw new ArgumentException("Address does not represent a string object", nameof(address));

// Validates the method table
_ = target.Contracts.RuntimeTypeSystem.GetTypeHandle(mt);

Data.String str = _target.ProcessedData.GetOrAdd<Data.String>(address);
uint length = target.Read<uint>(address + /* String::m_StringLength offset */);
Span<byte> span = stackalloc byte[(int)length * sizeof(char)];
target.ReadBuffer(address + /* String::m_FirstChar offset */, span);
return new string(MemoryMarshal.Cast<byte, char>(span));
}

TargetPointer GetArrayData(TargetPointer address, out uint count, out TargetPointer boundsStart, out TargetPointer lowerBounds)
{
TargetPointer mt = GetMethodTableAddress(address);
Contracts.IRuntimeTypeSystem rts = target.Contracts.RuntimeTypeSystem;
TypeHandle typeHandle = rts.GetTypeHandle(mt);
uint rank;
if (!rts.IsArray(typeHandle, out rank))
throw new ArgumentException("Address does not represent an array object", nameof(address));

count = target.Read<uint>(address + /* Array::m_NumComponents offset */;

CorElementType corType = rts.GetSignatureCorElementType(typeHandle);
if (corType == CorElementType.Array)
{
// Multi-dimensional - has bounds as part of the array object
// The object is allocated with:
// << fields that are part of the array type info >>
// int32_t bounds[rank];
// int32_t lowerBounds[rank];
boundsStart = address + /* Array size */;
lowerBounds = boundsStart + (rank * sizeof(int));
}
else
{
// Single-dimensional, zero-based - doesn't have bounds
boundsStart = address + /* Array::m_NumComponents offset */;
lowerBounds = _target.ReadGlobalPointer("ArrayBoundsZero");
}

// Sync block is before `this` pointer, so substract the object header size
ulong dataOffset = typeSystemContract.GetBaseSize(typeHandle) - _target.ReadGlobal<uint>("ObjectHeaderSize");
return address + dataOffset;
}
```
1 change: 1 addition & 0 deletions src/coreclr/debug/daccess/dacimpl.h
Original file line number Diff line number Diff line change
Expand Up @@ -1240,6 +1240,7 @@ class ClrDataAccess
HRESULT GetMethodTableDataImpl(CLRDATA_ADDRESS mt, struct DacpMethodTableData *data);
HRESULT GetMethodTableForEEClassImpl (CLRDATA_ADDRESS eeClassReallyMT, CLRDATA_ADDRESS *value);
HRESULT GetMethodTableNameImpl(CLRDATA_ADDRESS mt, unsigned int count, _Inout_updates_z_(count) WCHAR *mtName, unsigned int *pNeeded);
HRESULT GetObjectDataImpl(CLRDATA_ADDRESS addr, struct DacpObjectData *objectData);
HRESULT GetObjectExceptionDataImpl(CLRDATA_ADDRESS objAddr, struct DacpExceptionObjectData *data);
HRESULT GetObjectStringDataImpl(CLRDATA_ADDRESS obj, unsigned int count, _Inout_updates_z_(count) WCHAR *stringData, unsigned int *pNeeded);
HRESULT GetUsefulGlobalsImpl(struct DacpUsefulGlobalsData *globalsData);
Expand Down
177 changes: 103 additions & 74 deletions src/coreclr/debug/daccess/request.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2527,9 +2527,11 @@ ClrDataAccess::GetPEFileBase(CLRDATA_ADDRESS moduleAddr, CLRDATA_ADDRESS *base)

DWORD DACGetNumComponents(TADDR addr, ICorDebugDataTarget* target)
{
// For an object pointer, this attempts to read the number of
// array components.
addr+=sizeof(size_t);
// For an object pointer, this attempts to read the number of components.
// This expects that the first member after the MethodTable pointer (from Object)
// is a 32-bit integer representing the number of components.
// This holds for ArrayBase and StringObject - see coreclr/vm/object.h
Copy link
Member Author

Choose a reason for hiding this comment

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

Is there better / more explicit comments or doc that I could point to here?

addr += sizeof(size_t); // Method table pointer
ULONG32 returned = 0;
DWORD Value = 0;
HRESULT hr = target->ReadVirtual(addr, (PBYTE)&Value, sizeof(DWORD), &returned);
Expand All @@ -2542,115 +2544,142 @@ DWORD DACGetNumComponents(TADDR addr, ICorDebugDataTarget* target)
}

HRESULT
ClrDataAccess::GetObjectData(CLRDATA_ADDRESS addr, struct DacpObjectData *objectData)
ClrDataAccess::GetObjectData(CLRDATA_ADDRESS addr, struct DacpObjectData* objectData)
{
if (addr == 0 || objectData == NULL)
return E_INVALIDARG;

SOSDacEnter();

ZeroMemory (objectData, sizeof(DacpObjectData));
if (m_cdacSos != NULL)
{
hr = m_cdacSos->GetObjectData(addr, objectData);
if (FAILED(hr))
{
hr = GetObjectDataImpl(addr, objectData);
}
#ifdef _DEBUG
else
{
DacpObjectData objectDataLocal;
HRESULT hrLocal = GetObjectDataImpl(addr, &objectDataLocal);
_ASSERTE(hr == hrLocal);
_ASSERTE(objectData->MethodTable == objectDataLocal.MethodTable);
_ASSERTE(objectData->ObjectType == objectDataLocal.ObjectType);
_ASSERTE(objectData->Size == objectDataLocal.Size);
_ASSERTE(objectData->ElementTypeHandle == objectDataLocal.ElementTypeHandle);
_ASSERTE(objectData->ElementType == objectDataLocal.ElementType);
_ASSERTE(objectData->dwRank == objectDataLocal.dwRank);
_ASSERTE(objectData->dwNumComponents == objectDataLocal.dwNumComponents);
_ASSERTE(objectData->dwComponentSize == objectDataLocal.dwComponentSize);
_ASSERTE(objectData->ArrayDataPtr == objectDataLocal.ArrayDataPtr);
_ASSERTE(objectData->ArrayBoundsPtr == objectDataLocal.ArrayBoundsPtr);
_ASSERTE(objectData->ArrayLowerBoundsPtr == objectDataLocal.ArrayLowerBoundsPtr);
_ASSERTE(objectData->RCW == objectDataLocal.RCW);
_ASSERTE(objectData->CCW == objectDataLocal.CCW);
}
#endif
}
else
{
hr = GetObjectDataImpl(addr, objectData);
}

SOSDacLeave();
return hr;
}

HRESULT
ClrDataAccess::GetObjectDataImpl(CLRDATA_ADDRESS addr, struct DacpObjectData *objectData)
{
TADDR mtTADDR = DACGetMethodTableFromObjectPointer(CLRDATA_ADDRESS_TO_TADDR(addr),m_pTarget);
if (mtTADDR==(TADDR)NULL)
hr = E_INVALIDARG;
return E_INVALIDARG;

BOOL bFree = FALSE;
PTR_MethodTable mt = NULL;
if (SUCCEEDED(hr))
PTR_MethodTable mt = PTR_MethodTable(mtTADDR);
if (!DacValidateMethodTable(mt, bFree))
return E_INVALIDARG;

objectData->MethodTable = HOST_CDADDR(mt);
objectData->Size = mt->GetBaseSize();
if (mt->GetComponentSize())
{
mt = PTR_MethodTable(mtTADDR);
if (!DacValidateMethodTable(mt, bFree))
hr = E_INVALIDARG;
objectData->Size += (DACGetNumComponents(CLRDATA_ADDRESS_TO_TADDR(addr),m_pTarget) * mt->GetComponentSize());
objectData->dwComponentSize = mt->GetComponentSize();
}

if (SUCCEEDED(hr))
if (bFree)
{
objectData->MethodTable = HOST_CDADDR(mt);
objectData->Size = mt->GetBaseSize();
if (mt->GetComponentSize())
objectData->ObjectType = OBJ_FREE;
}
else
{
if (objectData->MethodTable == HOST_CDADDR(g_pStringClass))
{
objectData->Size += (DACGetNumComponents(CLRDATA_ADDRESS_TO_TADDR(addr),m_pTarget) * mt->GetComponentSize());
objectData->dwComponentSize = mt->GetComponentSize();
objectData->ObjectType = OBJ_STRING;
}

if (bFree)
else if (objectData->MethodTable == HOST_CDADDR(g_pObjectClass))
{
objectData->ObjectType = OBJ_FREE;
objectData->ObjectType = OBJ_OBJECT;
}
else
else if (mt->IsArray())
{
if (objectData->MethodTable == HOST_CDADDR(g_pStringClass))
{
objectData->ObjectType = OBJ_STRING;
}
else if (objectData->MethodTable == HOST_CDADDR(g_pObjectClass))
{
objectData->ObjectType = OBJ_OBJECT;
}
else if (mt->IsArray())
{
objectData->ObjectType = OBJ_ARRAY;
objectData->ObjectType = OBJ_ARRAY;

// For now, go ahead and instantiate array classes.
// TODO: avoid instantiating even object Arrays in the host.
// NOTE: This code is carefully written to deal with MethodTable fields
// in the array object having the mark bit set (because we may
// be in mark phase when this function is called).
ArrayBase *pArrayObj = PTR_ArrayBase(TO_TADDR(addr));
objectData->ElementType = mt->GetArrayElementType();
// For now, go ahead and instantiate array classes.
// TODO: avoid instantiating even object Arrays in the host.
// NOTE: This code is carefully written to deal with MethodTable fields
// in the array object having the mark bit set (because we may
// be in mark phase when this function is called).
ArrayBase *pArrayObj = PTR_ArrayBase(TO_TADDR(addr));
objectData->ElementType = mt->GetArrayElementType();

TypeHandle thElem = mt->GetArrayElementTypeHandle();
TypeHandle thElem = mt->GetArrayElementTypeHandle();

TypeHandle thCur = thElem;
while (thCur.IsArray())
thCur = thCur.GetArrayElementTypeHandle();
TypeHandle thCur = thElem;
while (thCur.IsArray())
thCur = thCur.GetArrayElementTypeHandle();

TADDR mtCurTADDR = thCur.AsTAddr();
if (!DacValidateMethodTable(PTR_MethodTable(mtCurTADDR), bFree))
{
hr = E_INVALIDARG;
}
else
{
objectData->ElementTypeHandle = (CLRDATA_ADDRESS)(thElem.AsTAddr());
objectData->dwRank = mt->GetRank();
objectData->dwNumComponents = pArrayObj->GetNumComponents ();
objectData->ArrayDataPtr = PTR_CDADDR(pArrayObj->GetDataPtr (TRUE));
objectData->ArrayBoundsPtr = HOST_CDADDR(pArrayObj->GetBoundsPtr());
objectData->ArrayLowerBoundsPtr = HOST_CDADDR(pArrayObj->GetLowerBoundsPtr());
}
}
else
TADDR mtCurTADDR = thCur.AsTAddr();
if (!DacValidateMethodTable(PTR_MethodTable(mtCurTADDR), bFree))
{
objectData->ObjectType = OBJ_OTHER;
return E_INVALIDARG;
}

objectData->ElementTypeHandle = (CLRDATA_ADDRESS)(thElem.AsTAddr());
objectData->dwRank = mt->GetRank();
objectData->dwNumComponents = pArrayObj->GetNumComponents ();
objectData->ArrayDataPtr = PTR_CDADDR(pArrayObj->GetDataPtr (TRUE));
objectData->ArrayBoundsPtr = HOST_CDADDR(pArrayObj->GetBoundsPtr());
objectData->ArrayLowerBoundsPtr = HOST_CDADDR(pArrayObj->GetLowerBoundsPtr());
}
else
{
objectData->ObjectType = OBJ_OTHER;
}
}

#ifdef FEATURE_COMINTEROP
if (SUCCEEDED(hr))
EX_TRY_ALLOW_DATATARGET_MISSING_MEMORY
{
EX_TRY_ALLOW_DATATARGET_MISSING_MEMORY
PTR_SyncBlock pSyncBlk = DACGetSyncBlockFromObjectPointer(CLRDATA_ADDRESS_TO_TADDR(addr), m_pTarget);
if (pSyncBlk != NULL)
{
PTR_SyncBlock pSyncBlk = DACGetSyncBlockFromObjectPointer(CLRDATA_ADDRESS_TO_TADDR(addr), m_pTarget);
if (pSyncBlk != NULL)
// see if we have an RCW and/or CCW associated with this object
PTR_InteropSyncBlockInfo pInfo = pSyncBlk->GetInteropInfoNoCreate();
if (pInfo != NULL)
{
// see if we have an RCW and/or CCW associated with this object
PTR_InteropSyncBlockInfo pInfo = pSyncBlk->GetInteropInfoNoCreate();
if (pInfo != NULL)
{
objectData->RCW = TO_CDADDR(pInfo->DacGetRawRCW());
objectData->CCW = HOST_CDADDR(pInfo->GetCCW());
}
objectData->RCW = TO_CDADDR(pInfo->DacGetRawRCW());
objectData->CCW = HOST_CDADDR(pInfo->GetCCW());
}
}
EX_END_CATCH_ALLOW_DATATARGET_MISSING_MEMORY;
}
EX_END_CATCH_ALLOW_DATATARGET_MISSING_MEMORY;
#endif // FEATURE_COMINTEROP

SOSDacLeave();

return hr;
return S_OK;
}

HRESULT ClrDataAccess::GetAppDomainList(unsigned int count, CLRDATA_ADDRESS values[], unsigned int *fetched)
Expand Down
12 changes: 12 additions & 0 deletions src/coreclr/debug/runtimeinfo/datadescriptor.h
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,11 @@ CDAC_TYPE_FIELD(String, /*pointer*/, m_FirstChar, cdac_offsets<StringObject>::m_
CDAC_TYPE_FIELD(String, /*uint32*/, m_StringLength, cdac_offsets<StringObject>::m_StringLength)
CDAC_TYPE_END(String)

CDAC_TYPE_BEGIN(Array)
CDAC_TYPE_SIZE(sizeof(ArrayBase))
CDAC_TYPE_FIELD(Array, /*pointer*/, m_NumComponents, cdac_offsets<ArrayBase>::m_NumComponents)
CDAC_TYPE_END(Array)

// Loader

CDAC_TYPE_BEGIN(Module)
Expand Down Expand Up @@ -291,6 +296,11 @@ CDAC_GLOBAL(FeatureEHFunclets, uint8, 1)
#else
CDAC_GLOBAL(FeatureEHFunclets, uint8, 0)
#endif
#if FEATURE_COMINTEROP
CDAC_GLOBAL(FeatureCOMInterop, uint8, 1)
#else
CDAC_GLOBAL(FeatureCOMInterop, uint8, 0)
#endif
// See Object::GetGCSafeMethodTable
#ifdef TARGET_64BIT
CDAC_GLOBAL(ObjectToMethodTableUnmask, uint8, 1 | 1 << 1 | 1 << 2)
Expand All @@ -299,6 +309,8 @@ CDAC_GLOBAL(ObjectToMethodTableUnmask, uint8, 1 | 1 << 1)
#endif //TARGET_64BIT
CDAC_GLOBAL(SOSBreakingChangeVersion, uint8, SOS_BREAKING_CHANGE_VERSION)
CDAC_GLOBAL(MethodDescAlignment, uint64, MethodDesc::ALIGNMENT)
CDAC_GLOBAL(ObjectHeaderSize, uint64, OBJHEADER_SIZE)
CDAC_GLOBAL_POINTER(ArrayBoundsZero, cdac_offsets<ArrayBase>::s_arrayBoundsZero)
CDAC_GLOBAL_POINTER(ExceptionMethodTable, &::g_pExceptionClass)
CDAC_GLOBAL_POINTER(FreeObjectMethodTable, &::g_pFreeObjectMethodTable)
CDAC_GLOBAL_POINTER(ObjectMethodTable, &::g_pObjectClass)
Expand Down
12 changes: 12 additions & 0 deletions src/coreclr/vm/object.h
Original file line number Diff line number Diff line change
Expand Up @@ -638,8 +638,20 @@ class ArrayBase : public Object

inline static unsigned GetBoundsOffset(MethodTable* pMT);
inline static unsigned GetLowerBoundsOffset(MethodTable* pMT);

template<typename T> friend struct ::cdac_offsets;
};

#ifndef DACCESS_COMPILE
template<>
struct cdac_offsets<ArrayBase>
{
static constexpr size_t m_NumComponents = offsetof(ArrayBase, m_NumComponents);

static constexpr INT32* s_arrayBoundsZero = &ArrayBase::s_arrayBoundsZero;
Copy link
Member Author

Choose a reason for hiding this comment

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

I put this in here to expose the static without just making it public, but it is a bit weird to be under cdac_offsets. Do we want to rename offsets to something more general about cdac info? Or we could add something like a separate template<typename T> struct cdac_globals.

Copy link
Member

Choose a reason for hiding this comment

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

I think we should rename it. in #104999 Jeremy is adding some cdac_offsets<Foo>::NestedStructSize constants because Foo has a private NestedStruct.

We could just name it cdac<T> or data_contract<T>

Copy link
Member Author

Choose a reason for hiding this comment

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

Will rename to cdac<T> in a follow-up.

};
#endif

//
// Template used to build all the non-object
// arrays of a single dimension
Expand Down
5 changes: 5 additions & 0 deletions src/native/managed/cdacreader/src/Constants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@ internal static class Globals
internal const string FinalizerThread = nameof(FinalizerThread);
internal const string GCThread = nameof(GCThread);

internal const string FeatureCOMInterop = nameof(FeatureCOMInterop);
internal const string FeatureEHFunclets = nameof(FeatureEHFunclets);

internal const string ObjectToMethodTableUnmask = nameof(ObjectToMethodTableUnmask);
internal const string SOSBreakingChangeVersion = nameof(SOSBreakingChangeVersion);

Expand All @@ -27,5 +29,8 @@ internal static class Globals
internal const string MiniMetaDataBuffMaxSize = nameof(MiniMetaDataBuffMaxSize);

internal const string MethodDescAlignment = nameof(MethodDescAlignment);
internal const string ObjectHeaderSize = nameof(ObjectHeaderSize);

internal const string ArrayBoundsZero = nameof(ArrayBoundsZero);
}
}
Loading
Loading