Skip to content

[cdac] Implement GetObjectStringData #105061

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 6 commits into from
Jul 19, 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
53 changes: 53 additions & 0 deletions docs/design/datacontracts/Object.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# Contract Object

This contract is for getting information about well-known managed objects

## APIs of contract

``` csharp
// Get the method table address for the object
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);
```

## Version 1

Data descriptors used:
| Data Descriptor Name | Field | Meaning |
| --- | --- | --- |
| `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 |
| --- | --- | --- |
| `ObjectToMethodTableUnmask` | uint8 | Bits to clear for converting to a method table address |
| `StringMethodTable` | TargetPointer | The method table for System.String |

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

string GetStringValue(TargetPointer address)
{
TargetPointer mt = GetMethodTableAddress(address);
TargetPointer stringMethodTable = target.ReadPointer(target.ReadGlobalPointer("StringMethodTable"));
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));
}
```
1 change: 1 addition & 0 deletions src/coreclr/debug/daccess/dacimpl.h
Original file line number Diff line number Diff line change
Expand Up @@ -1241,6 +1241,7 @@ class ClrDataAccess
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 GetObjectExceptionDataImpl(CLRDATA_ADDRESS objAddr, struct DacpExceptionObjectData *data);
HRESULT GetObjectStringDataImpl(CLRDATA_ADDRESS obj, unsigned int count, _Inout_updates_z_(count) WCHAR *stringData, unsigned int *pNeeded);

