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);
}
}
11 changes: 11 additions & 0 deletions src/native/managed/cdacreader/src/Contracts/IContract.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// 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 IContract
{
static virtual IContract Create(Target target) => throw new NotImplementedException();
}
32 changes: 32 additions & 0 deletions src/native/managed/cdacreader/src/Contracts/Registry.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// 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.Collections.Generic;

namespace Microsoft.Diagnostics.DataContractReader.Contracts;

internal sealed class Registry
{
public Dictionary<Type, IContract> _contracts = [];
public Target _target;
elinor-fung marked this conversation as resolved.
Show resolved Hide resolved

public Registry(Target target)
{
_target = target;
}

public Thread Thread => GetContract<Thread>();

private T GetContract<T>() where T : IContract
{
if (_contracts.TryGetValue(typeof(T), out IContract? contractMaybe))
return (T)contractMaybe;

IContract contract = T.Create(_target);

// Still okay if contract was already registered by someone else
Copy link
Member

Choose a reason for hiding this comment

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

Why is this okay? Does the contract not really matter in this case? The below logic means we could fail to add the contract because one already exists, and then return the one we just created. This seems like we could get into a state where an implementation has state and isn't the one being used. I think we need some other invariant in the system if this pattern is considered safe.

Copy link
Member Author

Choose a reason for hiding this comment

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

Agreed, this should return the existing one if it got registered in between. I don't think the contracts should really have state other than what is tied to the target itself.

Copy link
Member

@lambdageek lambdageek May 9, 2024

Choose a reason for hiding this comment

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

I don't think the contracts should really have state other than what is tied to the target itself.

I kind of want contract implementations to be valuetypes. That way it's evident they have no identity. Not sure if that's going overboard, though

Copy link
Member

Choose a reason for hiding this comment

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

That way it's evident they have no identity. Not sure if that's going overboard, though

That is the kind of architecture invariant that would work for me. It isn't a compile time enforcement, but does make people ask the right question. The counter to that though then becomes, why do we have a cache if none of the objects have state? Will the constructor have a lot of logic to set itself up?

Copy link
Member

@lambdageek lambdageek May 9, 2024

Choose a reason for hiding this comment

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

Will the constructor have a lot of logic to set itself up?

My feeling is, yes. (well not literally the constructor, but the Create static virtual method). In the C++ prototype there was a lot of work upfront to query the target and establish that the necessary data was available. I think something similar is already evident in the Thread contract, too

Copy link
Member Author

Choose a reason for hiding this comment

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

1ad1da3 turns Thread into a readonly struct - thoughts? I didn't want the additional interface originally, but after actually doing it, it doesn't seem like too much.

Copy link
Member

Choose a reason for hiding this comment

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

thoughts?

I like it, but I will also readily acknowledge we landed in a very non-idiomatic way of programming C#. But maybe with static virtuals and DIMs this is what programming with immutable values looks like in C# now?

_ = _contracts.TryAdd(typeof(T), contract);
return (T)contract;
}
}
59 changes: 59 additions & 0 deletions src/native/managed/cdacreader/src/Contracts/Thread.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
// 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;

// 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 class Thread : IContract
{
public static Thread NotImplemented = new Thread();

public static IContract Create(Target target)
{
if (!target.TryGetContractVersion(nameof(Thread), out int version))
return NotImplemented;

lambdageek marked this conversation as resolved.
Show resolved Hide resolved
if (!target.TryReadGlobalPointer(Constants.Globals.ThreadStore, out TargetPointer threadStore))
return NotImplemented;

return version switch
{
1 => new Thread_1(target, threadStore),
_ => NotImplemented,
};
}

public virtual ThreadStoreData GetThreadStoreData() => throw new NotImplementedException();
}

internal sealed class Thread_1 : Thread
{
private readonly Target _target;
private readonly TargetPointer _threadStoreAddr;

internal Thread_1(Target target, TargetPointer threadStore)
{
_target = target;
_threadStoreAddr = threadStore;
}

public override 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,
}
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 @@ -106,7 +106,21 @@ 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)
{
Contracts.Thread thread = _target.Contracts.Thread;
if (thread == Contracts.Thread.NotImplemented)
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
Loading
Loading