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] Read types from contract descriptor #101994

Merged
merged 10 commits into from
May 13, 2024
1 change: 1 addition & 0 deletions src/coreclr/debug/runtimeinfo/contracts.jsonc
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
// cdac-build-tool can take multiple "-c contract_file" arguments
// so to conditionally include contracts, put additional contracts in a separate file
{
"Thread": 1,
"SOSBreakingChangeVersion": 1 // example contract: "runtime exports an SOS breaking change version global"
}

18 changes: 12 additions & 6 deletions src/coreclr/debug/runtimeinfo/datadescriptor.h
Original file line number Diff line number Diff line change
Expand Up @@ -103,11 +103,17 @@
CDAC_BASELINE("empty")
CDAC_TYPES_BEGIN()

CDAC_TYPE_BEGIN(ManagedThread)
CDAC_TYPE_INDETERMINATE(ManagedThread)
CDAC_TYPE_FIELD(ManagedThread, GCHandle, GCHandle, cdac_offsets<Thread>::ExposedObject)
CDAC_TYPE_FIELD(ManagedThread, pointer, LinkNext, cdac_offsets<Thread>::Link)
CDAC_TYPE_END(ManagedThread)
CDAC_TYPE_BEGIN(Thread)
CDAC_TYPE_INDETERMINATE(Thread)
CDAC_TYPE_FIELD(Thread, GCHandle, GCHandle, cdac_offsets<Thread>::ExposedObject)
CDAC_TYPE_FIELD(Thread, pointer, LinkNext, cdac_offsets<Thread>::Link)
CDAC_TYPE_END(Thread)

CDAC_TYPE_BEGIN(ThreadStore)
CDAC_TYPE_INDETERMINATE(ThreadStore)
CDAC_TYPE_FIELD(ThreadStore, /*omit type*/, ThreadCount, cdac_offsets<ThreadStore>::ThreadCount)
CDAC_TYPE_FIELD(ThreadStore, /*omit type*/, ThreadList, cdac_offsets<ThreadStore>::ThreadList)
CDAC_TYPE_END(ThreadStore)

CDAC_TYPE_BEGIN(GCHandle)
CDAC_TYPE_SIZE(sizeof(OBJECTHANDLE))
Expand All @@ -116,7 +122,7 @@ CDAC_TYPE_END(GCHandle)
CDAC_TYPES_END()

CDAC_GLOBALS_BEGIN()
CDAC_GLOBAL_POINTER(ManagedThreadStore, &ThreadStore::s_pThreadStore)
CDAC_GLOBAL_POINTER(ThreadStore, &ThreadStore::s_pThreadStore)
#if FEATURE_EH_FUNCLETS
CDAC_GLOBAL(FeatureEHFunclets, uint8, 1)
#else
Expand Down
9 changes: 9 additions & 0 deletions src/coreclr/vm/threads.h
Original file line number Diff line number Diff line change
Expand Up @@ -4335,6 +4335,15 @@ class ThreadStore
void OnMaxGenerationGCStarted();
bool ShouldTriggerGCForDeadThreads();
void TriggerGCForDeadThreadsIfNecessary();

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

template<>
struct cdac_offsets<ThreadStore>
{
static constexpr size_t ThreadList = offsetof(ThreadStore, m_ThreadList);
static constexpr size_t ThreadCount = offsetof(ThreadStore, m_ThreadCount);
};

struct TSSuspendHelper {
Expand Down
1 change: 1 addition & 0 deletions src/native/managed/cdacreader/src/Constants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ internal static class Constants
internal static class Globals
{
// See src/coreclr/debug/runtimeinfo/datadescriptor.h
internal const string ThreadStore = nameof(ThreadStore);
internal const string SOSBreakingChangeVersion = nameof(SOSBreakingChangeVersion);
}
}
52 changes: 52 additions & 0 deletions src/native/managed/cdacreader/src/Contracts/Thread.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Diagnostics.CodeAnalysis;

namespace Microsoft.Diagnostics.DataContractReader.Contracts;

// TODO: [cdac] Add other counts / threads
public record struct ThreadStoreData(
elinor-fung marked this conversation as resolved.
Show resolved Hide resolved
int ThreadCount,
TargetPointer FirstThread);