BOOL IsExceptionFromManagedCode(EXCEPTION_RECORD * pExceptionRecord);
#ifndef TARGET_UNIX
Expand Down
81 changes: 55 additions & 26 deletions src/coreclr/debug/daccess/request.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1601,7 +1601,7 @@ ClrDataAccess::GetDomainFromContext(CLRDATA_ADDRESS contextAddr, CLRDATA_ADDRESS


HRESULT
ClrDataAccess::GetObjectStringData(CLRDATA_ADDRESS obj, unsigned int count, _Inout_updates_z_(count) WCHAR *stringData, unsigned int *pNeeded)
ClrDataAccess::GetObjectStringData(CLRDATA_ADDRESS obj, unsigned int count, _Inout_updates_z_(count) WCHAR* stringData, unsigned int* pNeeded)
{
if (obj == 0)
return E_INVALIDARG;
Expand All @@ -1611,44 +1611,73 @@ ClrDataAccess::GetObjectStringData(CLRDATA_ADDRESS obj, unsigned int count, _Ino

SOSDacEnter();

if (m_cdacSos != NULL)
{
hr = m_cdacSos->GetObjectStringData(obj, count, stringData, pNeeded);
if (FAILED(hr))
{
hr = GetObjectStringDataImpl(obj, count, stringData, pNeeded);
}
#ifdef _DEBUG
else
{
unsigned int neededLocal;
SString stringDataLocal;
HRESULT hrLocal = GetObjectStringDataImpl(obj, count, stringDataLocal.OpenUnicodeBuffer(count), &neededLocal);
_ASSERTE(hr == hrLocal);
_ASSERTE(pNeeded == NULL || *pNeeded == neededLocal);
_ASSERTE(u16_strncmp(stringData, stringDataLocal, count) == 0);
}
#endif
}
else
{
hr = GetObjectStringDataImpl(obj, count, stringData, pNeeded);
}

SOSDacLeave();
return hr;
}

HRESULT
ClrDataAccess::GetObjectStringDataImpl(CLRDATA_ADDRESS obj, unsigned int count, _Inout_updates_z_(count) WCHAR *stringData, unsigned int *pNeeded)
{
TADDR mtTADDR = DACGetMethodTableFromObjectPointer(TO_TADDR(obj), m_pTarget);
PTR_MethodTable mt = PTR_MethodTable(mtTADDR);

// Object must be a string
BOOL bFree = FALSE;
if (!DacValidateMethodTable(mt, bFree))
hr = E_INVALIDARG;
else if (HOST_CDADDR(mt) != HOST_CDADDR(g_pStringClass))
hr = E_INVALIDARG;
return E_INVALIDARG;

if (SUCCEEDED(hr))
{
PTR_StringObject str(TO_TADDR(obj));
ULONG32 needed = (ULONG32)str->GetStringLength() + 1;
if (HOST_CDADDR(mt) != HOST_CDADDR(g_pStringClass))
return E_INVALIDARG;

if (stringData && count > 0)
{
if (count > needed)
count = needed;
PTR_StringObject str(TO_TADDR(obj));
ULONG32 needed = (ULONG32)str->GetStringLength() + 1;

TADDR pszStr = TO_TADDR(obj)+offsetof(StringObject, m_FirstChar);
hr = m_pTarget->ReadVirtual(pszStr, (PBYTE)stringData, count * sizeof(WCHAR), &needed);
HRESULT hr;
if (stringData && count > 0)
{
if (count > needed)
count = needed;

if (SUCCEEDED(hr))
stringData[count - 1] = W('\0');
else
stringData[0] = W('\0');
}
else
{
hr = E_INVALIDARG;
}
TADDR pszStr = TO_TADDR(obj)+offsetof(StringObject, m_FirstChar);
hr = m_pTarget->ReadVirtual(pszStr, (PBYTE)stringData, count * sizeof(WCHAR), &needed);

if (pNeeded)
*pNeeded = needed;
if (SUCCEEDED(hr))
stringData[count - 1] = W('\0');
else
stringData[0] = W('\0');
}
else
{
hr = E_INVALIDARG;
}

SOSDacLeave();
if (pNeeded)
*pNeeded = needed;

return hr;
}

Expand Down
1 change: 1 addition & 0 deletions src/coreclr/debug/runtimeinfo/contracts.jsonc
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
"DacStreams": 1,
"Exception": 1,
"Loader": 1,
"Object": 1,
"RuntimeTypeSystem": 1,
"Thread": 1
}
18 changes: 18 additions & 0 deletions src/coreclr/debug/runtimeinfo/datadescriptor.h
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,17 @@ CDAC_TYPE_BEGIN(GCHandle)
CDAC_TYPE_SIZE(sizeof(OBJECTHANDLE))
CDAC_TYPE_END(GCHandle)

CDAC_TYPE_BEGIN(Object)
CDAC_TYPE_INDETERMINATE(Object)
CDAC_TYPE_FIELD(Object, /*pointer*/, m_pMethTab, cdac_offsets<Object>::m_pMethTab)
CDAC_TYPE_END(Object)

CDAC_TYPE_BEGIN(String)
CDAC_TYPE_INDETERMINATE(String)
CDAC_TYPE_FIELD(String, /*pointer*/, m_FirstChar, cdac_offsets<StringObject>::m_FirstChar)
CDAC_TYPE_FIELD(String, /*uint32*/, m_StringLength, cdac_offsets<StringObject>::m_StringLength)
CDAC_TYPE_END(String)

// Loader

CDAC_TYPE_BEGIN(Module)
Expand Down Expand Up @@ -264,8 +275,15 @@ CDAC_GLOBAL(FeatureEHFunclets, uint8, 1)
#else
CDAC_GLOBAL(FeatureEHFunclets, uint8, 0)
#endif
// See Object::GetGCSafeMethodTable
#ifdef TARGET_64BIT
CDAC_GLOBAL(ObjectToMethodTableUnmask, uint8, 1 | 1 << 1 | 1 << 2)
#else
CDAC_GLOBAL(ObjectToMethodTableUnmask, uint8, 1 | 1 << 1)
#endif //TARGET_64BIT
CDAC_GLOBAL(SOSBreakingChangeVersion, uint8, SOS_BREAKING_CHANGE_VERSION)
CDAC_GLOBAL_POINTER(FreeObjectMethodTable, &::g_pFreeObjectMethodTable)
CDAC_GLOBAL_POINTER(StringMethodTable, &::g_pStringClass)
CDAC_GLOBAL_POINTER(MiniMetaDataBuffAddress, &::g_MiniMetaDataBuffAddress)
CDAC_GLOBAL_POINTER(MiniMetaDataBuffMaxSize, &::g_MiniMetaDataBuffMaxSize)
CDAC_GLOBALS_END()
Expand Down
17 changes: 17 additions & 0 deletions src/coreclr/vm/object.h
Original file line number Diff line number Diff line change
Expand Up @@ -462,6 +462,14 @@ class Object

private:
VOID ValidateInner(BOOL bDeep, BOOL bVerifyNextHeader, BOOL bVerifySyncBlock);

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

template<>
struct cdac_offsets<Object>
{
static constexpr size_t m_pMethTab = offsetof(Object, m_pMethTab);
};

/*
Expand Down Expand Up @@ -930,6 +938,15 @@ class StringObject : public Object
private:
static STRINGREF* EmptyStringRefPtr;
static bool EmptyStringIsFrozen;

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

template<>
struct cdac_offsets<StringObject>
{
static constexpr size_t m_FirstChar = offsetof(StringObject, m_FirstChar);
static constexpr size_t m_StringLength = offsetof(StringObject, m_StringLength);
};

/*================================GetEmptyString================================
Expand Down
2 changes: 2 additions & 0 deletions src/native/managed/cdacreader/src/Constants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,11 @@ internal static class Globals
internal const string GCThread = nameof(GCThread);

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

internal const string FreeObjectMethodTable = nameof(FreeObjectMethodTable);
internal const string StringMethodTable = nameof(StringMethodTable);

internal const string MiniMetaDataBuffAddress = nameof(MiniMetaDataBuffAddress);
internal const string MiniMetaDataBuffMaxSize = nameof(MiniMetaDataBuffMaxSize);
Expand Down
31 changes: 31 additions & 0 deletions src/native/managed/cdacreader/src/Contracts/Object.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;

namespace Microsoft.Diagnostics.DataContractReader.Contracts;

internal interface IObject : IContract
{
static string IContract.Name { get; } = nameof(Object);
static IContract IContract.Create(Target target, int version)
{
ulong methodTableOffset = (ulong)target.GetTypeInfo(DataType.Object).Fields["m_pMethTab"].Offset;
byte objectToMethodTableUnmask = target.ReadGlobal<byte>(Constants.Globals.ObjectToMethodTableUnmask);
TargetPointer stringMethodTable = target.ReadPointer(
target.ReadGlobalPointer(Constants.Globals.StringMethodTable));
return version switch
{
1 => new Object_1(target, methodTableOffset, objectToMethodTableUnmask, stringMethodTable),
_ => default(Object),
};
}

public virtual TargetPointer GetMethodTableAddress(TargetPointer address) => throw new NotImplementedException();
public virtual string GetStringValue(TargetPointer address) => throw new NotImplementedException();
}

internal readonly struct Object : IObject
{
// Everything throws NotImplementedException
}
41 changes: 41 additions & 0 deletions src/native/managed/cdacreader/src/Contracts/Object_1.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Runtime.InteropServices;

namespace Microsoft.Diagnostics.DataContractReader.Contracts;

internal readonly struct Object_1 : IObject
{
private readonly Target _target;
private readonly ulong _methodTableOffset;
private readonly TargetPointer _stringMethodTable;
private readonly byte _objectToMethodTableUnmask;

internal Object_1(Target target, ulong methodTableOffset, byte objectToMethodTableUnmask, TargetPointer stringMethodTable)
{
_target = target;
_methodTableOffset = methodTableOffset;
_stringMethodTable = stringMethodTable;
_objectToMethodTableUnmask = objectToMethodTableUnmask;
}

public TargetPointer GetMethodTableAddress(TargetPointer address)
{
TargetPointer mt = _target.ReadPointer(address + _methodTableOffset);
return mt.Value & (ulong)~_objectToMethodTableUnmask;
}

string IObject.GetStringValue(TargetPointer address)
{
TargetPointer mt = GetMethodTableAddress(address);
if (mt != _stringMethodTable)
Copy link
Member Author

@elinor-fung elinor-fung Jul 18, 2024

Choose a reason for hiding this comment

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

DAC also does DacValidateMethodTable (cDAC equivalent would be RuntimeTypeSystem.GetTypeHandle), but it seemed unnecessary since we check that it is the known string method table.

throw new ArgumentException("Address does not represent a string object", nameof(address));

Data.String str = _target.ProcessedData.GetOrAdd<Data.String>(address);
Span<byte> span = stackalloc byte[(int)str.StringLength * sizeof(char)];
_target.ReadBuffer(str.FirstChar, span);
return new string(MemoryMarshal.Cast<byte, char>(span));
}
}
1 change: 1 addition & 0 deletions src/native/managed/cdacreader/src/Contracts/Registry.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ public Registry(Target target)

public IException Exception => GetContract<IException>();
public ILoader Loader => GetContract<ILoader>();
public IObject Object => GetContract<IObject>();
public IThread Thread => GetContract<IThread>();
public IRuntimeTypeSystem RuntimeTypeSystem => GetContract<IRuntimeTypeSystem>();
public IDacStreams DacStreams => GetContract<IDacStreams>();
Expand Down
21 changes: 21 additions & 0 deletions src/native/managed/cdacreader/src/Data/String.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 String : IData<String>
{
static String IData<String>.Create(Target target, TargetPointer address)
=> new String(target, address);

public String(Target target, TargetPointer address)
{
Target.TypeInfo type = target.GetTypeInfo(DataType.String);

FirstChar = address + (ulong)type.Fields["m_FirstChar"].Offset;
StringLength = target.Read<uint>(address + (ulong)type.Fields["m_StringLength"].Offset);
}

public TargetPointer FirstChar { get; init; }
public uint StringLength { get; init; }
}
2 changes: 2 additions & 0 deletions src/native/managed/cdacreader/src/DataType.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,4 +37,6 @@ public enum DataType
TypeVarTypeDesc,
FnPtrTypeDesc,
DynamicMetadata,
Object,
String,
}
16 changes: 15 additions & 1 deletion src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs
Original file line number Diff line number Diff line change
Expand Up @@ -308,7 +308,21 @@ public unsafe int GetObjectExceptionData(ulong objectAddress, DacpExceptionObjec
return HResults.S_OK;
}

public unsafe int GetObjectStringData(ulong obj, uint count, char* stringData, uint* pNeeded) => HResults.E_NOTIMPL;
public unsafe int GetObjectStringData(ulong obj, uint count, char* stringData, uint* pNeeded)
{
try
{
Contracts.IObject contract = _target.Contracts.Object;
string str = contract.GetStringValue(obj);
CopyStringToTargetBuffer(stringData, count, pNeeded, str);
}
catch (System.Exception ex)
{
return ex.HResult;
}

return HResults.S_OK;
}
public unsafe int GetOOMData(ulong oomAddr, void* data) => HResults.E_NOTIMPL;
public unsafe int GetOOMStaticData(void* data) => HResults.E_NOTIMPL;
public unsafe int GetPEFileBase(ulong addr, ulong* peBase) => HResults.E_NOTIMPL;
Expand Down
Loading
Loading