From 547e69e0b70bf77fda1e38eb57e6638f85a8aa04 Mon Sep 17 00:00:00 2001 From: Elinor Fung Date: Tue, 18 Jun 2024 10:00:10 -0700 Subject: [PATCH] [cdac] Implement ISOSDacInterface.GetThreadData (#103324) - Implement `GetThreadData` in `Thread` contract - Finish implementing `ISOSDacInterface::GetThreadData` in cDAC - Add `ExceptionInfo` to cDAC types --- docs/design/datacontracts/Thread.md | 61 ++----------------- .../debug/runtimeinfo/datadescriptor.h | 24 ++++++++ src/coreclr/vm/appdomain.hpp | 2 +- src/coreclr/vm/exstate.h | 3 + src/coreclr/vm/threads.h | 16 +++++ .../managed/cdacreader/src/Constants.cs | 2 + .../cdacreader/src/Contracts/Thread.cs | 41 +++++++++++++ .../cdacreader/src/Data/ExceptionInfo.cs | 19 ++++++ .../cdacreader/src/Data/GCAllocContext.cs | 20 ++++++ .../managed/cdacreader/src/Data/Thread.cs | 26 ++++++++ src/native/managed/cdacreader/src/DataType.cs | 2 + .../cdacreader/src/Legacy/SOSDacImpl.cs | 20 +++++- src/native/managed/cdacreader/src/Target.cs | 33 +++++++--- 13 files changed, 204 insertions(+), 65 deletions(-) create mode 100644 src/native/managed/cdacreader/src/Data/ExceptionInfo.cs create mode 100644 src/native/managed/cdacreader/src/Data/GCAllocContext.cs diff --git a/docs/design/datacontracts/Thread.md b/docs/design/datacontracts/Thread.md index 92382eacf32d5..dc17e506ea356 100644 --- a/docs/design/datacontracts/Thread.md +++ b/docs/design/datacontracts/Thread.md @@ -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 ( diff --git a/src/coreclr/debug/runtimeinfo/datadescriptor.h b/src/coreclr/debug/runtimeinfo/datadescriptor.h index e14d29f38ad79..0e2cdc7a39434 100644 --- a/src/coreclr/debug/runtimeinfo/datadescriptor.h +++ b/src/coreclr/debug/runtimeinfo/datadescriptor.h @@ -107,9 +107,17 @@ CDAC_TYPE_BEGIN(Thread) CDAC_TYPE_INDETERMINATE(Thread) CDAC_TYPE_FIELD(Thread, /*uint32*/, Id, cdac_offsets::Id) CDAC_TYPE_FIELD(Thread, /*nuint*/, OSId, cdac_offsets::OSId) +CDAC_TYPE_FIELD(Thread, /*uint32*/, State, cdac_offsets::State) +CDAC_TYPE_FIELD(Thread, /*uint32*/, PreemptiveGCDisabled, cdac_offsets::PreemptiveGCDisabled) +CDAC_TYPE_FIELD(Thread, /*pointer*/, AllocContext, cdac_offsets::AllocContext) +CDAC_TYPE_FIELD(Thread, /*pointer*/, Frame, cdac_offsets::Frame) +CDAC_TYPE_FIELD(Thread, /*pointer*/, ExceptionTracker, cdac_offsets::ExceptionTracker) CDAC_TYPE_FIELD(Thread, GCHandle, GCHandle, cdac_offsets::ExposedObject) CDAC_TYPE_FIELD(Thread, GCHandle, LastThrownObject, cdac_offsets::LastThrownObject) CDAC_TYPE_FIELD(Thread, pointer, LinkNext, cdac_offsets::Link) +#ifndef TARGET_UNIX +CDAC_TYPE_FIELD(Thread, /*pointer*/, TEB, cdac_offsets::TEB) +#endif CDAC_TYPE_END(Thread) CDAC_TYPE_BEGIN(ThreadStore) @@ -122,6 +130,21 @@ CDAC_TYPE_FIELD(ThreadStore, /*int32*/, PendingCount, cdac_offsets: CDAC_TYPE_FIELD(ThreadStore, /*int32*/, DeadCount, cdac_offsets::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) @@ -129,6 +152,7 @@ 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) diff --git a/src/coreclr/vm/appdomain.hpp b/src/coreclr/vm/appdomain.hpp index c0a22f8369aad..83d28792d556a 100644 --- a/src/coreclr/vm/appdomain.hpp +++ b/src/coreclr/vm/appdomain.hpp @@ -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; diff --git a/src/coreclr/vm/exstate.h b/src/coreclr/vm/exstate.h index 3ef7cb95fed1d..19fe3af779c76 100644 --- a/src/coreclr/vm/exstate.h +++ b/src/coreclr/vm/exstate.h @@ -12,6 +12,7 @@ class DebuggerExState; class EHClauseInfo; #include "exceptionhandling.h" +#include "cdacoffsets.h" #if !defined(FEATURE_EH_FUNCLETS) // ExInfo contains definitions for 32bit @@ -50,6 +51,8 @@ class ThreadExceptionState // ExceptionTracker or the ExInfo as appropriate for the platform friend class ProfToEEInterfaceImpl; + template friend struct ::cdac_offsets; + #ifdef FEATURE_EH_FUNCLETS friend class ExceptionTracker; friend struct ExInfo; diff --git a/src/coreclr/vm/threads.h b/src/coreclr/vm/threads.h index a83a1bc20bbc6..ba5e2265766c8 100644 --- a/src/coreclr/vm/threads.h +++ b/src/coreclr/vm/threads.h @@ -3971,9 +3971,25 @@ struct cdac_offsets { 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().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 diff --git a/src/native/managed/cdacreader/src/Constants.cs b/src/native/managed/cdacreader/src/Constants.cs index be4fd418b802d..c4003c9630656 100644 --- a/src/native/managed/cdacreader/src/Constants.cs +++ b/src/native/managed/cdacreader/src/Constants.cs @@ -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); } } diff --git a/src/native/managed/cdacreader/src/Contracts/Thread.cs b/src/native/managed/cdacreader/src/Contracts/Thread.cs index 58621f005d437..fa399ebc45498 100644 --- a/src/native/managed/cdacreader/src/Contracts/Thread.cs +++ b/src/native/managed/cdacreader/src/Contracts/Thread.cs @@ -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 @@ -85,8 +105,29 @@ ThreadStoreCounts IThread.GetThreadCounts() ThreadData IThread.GetThreadData(TargetPointer threadPointer) { Data.Thread thread = _target.ProcessedData.GetOrAdd(threadPointer); + + // Exception tracker is a pointer when EH funclets are enabled + TargetPointer address = _target.ReadGlobal(Constants.Globals.FeatureEHFunclets) != 0 + ? _target.ReadPointer(thread.ExceptionTracker) + : thread.ExceptionTracker; + TargetPointer firstNestedException = TargetPointer.Null; + if (address != TargetPointer.Null) + { + Data.ExceptionInfo exceptionInfo = _target.ProcessedData.GetOrAdd(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)); } diff --git a/src/native/managed/cdacreader/src/Data/ExceptionInfo.cs b/src/native/managed/cdacreader/src/Data/ExceptionInfo.cs new file mode 100644 index 0000000000000..1a26b124ca0dc --- /dev/null +++ b/src/native/managed/cdacreader/src/Data/ExceptionInfo.cs @@ -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 +{ + static ExceptionInfo IData.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; } +} diff --git a/src/native/managed/cdacreader/src/Data/GCAllocContext.cs b/src/native/managed/cdacreader/src/Data/GCAllocContext.cs new file mode 100644 index 0000000000000..02b22b0e335da --- /dev/null +++ b/src/native/managed/cdacreader/src/Data/GCAllocContext.cs @@ -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 +{ + static GCAllocContext IData.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; } +} diff --git a/src/native/managed/cdacreader/src/Data/Thread.cs b/src/native/managed/cdacreader/src/Data/Thread.cs index 3f29b90126a36..8995d0ede2179 100644 --- a/src/native/managed/cdacreader/src/Data/Thread.cs +++ b/src/native/managed/cdacreader/src/Data/Thread.cs @@ -13,9 +13,35 @@ public Thread(Target target, TargetPointer address) Target.TypeInfo type = target.GetTypeInfo(DataType.Thread); Id = target.Read(address + (ulong)type.Fields[nameof(Id)].Offset); + OSId = target.ReadNUInt(address + (ulong)type.Fields[nameof(OSId)].Offset); + State = target.Read(address + (ulong)type.Fields[nameof(State)].Offset); + PreemptiveGCDisabled = target.Read(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(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; } } diff --git a/src/native/managed/cdacreader/src/DataType.cs b/src/native/managed/cdacreader/src/DataType.cs index c301c6a008d72..68e41240e436b 100644 --- a/src/native/managed/cdacreader/src/DataType.cs +++ b/src/native/managed/cdacreader/src/DataType.cs @@ -22,4 +22,6 @@ public enum DataType GCHandle, Thread, ThreadStore, + GCAllocContext, + ExceptionInfo, } diff --git a/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs b/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs index 04fe0848c0395..96b058f5a7205 100644 --- a/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs +++ b/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs @@ -115,6 +115,23 @@ 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) @@ -122,8 +139,7 @@ public unsafe int GetThreadData(ulong thread, DacpThreadData* data) 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; diff --git a/src/native/managed/cdacreader/src/Target.cs b/src/native/managed/cdacreader/src/Target.cs index 83f637034dc64..d42f581581884 100644 --- a/src/native/managed/cdacreader/src/Target.cs +++ b/src/native/managed/cdacreader/src/Target.cs @@ -11,17 +11,23 @@ namespace Microsoft.Diagnostics.DataContractReader; -public struct TargetPointer +public readonly struct TargetPointer { public static TargetPointer Null = new(0); - public ulong Value; + public readonly ulong Value; public TargetPointer(ulong value) => Value = value; public static implicit operator ulong(TargetPointer p) => p.Value; public static implicit operator TargetPointer(ulong v) => new TargetPointer(v); } +public readonly struct TargetNUInt +{ + public readonly ulong Value; + public TargetNUInt(ulong value) => Value = value; +} + /// /// Representation of the target under inspection /// @@ -266,24 +272,37 @@ public TargetPointer ReadPointer(ulong address) return pointer; } + public TargetNUInt ReadNUInt(ulong address) + { + if (!TryReadNUInt(address, _config, _reader, out ulong value)) + throw new InvalidOperationException($"Failed to read nuint at 0x{address:x8}."); + + return new TargetNUInt(value); + } + private static bool TryReadPointer(ulong address, Configuration config, Reader reader, out TargetPointer pointer) { pointer = TargetPointer.Null; - - Span buffer = stackalloc byte[config.PointerSize]; - if (reader.ReadFromTarget(address, buffer) < 0) + if (!TryReadNUInt(address, config, reader, out ulong value)) return false; + pointer = new TargetPointer(value); + return true; + } + + private static bool TryReadNUInt(ulong address, Configuration config, Reader reader, out ulong value) + { + value = 0; if (config.PointerSize == sizeof(uint) && TryRead(address, config.IsLittleEndian, reader, out uint value32)) { - pointer = new TargetPointer(value32); + value = value32; return true; } else if (config.PointerSize == sizeof(ulong) && TryRead(address, config.IsLittleEndian, reader, out ulong value64)) { - pointer = new TargetPointer(value64); + value = value64; return true; }