internal sealed class Thread
lambdageek marked this conversation as resolved.
Show resolved Hide resolved
{
private readonly Target _target;
private readonly int _version;
private readonly TargetPointer _threadStoreAddr;

public static bool TryCreate(Target target, [NotNullWhen(true)] out Thread? thread)
{
thread = default;
if (!target.TryGetContractVersion(nameof(Thread), out int version))
return false;

if (!target.TryReadGlobalPointer(Constants.Globals.ThreadStore, out TargetPointer threadStore))
return false;

thread = new Thread(target, version, threadStore);
return true;
}

private Thread(Target target, int version, TargetPointer threadStore)
{
_target = target;
_version = version;
_threadStoreAddr = threadStore;
}

public ThreadStoreData GetThreadStoreData()
{
Data.ThreadStore? threadStore;
if (!_target.ProcessedData.TryGet(_threadStoreAddr.Value, out threadStore))
{
threadStore = new Data.ThreadStore(_target, _threadStoreAddr);

// Still okay if processed data is already registered by someone else
_ = _target.ProcessedData.TryRegister(_threadStoreAddr.Value, threadStore);
}

return new ThreadStoreData(threadStore.ThreadCount, threadStore.FirstThread);
}
}
20 changes: 20 additions & 0 deletions src/native/managed/cdacreader/src/Data/ThreadStore.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// 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 ThreadStore
{
public ThreadStore(Target target, TargetPointer pointer)
{
Target.TypeInfo type = target.GetTypeInfo(DataType.ThreadStore);
TargetPointer addr = target.ReadPointer(pointer.Value);

ThreadCount = target.Read<int>(addr.Value + (ulong)type.Fields[nameof(ThreadCount)].Offset);
FirstThread = TargetPointer.Null;
}

public int ThreadCount { get; init; }

public TargetPointer FirstThread { get; init; }
}
25 changes: 25 additions & 0 deletions src/native/managed/cdacreader/src/DataType.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;

public enum DataType
{
Unknown = 0,

int8,
uint8,
int16,
uint16,
int32,
uint32,
int64,
uint64,
nint,
nuint,
pointer,

GCHandle,
Thread,
ThreadStore,
}
15 changes: 14 additions & 1 deletion src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,20 @@ public int GetBreakingChangeVersion()
public unsafe int GetThreadFromThinlockID(uint thinLockId, ulong* pThread) => HResults.E_NOTIMPL;
public unsafe int GetThreadLocalModuleData(ulong thread, uint index, void* data) => HResults.E_NOTIMPL;
public unsafe int GetThreadpoolData(void* data) => HResults.E_NOTIMPL;
public unsafe int GetThreadStoreData(DacpThreadStoreData* data) => HResults.E_NOTIMPL;

public unsafe int GetThreadStoreData(DacpThreadStoreData* data)
{
if (!Contracts.Thread.TryCreate(_target, out Contracts.Thread? thread))
return HResults.E_NOTIMPL;

Contracts.ThreadStoreData threadStoreData = thread.GetThreadStoreData();
data->threadCount = threadStoreData.ThreadCount;
data->firstThread = threadStoreData.FirstThread.Value;
data->fHostConfig = 0;

return HResults.E_NOTIMPL;
}

public unsafe int GetTLSIndex(uint* pIndex) => HResults.E_NOTIMPL;
public unsafe int GetUsefulGlobals(void* data) => HResults.E_NOTIMPL;
public unsafe int GetWorkRequestData(ulong addrWorkRequest, void* data) => HResults.E_NOTIMPL;
Expand Down
132 changes: 126 additions & 6 deletions src/native/managed/cdacreader/src/Target.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Buffers.Binary;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Numerics;
using System.Runtime.CompilerServices;

Expand All @@ -19,6 +19,21 @@ public struct TargetPointer

