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] Implement ISOSDacInterface.GetThreadData #103324

Merged
merged 9 commits into from
Jun 18, 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
61 changes: 6 additions & 55 deletions docs/design/datacontracts/Thread.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,61 +19,12 @@ record struct ThreadStoreCounts (

enum ThreadState
{
TS_Unknown = 0x00000000, // threads are initialized this way

TS_AbortRequested = 0x00000001, // Abort the thread

TS_GCSuspendRedirected = 0x00000004, // ThreadSuspend::SuspendRuntime has redirected the thread to suspention routine.

TS_DebugSuspendPending = 0x00000008, // Is the debugger suspending threads?
TS_GCOnTransitions = 0x00000010, // Force a GC on stub transitions (GCStress only)

TS_LegalToJoin = 0x00000020, // Is it now legal to attempt a Join()

TS_ExecutingOnAltStack = 0x00000040, // Runtime is executing on an alternate stack located anywhere in the memory

TS_Hijacked = 0x00000080, // Return address has been hijacked

// unused = 0x00000100,
TS_Background = 0x00000200, // Thread is a background thread
TS_Unstarted = 0x00000400, // Thread has never been started
TS_Dead = 0x00000800, // Thread is dead

TS_WeOwn = 0x00001000, // Exposed object initiated this thread
TS_CoInitialized = 0x00002000, // CoInitialize has been called for this thread

TS_InSTA = 0x00004000, // Thread hosts an STA
TS_InMTA = 0x00008000, // Thread is part of the MTA

// Some bits that only have meaning for reporting the state to clients.
TS_ReportDead = 0x00010000, // in WaitForOtherThreads()
TS_FullyInitialized = 0x00020000, // Thread is fully initialized and we are ready to broadcast its existence to external clients

TS_TaskReset = 0x00040000, // The task is reset

TS_SyncSuspended = 0x00080000, // Suspended via WaitSuspendEvent
TS_DebugWillSync = 0x00100000, // Debugger will wait for this thread to sync

TS_StackCrawlNeeded = 0x00200000, // A stackcrawl is needed on this thread, such as for thread abort
// See comment for s_pWaitForStackCrawlEvent for reason.

// unused = 0x00400000,

// unused = 0x00800000,
TS_TPWorkerThread = 0x01000000, // is this a threadpool worker thread?

TS_Interruptible = 0x02000000, // sitting in a Sleep(), Wait(), Join()
TS_Interrupted = 0x04000000, // was awakened by an interrupt APC. !!! This can be moved to TSNC

TS_CompletionPortThread = 0x08000000, // Completion port thread

TS_AbortInitiated = 0x10000000, // set when abort is begun

TS_Finalized = 0x20000000, // The associated managed Thread object has been finalized.
// We can clean up the unmanaged part now.

TS_FailStarted = 0x40000000, // The thread fails during startup.
TS_Detached = 0x80000000, // Thread was detached by DllMain
Unknown = 0x00000000, // threads are initialized this way
Hijacked = 0x00000080, // Return address has been hijacked
Background = 0x00000200, // Thread is a background thread
Unstarted = 0x00000400, // Thread has never been started
Dead = 0x00000800, // Thread is dead
ThreadPoolWorker = 0x01000000, // is this a threadpool worker thread?
}

record struct ThreadData (
Expand Down
24 changes: 24 additions & 0 deletions src/coreclr/debug/runtimeinfo/datadescriptor.h
Original file line number Diff line number Diff line change
Expand Up @@ -107,9 +107,17 @@ CDAC_TYPE_BEGIN(Thread)
CDAC_TYPE_INDETERMINATE(Thread)
CDAC_TYPE_FIELD(Thread, /*uint32*/, Id, cdac_offsets<Thread>::Id)
CDAC_TYPE_FIELD(Thread, /*nuint*/, OSId, cdac_offsets<Thread>::OSId)
CDAC_TYPE_FIELD(Thread, /*uint32*/, State, cdac_offsets<Thread>::State)
CDAC_TYPE_FIELD(Thread, /*uint32*/, PreemptiveGCDisabled, cdac_offsets<Thread>::PreemptiveGCDisabled)
elinor-fung marked this conversation as resolved.
Show resolved Hide resolved
CDAC_TYPE_FIELD(Thread, /*pointer*/, AllocContext, cdac_offsets<Thread>::AllocContext)
CDAC_TYPE_FIELD(Thread, /*pointer*/, Frame, cdac_offsets<Thread>::Frame)
CDAC_TYPE_FIELD(Thread, /*pointer*/, ExceptionTracker, cdac_offsets<Thread>::ExceptionTracker)
CDAC_TYPE_FIELD(Thread, GCHandle, GCHandle, cdac_offsets<Thread>::ExposedObject)
CDAC_TYPE_FIELD(Thread, GCHandle, LastThrownObject, cdac_offsets<Thread>::LastThrownObject)
CDAC_TYPE_FIELD(Thread, pointer, LinkNext, cdac_offsets<Thread>::Link)
#ifndef TARGET_UNIX
CDAC_TYPE_FIELD(Thread, /*pointer*/, TEB, cdac_offsets<Thread>::TEB)
#endif
CDAC_TYPE_END(Thread)

CDAC_TYPE_BEGIN(ThreadStore)
Expand All @@ -122,13 +130,29 @@ CDAC_TYPE_FIELD(ThreadStore, /*int32*/, PendingCount, cdac_offsets<ThreadStore>:
CDAC_TYPE_FIELD(ThreadStore, /*int32*/, DeadCount, cdac_offsets<ThreadStore>::DeadCount)
CDAC_TYPE_END(ThreadStore)

CDAC_TYPE_BEGIN(GCAllocContext)
CDAC_TYPE_INDETERMINATE(GCAllocContext)
CDAC_TYPE_FIELD(GCAllocContext, /*pointer*/, Pointer, offsetof(gc_alloc_context, alloc_ptr))
CDAC_TYPE_FIELD(GCAllocContext, /*pointer*/, Limit, offsetof(gc_alloc_context, alloc_limit))
CDAC_TYPE_END(GCAllocContext)

CDAC_TYPE_BEGIN(ExceptionInfo)
CDAC_TYPE_INDETERMINATE(ExceptionInfo)
#if FEATURE_EH_FUNCLETS
CDAC_TYPE_FIELD(PreviousNestedInfo, /*pointer*/, PreviousNestedInfo, offsetof(ExceptionTrackerBase, m_pPrevNestedInfo))
#else
CDAC_TYPE_FIELD(PreviousNestedInfo, /*pointer*/, PreviousNestedInfo, offsetof(ExInfo, m_pPrevNestedInfo))
#endif
CDAC_TYPE_END(ExceptionInfo)

CDAC_TYPE_BEGIN(GCHandle)
CDAC_TYPE_SIZE(sizeof(OBJECTHANDLE))
CDAC_TYPE_END(GCHandle)

CDAC_TYPES_END()

CDAC_GLOBALS_BEGIN()
CDAC_GLOBAL_POINTER(AppDomain, &AppDomain::m_pTheAppDomain)
CDAC_GLOBAL_POINTER(ThreadStore, &ThreadStore::s_pThreadStore)
CDAC_GLOBAL_POINTER(FinalizerThread, &::g_pFinalizerThread)
CDAC_GLOBAL_POINTER(GCThread, &::g_pSuspensionThread)
Expand Down
2 changes: 1 addition & 1 deletion src/coreclr/vm/appdomain.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -1675,10 +1675,10 @@ class AppDomain : public BaseDomain
}
#endif

private:
// The one and only AppDomain
SPTR_DECL(AppDomain, m_pTheAppDomain);

private:
SString m_friendlyName;
PTR_Assembly m_pRootAssembly;

Expand Down
3 changes: 3 additions & 0 deletions src/coreclr/vm/exstate.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ class DebuggerExState;
class EHClauseInfo;

#include "exceptionhandling.h"
#include "cdacoffsets.h"

#if !defined(FEATURE_EH_FUNCLETS)
// ExInfo contains definitions for 32bit
Expand Down Expand Up @@ -50,6 +51,8 @@ class ThreadExceptionState
// ExceptionTracker or the ExInfo as appropriate for the platform
friend class ProfToEEInterfaceImpl;

template<typename T> friend struct ::cdac_offsets;

#ifdef FEATURE_EH_FUNCLETS
friend class ExceptionTracker;
friend struct ExInfo;
Expand Down
16 changes: 16 additions & 0 deletions src/coreclr/vm/threads.h
Original file line number Diff line number Diff line change
Expand Up @@ -3969,9 +3969,25 @@ struct cdac_offsets<Thread>
{
static constexpr size_t Id = offsetof(Thread, m_ThreadId);
static constexpr size_t OSId = offsetof(Thread, m_OSThreadId);
static constexpr size_t State = offsetof(Thread, m_State);
jkotas marked this conversation as resolved.
Show resolved Hide resolved
static constexpr size_t PreemptiveGCDisabled = offsetof(Thread, m_fPreemptiveGCDisabled);
static constexpr size_t AllocContext = offsetof(Thread, m_alloc_context);
static constexpr size_t Frame = offsetof(Thread, m_pFrame);
static constexpr size_t ExposedObject = offsetof(Thread, m_ExposedObject);
static constexpr size_t LastThrownObject = offsetof(Thread, m_LastThrownObjectHandle);
static constexpr size_t Link = offsetof(Thread, m_Link);

static_assert(std::is_same<decltype(std::declval<Thread>().m_ExceptionState), ThreadExceptionState>::value,
"Thread::m_ExceptionState is of type ThreadExceptionState");
#ifdef FEATURE_EH_FUNCLETS
static constexpr size_t ExceptionTracker = offsetof(Thread, m_ExceptionState) + offsetof(ThreadExceptionState, m_pCurrentTracker);
#else
static constexpr size_t ExceptionTracker = offsetof(Thread, m_ExceptionState) + offsetof(ThreadExceptionState, m_currentExInfo);
#endif
lambdageek marked this conversation as resolved.
Show resolved Hide resolved

#ifndef TARGET_UNIX
static constexpr size_t TEB = offsetof(Thread, m_pTEB);
#endif
};

// End of class Thread
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 @@ -8,10 +8,12 @@ internal static class Constants
internal static class Globals
{
// See src/coreclr/debug/runtimeinfo/datadescriptor.h
internal const string AppDomain = nameof(AppDomain);
internal const string ThreadStore = nameof(ThreadStore);
internal const string FinalizerThread = nameof(FinalizerThread);
internal const string GCThread = nameof(GCThread);

internal const string FeatureEHFunclets = nameof(FeatureEHFunclets);
internal const string SOSBreakingChangeVersion = nameof(SOSBreakingChangeVersion);
}
}
41 changes: 41 additions & 0 deletions src/native/managed/cdacreader/src/Contracts/Thread.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,28 @@ internal record struct ThreadStoreCounts(
int PendingThreadCount,
int DeadThreadCount);

[Flags]
internal enum ThreadState
{
Unknown = 0x00000000,
Hijacked = 0x00000080, // Return address has been hijacked
Background = 0x00000200, // Thread is a background thread
Unstarted = 0x00000400, // Thread has never been started
Dead = 0x00000800, // Thread is dead
ThreadPoolWorker = 0x01000000, // Thread is a thread pool worker thread
}

internal record struct ThreadData(
uint Id,
TargetNUInt OSId,
ThreadState State,
bool PreemptiveGCDisabled,
TargetPointer AllocContextPointer,
TargetPointer AllocContextLimit,
TargetPointer Frame,
TargetPointer FirstNestedException,
TargetPointer TEB,
TargetPointer LastThrownObjectHandle,
TargetPointer NextThread);

internal interface IThread : IContract
Expand Down Expand Up @@ -85,8 +105,29 @@ ThreadStoreCounts IThread.GetThreadCounts()
ThreadData IThread.GetThreadData(TargetPointer threadPointer)
{
Data.Thread thread = _target.ProcessedData.GetOrAdd<Data.Thread>(threadPointer);

// Exception tracker is a pointer when EH funclets are enabled
TargetPointer address = _target.ReadGlobal<byte>(Constants.Globals.FeatureEHFunclets) != 0
? _target.ReadPointer(thread.ExceptionTracker)
: thread.ExceptionTracker;
TargetPointer firstNestedException = TargetPointer.Null;
if (address != TargetPointer.Null)
{
Data.ExceptionInfo exceptionInfo = _target.ProcessedData.GetOrAdd<Data.ExceptionInfo>(address);
firstNestedException = exceptionInfo.PreviousNestedInfo;
}

return new ThreadData(
thread.Id,
thread.OSId,
(ThreadState)thread.State,
(thread.PreemptiveGCDisabled & 0x1) != 0,
thread.AllocContext is null ? TargetPointer.Null : thread.AllocContext.Pointer,
thread.AllocContext is null ? TargetPointer.Null : thread.AllocContext.Limit,
thread.Frame,
firstNestedException,
thread.TEB,
thread.LastThrownObject,
GetThreadFromLink(thread.LinkNext));
}

Expand Down
19 changes: 19 additions & 0 deletions src/native/managed/cdacreader/src/Data/ExceptionInfo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// 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 ExceptionInfo : IData<ExceptionInfo>
{
static ExceptionInfo IData<ExceptionInfo>.Create(Target target, TargetPointer address)
=> new ExceptionInfo(target, address);

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

PreviousNestedInfo = target.ReadPointer(address + (ulong)type.Fields[nameof(PreviousNestedInfo)].Offset);
}

public TargetPointer PreviousNestedInfo { get; init; }
}
20 changes: 20 additions & 0 deletions src/native/managed/cdacreader/src/Data/GCAllocContext.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 GCAllocContext : IData<GCAllocContext>
{
static GCAllocContext IData<GCAllocContext>.Create(Target target, TargetPointer address)
=> new GCAllocContext(target, address);

public GCAllocContext(Target target, TargetPointer address)
{
Target.TypeInfo type = target.GetTypeInfo(DataType.GCAllocContext);
Pointer = target.ReadPointer(address + (ulong)type.Fields[nameof(Pointer)].Offset);
Limit = target.ReadPointer(address + (ulong)type.Fields[nameof(Limit)].Offset);
}

public TargetPointer Pointer { get; init; }
public TargetPointer Limit { get; init; }
}
26 changes: 26 additions & 0 deletions src/native/managed/cdacreader/src/Data/Thread.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,35 @@ public Thread(Target target, TargetPointer address)
Target.TypeInfo type = target.GetTypeInfo(DataType.Thread);

