Skip to content

Commit

Permalink
[cdac] Implement ISOSDacInterface.GetThreadData (#103324)
Browse files Browse the repository at this point in the history
- Implement `GetThreadData` in `Thread` contract
- Finish implementing `ISOSDacInterface::GetThreadData` in cDAC
- Add `ExceptionInfo` to cDAC types
  • Loading branch information
elinor-fung committed Jun 18, 2024
1 parent 01172db commit 547e69e
Show file tree
Hide file tree
Showing 13 changed files with 204 additions and 65 deletions.
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)
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 @@ -3971,9 +3971,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);
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

#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

0 comments on commit 547e69e

Please sign in to comment.