Skip to content

Commit 5fd965d

Browse files
authored
[cdac] Implement GetObjectStringData (#105061)
- Include `String` and `Object` in data descriptor - Start an `Object` contract for getting information about known managed objects - Make cDAC implement `ISOSDacInterface::GetObjectStringData`
1 parent f8bcd05 commit 5fd965d

File tree

14 files changed

+427
-27
lines changed

14 files changed

+427
-27
lines changed

docs/design/datacontracts/Object.md

+53
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
# Contract Object
2+
3+
This contract is for getting information about well-known managed objects
4+
5+
## APIs of contract
6+
7+
``` csharp
8+
// Get the method table address for the object
9+
TargetPointer GetMethodTableAddress(TargetPointer address);
10+
11+
// Get the string corresponding to a managed string object. Error if address does not represent a string.
12+
string GetStringValue(TargetPointer address);
13+
```
14+
15+
## Version 1
16+
17+
Data descriptors used:
18+
| Data Descriptor Name | Field | Meaning |
19+
| --- | --- | --- |
20+
| `Object` | `m_pMethTab` | Method table for the object |
21+
| `String` | `m_FirstChar` | First character of the string - `m_StringLength` can be used to read the full string (encoded in UTF-16) |
22+
| `String` | `m_StringLength` | Length of the string in characters (encoded in UTF-16) |
23+
24+
Global variables used:
25+
| Global Name | Type | Purpose |
26+
| --- | --- | --- |
27+
| `ObjectToMethodTableUnmask` | uint8 | Bits to clear for converting to a method table address |
28+
| `StringMethodTable` | TargetPointer | The method table for System.String |
29+
30+
``` csharp
31+
TargetPointer GetMethodTableAddress(TargetPointer address)
32+
{
33+
TargetPointer mt = _targetPointer.ReadPointer(address + /* Object::m_pMethTab offset */);
34+
return mt.Value & ~target.ReadGlobal<byte>("ObjectToMethodTableUnmask");
35+
}
36+
37+
string GetStringValue(TargetPointer address)
38+
{
39+
TargetPointer mt = GetMethodTableAddress(address);
40+
TargetPointer stringMethodTable = target.ReadPointer(target.ReadGlobalPointer("StringMethodTable"));
41+
if (mt != stringMethodTable)
42+
throw new ArgumentException("Address does not represent a string object", nameof(address));
43+
44+
// Validates the method table
45+
_ = target.Contracts.RuntimeTypeSystem.GetTypeHandle(mt);
46+
47+
Data.String str = _target.ProcessedData.GetOrAdd<Data.String>(address);
48+
uint length = target.Read<uint>(address + /* String::m_StringLength offset */);
49+
Span<byte> span = stackalloc byte[(int)length * sizeof(char)];
50+
target.ReadBuffer(address + /* String::m_FirstChar offset */, span);
51+
return new string(MemoryMarshal.Cast<byte, char>(span));
52+
}
53+
```

src/coreclr/debug/daccess/dacimpl.h

+1
Original file line numberDiff line numberDiff line change
@@ -1241,6 +1241,7 @@ class ClrDataAccess
12411241
HRESULT GetMethodTableForEEClassImpl (CLRDATA_ADDRESS eeClassReallyMT, CLRDATA_ADDRESS *value);
12421242
HRESULT GetMethodTableNameImpl(CLRDATA_ADDRESS mt, unsigned int count, _Inout_updates_z_(count) WCHAR *mtName, unsigned int *pNeeded);
12431243
HRESULT GetObjectExceptionDataImpl(CLRDATA_ADDRESS objAddr, struct DacpExceptionObjectData *data);
1244+
HRESULT GetObjectStringDataImpl(CLRDATA_ADDRESS obj, unsigned int count, _Inout_updates_z_(count) WCHAR *stringData, unsigned int *pNeeded);
12441245

12451246
BOOL IsExceptionFromManagedCode(EXCEPTION_RECORD * pExceptionRecord);
12461247
#ifndef TARGET_UNIX

src/coreclr/debug/daccess/request.cpp

+55-26
Original file line numberDiff line numberDiff line change
@@ -1601,7 +1601,7 @@ ClrDataAccess::GetDomainFromContext(CLRDATA_ADDRESS contextAddr, CLRDATA_ADDRESS
16011601

16021602

16031603
HRESULT
1604-
ClrDataAccess::GetObjectStringData(CLRDATA_ADDRESS obj, unsigned int count, _Inout_updates_z_(count) WCHAR *stringData, unsigned int *pNeeded)
1604+
ClrDataAccess::GetObjectStringData(CLRDATA_ADDRESS obj, unsigned int count, _Inout_updates_z_(count) WCHAR* stringData, unsigned int* pNeeded)
16051605
{
16061606
if (obj == 0)
16071607
return E_INVALIDARG;
@@ -1611,44 +1611,73 @@ ClrDataAccess::GetObjectStringData(CLRDATA_ADDRESS obj, unsigned int count, _Ino
16111611

16121612
SOSDacEnter();
16131613

1614+
if (m_cdacSos != NULL)
1615+
{
1616+
hr = m_cdacSos->GetObjectStringData(obj, count, stringData, pNeeded);
1617+
if (FAILED(hr))
1618+
{
1619+
hr = GetObjectStringDataImpl(obj, count, stringData, pNeeded);
1620+
}
1621+
#ifdef _DEBUG
1622+
else
1623+
{
1624+
unsigned int neededLocal;
1625+
SString stringDataLocal;
1626+
HRESULT hrLocal = GetObjectStringDataImpl(obj, count, stringDataLocal.OpenUnicodeBuffer(count), &neededLocal);
1627+
_ASSERTE(hr == hrLocal);
1628+
_ASSERTE(pNeeded == NULL || *pNeeded == neededLocal);
1629+
_ASSERTE(u16_strncmp(stringData, stringDataLocal, count) == 0);
1630+
}
1631+
#endif
1632+
}
1633+
else
1634+
{
1635+
hr = GetObjectStringDataImpl(obj, count, stringData, pNeeded);
1636+
}
1637+
1638+
SOSDacLeave();
1639+
return hr;
1640+
}
1641+
1642+
HRESULT
1643+
ClrDataAccess::GetObjectStringDataImpl(CLRDATA_ADDRESS obj, unsigned int count, _Inout_updates_z_(count) WCHAR *stringData, unsigned int *pNeeded)
1644+
{
16141645
TADDR mtTADDR = DACGetMethodTableFromObjectPointer(TO_TADDR(obj), m_pTarget);
16151646
PTR_MethodTable mt = PTR_MethodTable(mtTADDR);
16161647

16171648
// Object must be a string
16181649
BOOL bFree = FALSE;
16191650
if (!DacValidateMethodTable(mt, bFree))
1620-
hr = E_INVALIDARG;
1621-
else if (HOST_CDADDR(mt) != HOST_CDADDR(g_pStringClass))
1622-
hr = E_INVALIDARG;
1651+
return E_INVALIDARG;
16231652

1624-
if (SUCCEEDED(hr))
1625-
{
1626-
PTR_StringObject str(TO_TADDR(obj));
1627-
ULONG32 needed = (ULONG32)str->GetStringLength() + 1;
1653+
if (HOST_CDADDR(mt) != HOST_CDADDR(g_pStringClass))
1654+
return E_INVALIDARG;
16281655

1629-
if (stringData && count > 0)
1630-
{
1631-
if (count > needed)
1632-
count = needed;
1656+
PTR_StringObject str(TO_TADDR(obj));
1657+
ULONG32 needed = (ULONG32)str->GetStringLength() + 1;
16331658

1634-
TADDR pszStr = TO_TADDR(obj)+offsetof(StringObject, m_FirstChar);
1635-
hr = m_pTarget->ReadVirtual(pszStr, (PBYTE)stringData, count * sizeof(WCHAR), &needed);
1659+
HRESULT hr;
1660+
if (stringData && count > 0)
1661+
{
1662+
if (count > needed)
1663+
count = needed;
16361664

1637-
if (SUCCEEDED(hr))
1638-
stringData[count - 1] = W('\0');
1639-
else
1640-
stringData[0] = W('\0');
1641-
}
1642-
else
1643-
{
1644-
hr = E_INVALIDARG;
1645-
}
1665+
TADDR pszStr = TO_TADDR(obj)+offsetof(StringObject, m_FirstChar);
1666+
hr = m_pTarget->ReadVirtual(pszStr, (PBYTE)stringData, count * sizeof(WCHAR), &needed);
16461667

1647-
if (pNeeded)
1648-
*pNeeded = needed;
1668+
if (SUCCEEDED(hr))
1669+
stringData[count - 1] = W('\0');
1670+
else
1671+
stringData[0] = W('\0');
1672+
}
1673+
else
1674+
{
1675+
hr = E_INVALIDARG;
16491676
}
16501677

1651-
SOSDacLeave();
1678+
if (pNeeded)
1679+
*pNeeded = needed;
1680+
16521681
return hr;
16531682
}
16541683

src/coreclr/debug/runtimeinfo/contracts.jsonc

+1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
"DacStreams": 1,
1313
"Exception": 1,
1414
"Loader": 1,
15+
"Object": 1,
1516
"RuntimeTypeSystem": 1,
1617
"Thread": 1
1718
}

src/coreclr/debug/runtimeinfo/datadescriptor.h

+18
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,17 @@ CDAC_TYPE_BEGIN(GCHandle)
172172
CDAC_TYPE_SIZE(sizeof(OBJECTHANDLE))
173173
CDAC_TYPE_END(GCHandle)
174174

175+
CDAC_TYPE_BEGIN(Object)
176+
CDAC_TYPE_INDETERMINATE(Object)
177+
CDAC_TYPE_FIELD(Object, /*pointer*/, m_pMethTab, cdac_offsets<Object>::m_pMethTab)
178+
CDAC_TYPE_END(Object)
179+
180+
CDAC_TYPE_BEGIN(String)
181+
CDAC_TYPE_INDETERMINATE(String)
182+
CDAC_TYPE_FIELD(String, /*pointer*/, m_FirstChar, cdac_offsets<StringObject>::m_FirstChar)
183+
CDAC_TYPE_FIELD(String, /*uint32*/, m_StringLength, cdac_offsets<StringObject>::m_StringLength)
184+
CDAC_TYPE_END(String)
185+
175186
// Loader
176187

177188
CDAC_TYPE_BEGIN(Module)
@@ -264,8 +275,15 @@ CDAC_GLOBAL(FeatureEHFunclets, uint8, 1)
264275
#else
265276
CDAC_GLOBAL(FeatureEHFunclets, uint8, 0)
266277
#endif
278+
// See Object::GetGCSafeMethodTable
279+
#ifdef TARGET_64BIT
280+
CDAC_GLOBAL(ObjectToMethodTableUnmask, uint8, 1 | 1 << 1 | 1 << 2)
281+
#else
282+
CDAC_GLOBAL(ObjectToMethodTableUnmask, uint8, 1 | 1 << 1)
283+
#endif //TARGET_64BIT
267284
CDAC_GLOBAL(SOSBreakingChangeVersion, uint8, SOS_BREAKING_CHANGE_VERSION)
268285
CDAC_GLOBAL_POINTER(FreeObjectMethodTable, &::g_pFreeObjectMethodTable)
286+
CDAC_GLOBAL_POINTER(StringMethodTable, &::g_pStringClass)
269287
CDAC_GLOBAL_POINTER(MiniMetaDataBuffAddress, &::g_MiniMetaDataBuffAddress)
270288
CDAC_GLOBAL_POINTER(MiniMetaDataBuffMaxSize, &::g_MiniMetaDataBuffMaxSize)
271289
CDAC_GLOBALS_END()

src/coreclr/vm/object.h

+17
Original file line numberDiff line numberDiff line change
@@ -462,6 +462,14 @@ class Object
462462

463463
private:
464464
VOID ValidateInner(BOOL bDeep, BOOL bVerifyNextHeader, BOOL bVerifySyncBlock);
465+
466+
template<typename T> friend struct ::cdac_offsets;
467+
};
468+
469+
template<>
470+
struct cdac_offsets<Object>
471+
{
472+
static constexpr size_t m_pMethTab = offsetof(Object, m_pMethTab);
465473
};
466474

467475
/*
@@ -930,6 +938,15 @@ class StringObject : public Object
930938
private:
931939
static STRINGREF* EmptyStringRefPtr;
932940
static bool EmptyStringIsFrozen;
941+
942+
template<typename T> friend struct ::cdac_offsets;
943+
};
944+
945+
template<>
946+
struct cdac_offsets<StringObject>
947+
{
948+
static constexpr size_t m_FirstChar = offsetof(StringObject, m_FirstChar);
949+
static constexpr size_t m_StringLength = offsetof(StringObject, m_StringLength);
933950
};
934951

935952
/*================================GetEmptyString================================

src/native/managed/cdacreader/src/Constants.cs

+2
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,11 @@ internal static class Globals
1414
internal const string GCThread = nameof(GCThread);
1515

1616
internal const string FeatureEHFunclets = nameof(FeatureEHFunclets);
17+
internal const string ObjectToMethodTableUnmask = nameof(ObjectToMethodTableUnmask);
1718
internal const string SOSBreakingChangeVersion = nameof(SOSBreakingChangeVersion);
1819

1920
internal const string FreeObjectMethodTable = nameof(FreeObjectMethodTable);
21+
internal const string StringMethodTable = nameof(StringMethodTable);
2022

2123
internal const string MiniMetaDataBuffAddress = nameof(MiniMetaDataBuffAddress);
2224
internal const string MiniMetaDataBuffMaxSize = nameof(MiniMetaDataBuffMaxSize);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System;
5+
6+
namespace Microsoft.Diagnostics.DataContractReader.Contracts;
7+
8+
internal interface IObject : IContract
9+
{
10+
static string IContract.Name { get; } = nameof(Object);
11+
static IContract IContract.Create(Target target, int version)
12+
{
13+
ulong methodTableOffset = (ulong)target.GetTypeInfo(DataType.Object).Fields["m_pMethTab"].Offset;
14+
byte objectToMethodTableUnmask = target.ReadGlobal<byte>(Constants.Globals.ObjectToMethodTableUnmask);
15+
TargetPointer stringMethodTable = target.ReadPointer(
16+
target.ReadGlobalPointer(Constants.Globals.StringMethodTable));
17+
return version switch
18+
{
19+
1 => new Object_1(target, methodTableOffset, objectToMethodTableUnmask, stringMethodTable),
20+
_ => default(Object),
21+
};
22+
}
23+
24+
public virtual TargetPointer GetMethodTableAddress(TargetPointer address) => throw new NotImplementedException();
25+
public virtual string GetStringValue(TargetPointer address) => throw new NotImplementedException();
26+
}
27+
28+
internal readonly struct Object : IObject
29+
{
30+
// Everything throws NotImplementedException
31+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System;
5+
using System.Runtime.InteropServices;
6+
7+
namespace Microsoft.Diagnostics.DataContractReader.Contracts;
8+
9+
internal readonly struct Object_1 : IObject
10+
{
11+
private readonly Target _target;
12+
private readonly ulong _methodTableOffset;
13+
private readonly TargetPointer _stringMethodTable;
14+
private readonly byte _objectToMethodTableUnmask;
15+
16+
internal Object_1(Target target, ulong methodTableOffset, byte objectToMethodTableUnmask, TargetPointer stringMethodTable)
17+
{
18+
_target = target;
19+
_methodTableOffset = methodTableOffset;
20+
_stringMethodTable = stringMethodTable;
21+
_objectToMethodTableUnmask = objectToMethodTableUnmask;
22+
}
23+
24+
public TargetPointer GetMethodTableAddress(TargetPointer address)
25+
{
26+
TargetPointer mt = _target.ReadPointer(address + _methodTableOffset);
27+
return mt.Value & (ulong)~_objectToMethodTableUnmask;
28+
}
29+
30+
string IObject.GetStringValue(TargetPointer address)
31+
{
32+
TargetPointer mt = GetMethodTableAddress(address);
33+
if (mt != _stringMethodTable)
34+
throw new ArgumentException("Address does not represent a string object", nameof(address));
35+
36+
Data.String str = _target.ProcessedData.GetOrAdd<Data.String>(address);
37+
Span<byte> span = stackalloc byte[(int)str.StringLength * sizeof(char)];
38+
_target.ReadBuffer(str.FirstChar, span);
39+
return new string(MemoryMarshal.Cast<byte, char>(span));
40+
}
41+
}

src/native/managed/cdacreader/src/Contracts/Registry.cs

+1
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ public Registry(Target target)
2020

2121
public IException Exception => GetContract<IException>();
2222
public ILoader Loader => GetContract<ILoader>();
23+
public IObject Object => GetContract<IObject>();
2324
public IThread Thread => GetContract<IThread>();
2425
public IRuntimeTypeSystem RuntimeTypeSystem => GetContract<IRuntimeTypeSystem>();
2526
public IDacStreams DacStreams => GetContract<IDacStreams>();
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
namespace Microsoft.Diagnostics.DataContractReader.Data;
5+
6+
internal sealed class String : IData<String>
7+
{
8+
static String IData<String>.Create(Target target, TargetPointer address)
9+
=> new String(target, address);
10+
11+
public String(Target target, TargetPointer address)
12+
{
13+
Target.TypeInfo type = target.GetTypeInfo(DataType.String);
14+
15+
FirstChar = address + (ulong)type.Fields["m_FirstChar"].Offset;
16+
StringLength = target.Read<uint>(address + (ulong)type.Fields["m_StringLength"].Offset);
17+
}
18+
19+
public TargetPointer FirstChar { get; init; }
20+
public uint StringLength { get; init; }
21+
}

src/native/managed/cdacreader/src/DataType.cs

+2
Original file line numberDiff line numberDiff line change
@@ -37,4 +37,6 @@ public enum DataType
3737
TypeVarTypeDesc,
3838
FnPtrTypeDesc,
3939
DynamicMetadata,
40+
Object,
41+
String,
4042
}

src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs

+15-1
Original file line numberDiff line numberDiff line change
@@ -308,7 +308,21 @@ public unsafe int GetObjectExceptionData(ulong objectAddress, DacpExceptionObjec
308308
return HResults.S_OK;
309309
}
310310

311-
public unsafe int GetObjectStringData(ulong obj, uint count, char* stringData, uint* pNeeded) => HResults.E_NOTIMPL;
311+
public unsafe int GetObjectStringData(ulong obj, uint count, char* stringData, uint* pNeeded)
312+
{
313+
try
314+
{
315+
Contracts.IObject contract = _target.Contracts.Object;
316+
string str = contract.GetStringValue(obj);
317+
CopyStringToTargetBuffer(stringData, count, pNeeded, str);
318+
}
319+
catch (System.Exception ex)
320+
{
321+
return ex.HResult;
322+
}
323+
324+
return HResults.S_OK;
325+
}
312326
public unsafe int GetOOMData(ulong oomAddr, void* data) => HResults.E_NOTIMPL;
313327
public unsafe int GetOOMStaticData(void* data) => HResults.E_NOTIMPL;
314328
public unsafe int GetPEFileBase(ulong addr, ulong* peBase) => HResults.E_NOTIMPL;

0 commit comments

Comments
 (0)