Id = target.Read<uint>(address + (ulong)type.Fields[nameof(Id)].Offset);
OSId = target.ReadNUInt(address + (ulong)type.Fields[nameof(OSId)].Offset);
State = target.Read<uint>(address + (ulong)type.Fields[nameof(State)].Offset);
PreemptiveGCDisabled = target.Read<uint>(address + (ulong)type.Fields[nameof(PreemptiveGCDisabled)].Offset);

TargetPointer allocContextPointer = target.ReadPointer(address + (ulong)type.Fields[nameof(AllocContext)].Offset);
if (allocContextPointer != TargetPointer.Null)
AllocContext = target.ProcessedData.GetOrAdd<GCAllocContext>(allocContextPointer);

Frame = target.ReadPointer(address + (ulong)type.Fields[nameof(Frame)].Offset);

// TEB does not exist on certain platforms
TEB = type.Fields.TryGetValue(nameof(TEB), out Target.FieldInfo fieldInfo)
? target.ReadPointer(address + (ulong)fieldInfo.Offset)
: TargetPointer.Null;
LastThrownObject = target.ReadPointer(address + (ulong)type.Fields[nameof(LastThrownObject)].Offset);
LinkNext = target.ReadPointer(address + (ulong)type.Fields[nameof(LinkNext)].Offset);