public sealed unsafe class Target
{
public record struct TypeInfo
{
public uint? Size;
public Dictionary<string, FieldInfo> Fields = [];

public TypeInfo() { }
AaronRobinsonMSFT marked this conversation as resolved.
Show resolved Hide resolved
}

public record struct FieldInfo
{
public int Offset;
public DataType Type;
public string? TypeName;
}

private const int StackAllocByteThreshold = 1024;

private readonly struct Configuration
Expand All @@ -30,8 +45,12 @@ private readonly struct Configuration
private readonly Configuration _config;
private readonly Reader _reader;

private readonly IReadOnlyDictionary<string, int> _contracts = new Dictionary<string, int>();
private readonly Dictionary<string, int> _contracts = [];
private readonly IReadOnlyDictionary<string, (ulong Value, string? Type)> _globals = new Dictionary<string, (ulong, string?)>();
private readonly Dictionary<DataType, TypeInfo> _knownTypes = [];
private readonly Dictionary<string, TypeInfo> _types = [];

internal DataCache ProcessedData { get; } = new DataCache();

public static bool TryCreate(ulong contractDescriptor, delegate* unmanaged<ulong, byte*, uint, void*, int> readFromTarget, void* readContext, out Target? target)
{
Expand All @@ -51,10 +70,39 @@ private Target(Configuration config, ContractDescriptorParser.ContractDescriptor
_config = config;
_reader = reader;

// TODO: [cdac] Read types
// note: we will probably want to store the globals and types into a more usable form
_contracts = descriptor.Contracts ?? [];

// Read types and map to known data types
if (descriptor.Types is not null)
{
foreach ((string name, ContractDescriptorParser.TypeDescriptor type) in descriptor.Types)
{
TypeInfo typeInfo = new() { Size = type.Size };
if (type.Fields is not null)
{
foreach ((string fieldName, ContractDescriptorParser.FieldDescriptor field) in type.Fields)
{
typeInfo.Fields[fieldName] = new FieldInfo()
{
Offset = field.Offset,
Type = field.Type is null ? DataType.Unknown : GetDataType(field.Type),
TypeName = field.Type
};
}
}

DataType dataType = GetDataType(name);
if (dataType is not DataType.Unknown)
{
_knownTypes[dataType] = typeInfo;
}
else
{
_types[name] = typeInfo;
}
}
}

// Read globals and map indirect values to pointer data
if (descriptor.Globals is not null)
{
Expand Down Expand Up @@ -159,9 +207,17 @@ private static bool TryReadContractDescriptor(
return true;
}

public T Read<T>(ulong address, out T value) where T : unmanaged, IBinaryInteger<T>, IMinMaxValue<T>
private static DataType GetDataType(string type)
{
if (Enum.TryParse(type, false, out DataType dataType) && Enum.IsDefined(dataType))
return dataType;

return DataType.Unknown;
}

public T Read<T>(ulong address) where T : unmanaged, IBinaryInteger<T>, IMinMaxValue<T>
{
if (!TryRead(address, out value))
if (!TryRead(address, out T value))
throw new InvalidOperationException($"Failed to read {typeof(T)} at 0x{address:x8}.");

return value;
Expand Down Expand Up @@ -283,6 +339,70 @@ public bool TryReadGlobalPointer(string name, out TargetPointer pointer)
return true;
}

public TypeInfo GetTypeInfo(DataType type)
{
if (!TryGetTypeInfo(type, out TypeInfo typeInfo))
throw new InvalidOperationException($"Failed to get type info for '{type}'");

return typeInfo;
}

public bool TryGetTypeInfo(DataType type, out TypeInfo typeInfo)
=> _knownTypes.TryGetValue(type, out typeInfo);

public TypeInfo GetTypeInfo(string type)
{
if (!TryGetTypeInfo(type, out TypeInfo typeInfo))
throw new InvalidOperationException($"Failed to get type info for '{type}'");

return typeInfo;
}
lambdageek marked this conversation as resolved.
Show resolved Hide resolved

public bool TryGetTypeInfo(string type, out TypeInfo typeInfo)
{
if (_types.TryGetValue(type, out typeInfo))
return true;

DataType dataType = GetDataType(type);
if (dataType is DataType.Unknown)
return false;

return TryGetTypeInfo(dataType, out typeInfo);
}

internal bool TryGetContractVersion(string contractName, out int version)
=> _contracts.TryGetValue(contractName, out version);

/// <summary>
/// Store of addresses that have already been read into corresponding data models
/// </summary>
internal sealed class DataCache
{
private readonly Dictionary<ulong, object?> _readDataByAddress = [];

public bool TryRegister(ulong address, object data)
{
bool success = _readDataByAddress.TryAdd(address, data);
System.Diagnostics.Debug.Assert(success || data.GetType() == _readDataByAddress[address]!.GetType());
return success;
}

public bool TryGet<T>(ulong address, [NotNullWhen(true)] out T? data)
{
data = default;
if (!_readDataByAddress.TryGetValue(address, out object? dataObj))
return false;

if (dataObj is T dataMaybe)
{
data = dataMaybe;
return true;
}

return false;
}
}

private sealed class Reader
{
private readonly delegate* unmanaged<ulong, byte*, uint, void*, int> _readFromTarget;
Expand Down
Loading
Loading