1
0
Fork 0
mirror of https://github.com/VSadov/Satori.git synced 2025-06-08 03:27:04 +09:00

[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`
This commit is contained in:
Elinor Fung 2024-07-19 06:25:47 -07:00 committed by GitHub
parent f8bcd05a73
commit 5fd965d7bf
Signed by: github
GPG key ID: B5690EEEBB952194
14 changed files with 427 additions and 27 deletions

View file

@ -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));
}
```

View file

@ -1241,6 +1241,7 @@ public:
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

View file

@ -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;
@ -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))
if (HOST_CDADDR(mt) != HOST_CDADDR(g_pStringClass))
return E_INVALIDARG;
PTR_StringObject str(TO_TADDR(obj));
ULONG32 needed = (ULONG32)str->GetStringLength() + 1;
HRESULT hr;
if (stringData && count > 0)
{
PTR_StringObject str(TO_TADDR(obj));
ULONG32 needed = (ULONG32)str->GetStringLength() + 1;
if (count > needed)
count = needed;
if (stringData && count > 0)
{
if (count > needed)
count = needed;
TADDR pszStr = TO_TADDR(obj)+offsetof(StringObject, m_FirstChar);
hr = m_pTarget->ReadVirtual(pszStr, (PBYTE)stringData, count * sizeof(WCHAR), &needed);
TADDR pszStr = TO_TADDR(obj)+offsetof(StringObject, m_FirstChar);
hr = m_pTarget->ReadVirtual(pszStr, (PBYTE)stringData, count * sizeof(WCHAR), &needed);
if (SUCCEEDED(hr))
stringData[count - 1] = W('\0');
else
stringData[0] = W('\0');
}
if (SUCCEEDED(hr))
stringData[count - 1] = W('\0');
else
{
hr = E_INVALIDARG;
}
if (pNeeded)
*pNeeded = needed;
stringData[0] = W('\0');
}
else
{
hr = E_INVALIDARG;
}
SOSDacLeave();
if (pNeeded)
*pNeeded = needed;
return hr;
}

View file

@ -12,6 +12,7 @@
"DacStreams": 1,
"Exception": 1,
"Loader": 1,
"Object": 1,
"RuntimeTypeSystem": 1,
"Thread": 1
}

View file

@ -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)
@ -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()

View file

