1
0
Fork 0
mirror of https://github.com/VSadov/Satori.git synced 2025-06-10 01:50:53 +09:00

[cdac] Implement ISOSDacInterface.GetThreadData (#103324)

- Implement `GetThreadData` in `Thread` contract
- Finish implementing `ISOSDacInterface::GetThreadData` in cDAC
- Add `ExceptionInfo` to cDAC types
This commit is contained in:
Elinor Fung 2024-06-18 10:00:10 -07:00 committed by GitHub
parent 01172dba2e
commit 547e69e0b7
Signed by: github
GPG key ID: B5690EEEBB952194
13 changed files with 204 additions and 65 deletions

View file

@ -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 (

View file

@ -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)
@ -122,6 +130,21 @@ 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)
@ -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)

View file

@ -1675,10 +1675,10 @@ public:
}
#endif
private:
// The one and only AppDomain
SPTR_DECL(AppDomain, m_pTheAppDomain);
private:
SString m_friendlyName;
PTR_Assembly m_pRootAssembly;

View file

@ -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<typename T> friend struct ::cdac_offsets;
#ifdef FEATURE_EH_FUNCLETS
friend class ExceptionTracker;
friend struct ExInfo;

View file

@ -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

View file

@ -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);
}
}

View file

@ -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 @@ internal readonly struct Thread_1 : IThread
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));
}

View file

@ -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; }
}

View file

@ -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; }
}

View file

@ -13,9 +13,35 @@ internal sealed class Thread : IData<Thread>
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; }
}

View file

@ -22,4 +22,6 @@ public enum DataType
GCHandle,
Thread,
ThreadStore,
GCAllocContext,
ExceptionInfo,
}

View file

@ -115,6 +115,23 @@ internal sealed partial class SOSDacImpl : ISOSDacInterface, ISOSDacInterface9
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 @@ internal sealed partial class SOSDacImpl : ISOSDacInterface, ISOSDacInterface9
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;

View file

@ -11,17 +11,23 @@ using Microsoft.Diagnostics.DataContractReader.Data;
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;
}
/// <summary>
/// Representation of the target under inspection
/// </summary>
@ -266,24 +272,37 @@ public sealed unsafe class Target
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<byte> 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;
}