// Address of the exception tracker - how it should be read depends on EH funclets feature global value
ExceptionTracker = address + (ulong)type.Fields[nameof(ExceptionTracker)].Offset;
}

public uint Id { get; init; }
public TargetNUInt OSId { get; init; }
public uint State { get; init; }
public uint PreemptiveGCDisabled { get; init; }
public GCAllocContext? AllocContext { get; init; }
public TargetPointer Frame { get; init; }
public TargetPointer TEB { get; init; }
public TargetPointer LastThrownObject { get; init; }
public TargetPointer LinkNext { get; init; }
public TargetPointer ExceptionTracker { 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 @@ -22,4 +22,6 @@ public enum DataType
GCHandle,
Thread,
ThreadStore,
GCAllocContext,
ExceptionInfo,
}
20 changes: 18 additions & 2 deletions src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs
Original file line number Diff line number Diff line change
Expand Up @@ -115,15 +115,31 @@ public unsafe int GetThreadData(ulong thread, DacpThreadData* data)
Contracts.IThread contract = _target.Contracts.Thread;
Contracts.ThreadData threadData = contract.GetThreadData(thread);
data->corThreadId = (int)threadData.Id;
data->osThreadId = (int)threadData.OSId.Value;
data->state = (int)threadData.State;
data->preemptiveGCDisabled = (uint)(threadData.PreemptiveGCDisabled ? 1 : 0);
data->allocContextPtr = threadData.AllocContextPointer;
data->allocContextLimit = threadData.AllocContextLimit;
data->fiberData = 0; // Always set to 0 - fibers are no longer supported

TargetPointer appDomainPointer = _target.ReadGlobalPointer(Constants.Globals.AppDomain);
TargetPointer appDomain = _target.ReadPointer(appDomainPointer);
data->context = appDomain;
data->domain = appDomain;

data->lockCount = -1; // Always set to -1 - lock count was .NET Framework and no longer needed
data->pFrame = threadData.Frame;
data->firstNestedException = threadData.FirstNestedException;
data->teb = threadData.TEB;
data->lastThrownObjectHandle = threadData.LastThrownObjectHandle;
data->nextThread = threadData.NextThread;
}
catch (Exception ex)
{
return ex.HResult;
}

// TODO: [cdac] Implement/populate rest of thread data fields
return HResults.E_NOTIMPL;
return HResults.S_OK;
}
public unsafe int GetThreadFromThinlockID(uint thinLockId, ulong* pThread) => HResults.E_NOTIMPL;
public unsafe int GetThreadLocalModuleData(ulong thread, uint index, void* data) => HResults.E_NOTIMPL;
Expand Down
Loading
Loading