@ -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);
};
/*
@ -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================================

View file

@ -14,9 +14,11 @@ internal static class Constants
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);

View file

@ -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
}

View file

@ -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)
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));
}
}

View file

@ -20,6 +20,7 @@ internal sealed class Registry
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>();

View file

@ -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; }
}

View file

@ -37,4 +37,6 @@ public enum DataType
TypeVarTypeDesc,
FnPtrTypeDesc,
DynamicMetadata,
Object,
String,
}

View file

@ -308,7 +308,21 @@ internal sealed partial class SOSDacImpl : ISOSDacInterface, ISOSDacInterface2,
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;

View file

@ -0,0 +1,169 @@
// 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;
using System.Text;
using Xunit;
namespace Microsoft.Diagnostics.DataContractReader.UnitTests;
public unsafe class ObjectTests
{
const ulong TestStringMethodTableGlobalAddress = 0x00000000_100000a0;
const ulong TestStringMethodTableAddress = 0x00000000_100000a8;
private static readonly Target.TypeInfo ObjectTypeInfo = new()
{
Fields = {
{ "m_pMethTab", new() { Offset = 0, Type = DataType.pointer} },
}
};
private static readonly Target.TypeInfo StringTypeInfo = new Target.TypeInfo()
{
Fields = {
{ "m_StringLength", new() { Offset = 0x8, Type = DataType.uint32} },
{ "m_FirstChar", new() { Offset = 0xc, Type = DataType.uint16} },
}
};
private static readonly (DataType Type, Target.TypeInfo Info)[] ObjectTypes =
[
(DataType.Object, ObjectTypeInfo),
(DataType.String, StringTypeInfo),
];
const ulong TestObjectToMethodTableUnmask = 0x7;
private static (string Name, ulong Value, string? Type)[] ObjectGlobals =
[
(nameof(Constants.Globals.ObjectToMethodTableUnmask), TestObjectToMethodTableUnmask, "uint8"),
(nameof(Constants.Globals.StringMethodTable), TestStringMethodTableGlobalAddress, null),
];
private static MockMemorySpace.Builder AddStringMethodTablePointer(TargetTestHelpers targetTestHelpers, MockMemorySpace.Builder builder)
{
MockMemorySpace.HeapFragment fragment = new() { Name = "Address of String Method Table", Address = TestStringMethodTableGlobalAddress, Data = new byte[targetTestHelpers.PointerSize] };
targetTestHelpers.WritePointer(fragment.Data, TestStringMethodTableAddress);
return builder.AddHeapFragments([
fragment,
new () { Name = "String Method Table", Address = TestStringMethodTableAddress, Data = new byte[targetTestHelpers.PointerSize] }
]);
}
private delegate MockMemorySpace.Builder ConfigureContextBuilder(MockMemorySpace.Builder builder);
private static void ObjectContractHelper(MockTarget.Architecture arch, ConfigureContextBuilder configure, Action<Target> testCase)
{
TargetTestHelpers targetTestHelpers = new(arch);
string typesJson = TargetTestHelpers.MakeTypesJson(ObjectTypes);
string globalsJson = TargetTestHelpers.MakeGlobalsJson(ObjectGlobals);
byte[] json = Encoding.UTF8.GetBytes($$"""
{
"version": 0,
"baseline": "empty",
"contracts": {
"{{nameof(Contracts.Object)}}": 1
},
"types": { {{typesJson}} },
"globals": { {{globalsJson}} }
}
""");
Span<byte> descriptor = stackalloc byte[targetTestHelpers.ContractDescriptorSize];
targetTestHelpers.ContractDescriptorFill(descriptor, json.Length, ObjectGlobals.Length);
int pointerSize = targetTestHelpers.PointerSize;
Span<byte> pointerData = stackalloc byte[ObjectGlobals.Length * pointerSize];
for (int i = 0; i < ObjectGlobals.Length; i++)
{
var (_, value, _) = ObjectGlobals[i];
targetTestHelpers.WritePointer(pointerData.Slice(i * pointerSize), value);
}
fixed (byte* jsonPtr = json)
{
MockMemorySpace.Builder builder = new();
builder = builder.SetDescriptor(descriptor)
.SetJson(json)
.SetPointerData(pointerData);
builder = AddStringMethodTablePointer(targetTestHelpers, builder);
if (configure != null)
{
builder = configure(builder);
}
using MockMemorySpace.ReadContext context = builder.Create();
bool success = MockMemorySpace.TryCreateTarget(&context, out Target? target);
Assert.True(success);
testCase(target);
}
GC.KeepAlive(json);
}
private static MockMemorySpace.Builder AddObject(TargetTestHelpers targetTestHelpers, MockMemorySpace.Builder builder, TargetPointer address, TargetPointer methodTable)
{
MockMemorySpace.HeapFragment fragment = new() { Name = $"Object : MT = '{methodTable}'", Address = address, Data = new byte[targetTestHelpers.SizeOfTypeInfo(ObjectTypeInfo)] };
Span<byte> dest = fragment.Data;
targetTestHelpers.WritePointer(dest.Slice(ObjectTypeInfo.Fields["m_pMethTab"].Offset), methodTable);
return builder.AddHeapFragment(fragment);
}
private static MockMemorySpace.Builder AddStringObject(TargetTestHelpers targetTestHelpers, MockMemorySpace.Builder builder, TargetPointer address, string value)
{
int size = targetTestHelpers.SizeOfTypeInfo(ObjectTypeInfo) + targetTestHelpers.SizeOfTypeInfo(StringTypeInfo) + value.Length * sizeof(char);
MockMemorySpace.HeapFragment fragment = new() { Name = $"String = '{value}'", Address = address, Data = new byte[size] };
Span<byte> dest = fragment.Data;
targetTestHelpers.WritePointer(dest.Slice(ObjectTypeInfo.Fields["m_pMethTab"].Offset), TestStringMethodTableAddress);
targetTestHelpers.Write(dest.Slice(StringTypeInfo.Fields["m_StringLength"].Offset), (uint)value.Length);
MemoryMarshal.Cast<char, byte>(value).CopyTo(dest.Slice(StringTypeInfo.Fields["m_FirstChar"].Offset));
return builder.AddHeapFragment(fragment);
}
[Theory]
[ClassData(typeof(MockTarget.StdArch))]
public void UnmaskMethodTableAddress(MockTarget.Architecture arch)
{
const ulong TestObjectAddress = 0x00000000_10000010;
const ulong TestMethodTableAddress = 0x00000000_10000027;
TargetTestHelpers targetTestHelpers = new(arch);
ObjectContractHelper(arch,
(builder) =>
{
builder = AddObject(targetTestHelpers, builder, TestObjectAddress, TestMethodTableAddress);
return builder;
},
(target) =>
{
Contracts.IObject contract = target.Contracts.Object;
Assert.NotNull(contract);
TargetPointer mt = contract.GetMethodTableAddress(TestObjectAddress);
Assert.Equal(TestMethodTableAddress & ~TestObjectToMethodTableUnmask, mt.Value);
});
}
[Theory]
[ClassData(typeof(MockTarget.StdArch))]
public void StringValue(MockTarget.Architecture arch)
{
const ulong TestStringAddress = 0x00000000_10000010;
string expected = "test_string_value";
TargetTestHelpers targetTestHelpers = new(arch);
ObjectContractHelper(arch,
(builder) =>
{
builder = AddStringObject(targetTestHelpers, builder, TestStringAddress, expected);
return builder;
},
(target) =>
{
Contracts.IObject contract = target.Contracts.Object;
Assert.NotNull(contract);
string actual = contract.GetStringValue(TestStringAddress);
Assert.Equal(expected, actual);
});
}
}