From b98afa8a7ec7805e81b6c165f4f2333269235c5d Mon Sep 17 00:00:00 2001 From: Elinor Fung Date: Thu, 16 May 2024 16:50:15 -0700 Subject: [PATCH 01/62] Implement GetThreadStoreData in cDAC --- docs/design/datacontracts/Thread.md | 34 +++---- .../contract_csharp_api_design.cs | 94 ++++--------------- .../debug/runtimeinfo/datadescriptor.h | 10 +- src/coreclr/inc/slist.h | 4 + src/coreclr/vm/threads.h | 6 +- .../managed/cdacreader/src/Constants.cs | 3 + .../cdacreader/src/Contracts/Thread.cs | 47 ++++++++-- .../cdacreader/src/Data/ThreadStore.cs | 18 ++-- .../cdacreader/src/Legacy/SOSDacImpl.cs | 20 +++- src/native/managed/cdacreader/src/Target.cs | 12 +++ 10 files changed, 138 insertions(+), 110 deletions(-) diff --git a/docs/design/datacontracts/Thread.md b/docs/design/datacontracts/Thread.md index 2910dc8dbb8200..7febe0716e0ce6 100644 --- a/docs/design/datacontracts/Thread.md +++ b/docs/design/datacontracts/Thread.md @@ -2,15 +2,16 @@ This contract is for reading and iterating the threads of the process. -## Data structures defined by contract +## APIs of contract + ``` csharp -record struct DacThreadStoreData ( +record struct ThreadStoreData ( int ThreadCount, TargetPointer FirstThread, TargetPointer FinalizerThread, TargetPointer GcThread); -record struct DacThreadStoreCounts ( +record struct ThreadStoreCounts ( int UnstartedThreadCount, int BackgroundThreadCount, int PendingThreadCount, @@ -75,7 +76,7 @@ enum ThreadState TS_Detached = 0x80000000, // Thread was detached by DllMain } -record struct DacThreadData ( +record struct ThreadData ( uint ThreadId; TargetNUint OsThreadId; ThreadState State; @@ -90,11 +91,10 @@ record struct DacThreadData ( ); ``` -## Apis of contract ``` csharp -DacThreadStoreData GetThreadStoreData(); -DacThreadStoreCounts GetThreadCounts(); -DacThreadData GetThreadData(TargetPointer threadPointer); +ThreadStoreData GetThreadStoreData(); +ThreadStoreCounts GetThreadCounts(); +ThreadData GetThreadData(TargetPointer threadPointer); TargetPointer GetNestedExceptionInfo(TargetPointer nestedExceptionPointer, out TargetPointer nextNestedException); TargetPointer GetManagedThreadObject(TargetPointer threadPointer); ``` @@ -106,26 +106,26 @@ TargetPointer GetManagedThreadObject(TargetPointer threadPointer); ``` csharp SListReader ThreadListReader = Contracts.SList.GetReader("Thread"); -DacThreadStoreData GetThreadStoreData() +ThreadStoreData GetThreadStoreData() { - TargetPointer threadStore = Target.ReadGlobalTargetPointer("s_pThreadStore"); + TargetPointer threadStore = Target.ReadGlobalPointer("s_pThreadStore"); var runtimeThreadStore = new ThreadStore(Target, threadStore); TargetPointer firstThread = ThreadListReader.GetHead(runtimeThreadStore.SList.Pointer); - return new DacThreadStoreData( + return new ThreadStoreData( ThreadCount : runtimeThreadStore.m_ThreadCount, FirstThread: firstThread, - FinalizerThread: Target.ReadGlobalTargetPointer("g_pFinalizerThread"), - GcThread: Target.ReadGlobalTargetPointer("g_pSuspensionThread")); + FinalizerThread: Target.ReadGlobalPointer("g_pFinalizerThread"), + GCThread: Target.ReadGlobalPointer("g_pSuspensionThread")); } DacThreadStoreCounts GetThreadCounts() { - TargetPointer threadStore = Target.ReadGlobalTargetPointer("s_pThreadStore"); + TargetPointer threadStore = Target.ReadGlobalPointer("s_pThreadStore"); var runtimeThreadStore = new ThreadStore(Target, threadStore); - return new DacThreadStoreCounts( + return new ThreadStoreCounts( ThreadCount : runtimeThreadStore.m_ThreadCount, UnstartedThreadCount : runtimeThreadStore.m_UnstartedThreadCount, BackgroundThreadCount : runtimeThreadStore.m_BackgroundThreadCount, @@ -133,7 +133,7 @@ DacThreadStoreCounts GetThreadCounts() DeadThreadCount: runtimeThreadStore.m_DeadThreadCount, } -DacThreadData GetThreadData(TargetPointer threadPointer) +ThreadData GetThreadData(TargetPointer threadPointer) { var runtimeThread = new Thread(Target, threadPointer); @@ -150,7 +150,7 @@ DacThreadData GetThreadData(TargetPointer threadPointer) firstNestedException = runtimeThread.m_ExceptionState.m_currentExInfo.m_pPrevNestedInfo; } - return new DacThread( + return new ThreadData( ThreadId : runtimeThread.m_ThreadId, OsThreadId : (OsThreadId)runtimeThread.m_OSThreadId, State : (ThreadState)runtimeThread.m_State, diff --git a/docs/design/datacontracts/contract_csharp_api_design.cs b/docs/design/datacontracts/contract_csharp_api_design.cs index 062c04806003ca..68230820455c88 100644 --- a/docs/design/datacontracts/contract_csharp_api_design.cs +++ b/docs/design/datacontracts/contract_csharp_api_design.cs @@ -40,7 +40,7 @@ struct TargetPointer struct TargetNInt { public long Value; - // Add a full set of operators to support arithmetic as well as casting to/from TargetPointer + // Add a full set of operators to support arithmetic as well as casting to/from TargetPointer } struct TargetNUInt @@ -72,84 +72,37 @@ struct FieldLayout public FieldType Type; } - interface IAlgorithmContract - { - void Init(); - } - interface IContract { string Name { get; } uint Version { get; } } - class Target + + class Target { // Users of the data contract may adjust this number to force re-reading of all data public int CurrentEpoch = 0; - sbyte ReadInt8(TargetPointer pointer); - byte ReadUInt8(TargetPointer pointer); - short ReadInt16(TargetPointer pointer); - ushort ReadUInt16(TargetPointer pointer); - int ReadInt32(TargetPointer pointer); - uint ReadUInt32(TargetPointer pointer); - long ReadInt64(TargetPointer pointer); - ulong ReadUInt64(TargetPointer pointer); - TargetPointer ReadTargetPointer(TargetPointer pointer); - TargetNInt ReadNInt(TargetPointer pointer); - TargetNUInt ReadNUint(TargetPointer pointer); + public T Read(ulong address) where T : unmanaged, IBinaryInteger, IMinMaxValue; + TargetPointer ReadPointer(ulong address); + byte[] ReadByteArray(TargetPointer pointer, ulong size); void FillByteArray(TargetPointer pointer, byte[] array, ulong size); - bool TryReadInt8(TargetPointer pointer, out sbyte value); - bool TryReadUInt8(TargetPointer pointer, out byte value); - bool TryReadInt16(TargetPointer pointer, out short value); - bool TryReadUInt16(TargetPointer pointer, out ushort value); - bool TryReadInt32(TargetPointer pointer, out int value); - bool TryReadUInt32(TargetPointer pointer, out uint value); - bool TryReadInt64(TargetPointer pointer, out long value); - bool TryReadUInt64(TargetPointer pointer, out ulong value); - bool TryReadTargetPointer(TargetPointer pointer, out TargetPointer value); - bool TryReadNInt(TargetPointer pointer, out TargetNInt value); - bool TryReadNUInt(TargetPointer pointer, out TargetNUInt value); - bool TryReadByteArray(TargetPointer pointer, ulong size, out byte[] value); - bool TryFillByteArray(TargetPointer pointer, byte[] array, ulong size); - // If pointer is 0, then the return value will be 0 TargetPointer GetTargetPointerForField(TargetPointer pointer, FieldLayout fieldLayout); - sbyte ReadGlobalInt8(string globalName); - byte ReadGlobalUInt8(string globalName); - short ReadGlobalInt16(string globalName); - ushort ReadGlobalUInt16(string globalName); - int ReadGlobalInt32(string globalName); - uint ReadGlobalUInt32(string globalName); - long ReadGlobalInt64(string globalName); - ulong ReadGlobalUInt64(string globalName); - TargetPointer ReadGlobalTargetPointer(string globalName); - - bool TryReadGlobalInt8(string globalName, out sbyte value); - bool TryReadGlobalUInt8(string globalName, out byte value); - bool TryReadGlobalInt16(string globalName, out short value); - bool TryReadGlobalUInt16(string globalName, out ushort value); - bool TryReadGlobalInt32(string globalName, out int value); - bool TryReadGlobalUInt32(string globalName, out uint value); - bool TryReadGlobalInt64(string globalName, out long value); - bool TryReadGlobalUInt64(string globalName, out ulong value); - bool TryReadGlobalTargetPointer(string globalName, out TargetPointer value); - - Contracts Contract { get; } - - partial class Contracts - { - FieldLayout GetFieldLayout(string typeName, string fieldName); - bool TryGetFieldLayout(string typeName, string fieldName, out FieldLayout layout); - int GetTypeSize(string typeName); - bool TryGetTypeSize(string typeName, out int size); + T ReadGlobal(string globalName) where T : unmanaged, IBinaryInteger, IMinMaxValue; + TargetPointer ReadGlobalPointer(string globalName); - object GetContract(string contractName); - bool TryGetContract(string contractName, out object contract); + Contracts.Registry Contracts { get; } + } + // Types defined by contracts live here + namespace Contracts + { + class Registry + { // Every contract that is defined has a field here. As an example this document defines a MethodTableContract // If the contract is not supported by the runtime in use, then the implementation of the contract will be the base type which // is defined to throw if it is ever used. @@ -157,15 +110,6 @@ partial class Contracts // List of contracts will be inserted here by source generator MethodTableContract MethodTableContract; } - } - - // Types defined by contracts live here - namespace ContractDefinitions - { - class CompositeContract - { - List> Subcontracts; - } class DataStructureContract { @@ -173,8 +117,8 @@ class DataStructureContract List> FieldData; } - // Insert Algorithmic Contract definitions here - class MethodTableContract + // Insert contract definitions here + interface MethodTableContract : IContract { public virtual int DynamicTypeID(TargetPointer methodTablePointer) { throw new NotImplementedException(); } public virtual int BaseSize(TargetPointer methodTablePointer) { throw new NotImplementedException(); } @@ -207,7 +151,7 @@ public class FeatureFlags_2 } [DataContractAlgorithm(1)] - class MethodTableContract_1 : ContractDefinitions.MethodTableContract, IAlgorithmContract + readonly struct MethodTableContract_1 : Contracts.MethodTableContract { DataContracts.Target Target; readonly uint ContractVersion; @@ -219,7 +163,7 @@ class MethodTableContract_1 : ContractDefinitions.MethodTableContract, IAlgorith // This is used for version 2 and 3 of the contract, where the dynamic type id is no longer present, and baseSize has a new limitation in that it can only be a value up to 0x1FFFFFFF in v3 [DataContractAlgorithm(2, 3)] - class MethodTableContract_2 : ContractDefinitions.MethodTableContract, IAlgorithmContract + readonly struct MethodTableContract_2 : Contracts.MethodTableContract { DataContracts.Target Target; readonly uint ContractVersion; diff --git a/src/coreclr/debug/runtimeinfo/datadescriptor.h b/src/coreclr/debug/runtimeinfo/datadescriptor.h index 2687ceff553534..e4f75383f76835 100644 --- a/src/coreclr/debug/runtimeinfo/datadescriptor.h +++ b/src/coreclr/debug/runtimeinfo/datadescriptor.h @@ -111,8 +111,12 @@ CDAC_TYPE_END(Thread) CDAC_TYPE_BEGIN(ThreadStore) CDAC_TYPE_INDETERMINATE(ThreadStore) -CDAC_TYPE_FIELD(ThreadStore, /*omit type*/, ThreadCount, cdac_offsets::ThreadCount) -CDAC_TYPE_FIELD(ThreadStore, /*omit type*/, ThreadList, cdac_offsets::ThreadList) +CDAC_TYPE_FIELD(ThreadStore, /*SLink*/, FirstThreadLink, cdac_offsets::FirstThreadLink) +CDAC_TYPE_FIELD(ThreadStore, /*int32*/, ThreadCount, cdac_offsets::ThreadCount) +CDAC_TYPE_FIELD(ThreadStore, /*int32*/, UnstartedCount, cdac_offsets::UnstartedCount) +CDAC_TYPE_FIELD(ThreadStore, /*int32*/, BackgroundCount, cdac_offsets::BackgroundCount) +CDAC_TYPE_FIELD(ThreadStore, /*int32*/, PendingCount, cdac_offsets::PendingCount) +CDAC_TYPE_FIELD(ThreadStore, /*int32*/, DeadCount, cdac_offsets::DeadCount) CDAC_TYPE_END(ThreadStore) CDAC_TYPE_BEGIN(GCHandle) @@ -123,6 +127,8 @@ CDAC_TYPES_END() CDAC_GLOBALS_BEGIN() CDAC_GLOBAL_POINTER(ThreadStore, &ThreadStore::s_pThreadStore) +CDAC_GLOBAL_POINTER(FinalizerThread, &::g_pFinalizerThread) +CDAC_GLOBAL_POINTER(GCThread, &::g_pSuspensionThread) #if FEATURE_EH_FUNCLETS CDAC_GLOBAL(FeatureEHFunclets, uint8, 1) #else diff --git a/src/coreclr/inc/slist.h b/src/coreclr/inc/slist.h index 805413fd3da349..e48a078adda5d0 100644 --- a/src/coreclr/inc/slist.h +++ b/src/coreclr/inc/slist.h @@ -26,6 +26,8 @@ #ifndef _H_SLIST_ #define _H_SLIST_ +#include "cdacoffsets.h" + //------------------------------------------------------------------ // struct SLink, to use a singly linked list // have a data member m_Link of type SLink in your class @@ -118,6 +120,8 @@ class SList PTR_SLink m_pHead; PTR_SLink m_pTail; + template friend struct ::cdac_offsets; + // get the list node within the object static SLink* GetLink (T* pLink) { diff --git a/src/coreclr/vm/threads.h b/src/coreclr/vm/threads.h index d95b90c6c28911..fd3489c79a40ef 100644 --- a/src/coreclr/vm/threads.h +++ b/src/coreclr/vm/threads.h @@ -4307,8 +4307,12 @@ class ThreadStore template<> struct cdac_offsets { - static constexpr size_t ThreadList = offsetof(ThreadStore, m_ThreadList); + static constexpr size_t FirstThreadLink = offsetof(ThreadStore, m_ThreadList) + offsetof(ThreadList, m_link); static constexpr size_t ThreadCount = offsetof(ThreadStore, m_ThreadCount); + static constexpr size_t UnstartedCount = offsetof(ThreadStore, m_UnstartedThreadCount); + static constexpr size_t BackgroundCount = offsetof(ThreadStore, m_BackgroundThreadCount); + static constexpr size_t PendingCount = offsetof(ThreadStore, m_PendingThreadCount); + static constexpr size_t DeadCount = offsetof(ThreadStore, m_DeadThreadCount); }; struct TSSuspendHelper { diff --git a/src/native/managed/cdacreader/src/Constants.cs b/src/native/managed/cdacreader/src/Constants.cs index a4874ce179e16e..be4fd418b802d5 100644 --- a/src/native/managed/cdacreader/src/Constants.cs +++ b/src/native/managed/cdacreader/src/Constants.cs @@ -9,6 +9,9 @@ internal static class Globals { // See src/coreclr/debug/runtimeinfo/datadescriptor.h internal const string ThreadStore = nameof(ThreadStore); + internal const string FinalizerThread = nameof(FinalizerThread); + internal const string GCThread = nameof(GCThread); + 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 30187567fe5615..3839bb42006a5d 100644 --- a/src/native/managed/cdacreader/src/Contracts/Thread.cs +++ b/src/native/managed/cdacreader/src/Contracts/Thread.cs @@ -5,17 +5,25 @@ namespace Microsoft.Diagnostics.DataContractReader.Contracts; -// TODO: [cdac] Add other counts / threads internal record struct ThreadStoreData( int ThreadCount, - TargetPointer FirstThread); + TargetPointer FirstThread, + TargetPointer FinalizerThread, + TargetPointer GCThread); + +internal record struct ThreadStoreCounts( + int UnstartedThreadCount, + int BackgroundThreadCount, + int PendingThreadCount, + int DeadThreadCount); internal interface IThread : IContract { static string IContract.Name { get; } = nameof(Thread); static IContract IContract.Create(Target target, int version) { - TargetPointer threadStore = target.ReadGlobalPointer(Constants.Globals.ThreadStore); + TargetPointer threadStorePointer = target.ReadGlobalPointer(Constants.Globals.ThreadStore); + TargetPointer threadStore = target.ReadPointer(threadStorePointer); return version switch { 1 => new Thread_1(target, threadStore), @@ -24,6 +32,7 @@ static IContract IContract.Create(Target target, int version) } public virtual ThreadStoreData GetThreadStoreData() => throw new NotImplementedException(); + public virtual ThreadStoreCounts GetThreadCounts() => throw new NotImplementedException(); } internal readonly struct Thread : IThread @@ -35,24 +44,50 @@ static IContract IContract.Create(Target target, int version) { private readonly Target _target; private readonly TargetPointer _threadStoreAddr; + private readonly ulong _threadLinkOffset; internal Thread_1(Target target, TargetPointer threadStore) { _target = target; _threadStoreAddr = threadStore; + + Target.TypeInfo type = _target.GetTypeInfo(DataType.Thread); + _threadLinkOffset = (ulong)type.Fields["LinkNext"].Offset; } ThreadStoreData IThread.GetThreadStoreData() { Data.ThreadStore? threadStore; - if (!_target.ProcessedData.TryGet(_threadStoreAddr.Value, out threadStore)) + if (!_target.ProcessedData.TryGet(_threadStoreAddr, out threadStore)) + { + threadStore = new Data.ThreadStore(_target, _threadStoreAddr); + + // Still okay if processed data is already registered by someone else + _ = _target.ProcessedData.TryRegister(_threadStoreAddr, threadStore); + } + + return new ThreadStoreData( + threadStore.ThreadCount, + new TargetPointer(threadStore.FirstThreadLink - _threadLinkOffset), + _target.ReadGlobalPointer(Constants.Globals.FinalizerThread), + _target.ReadGlobalPointer(Constants.Globals.GCThread)); + } + + ThreadStoreCounts IThread.GetThreadCounts() + { + Data.ThreadStore? threadStore; + if (!_target.ProcessedData.TryGet(_threadStoreAddr, 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); + _ = _target.ProcessedData.TryRegister(_threadStoreAddr, threadStore); } - return new ThreadStoreData(threadStore.ThreadCount, threadStore.FirstThread); + return new ThreadStoreCounts( + threadStore.UnstartedCount, + threadStore.BackgroundCount, + threadStore.PendingCount, + threadStore.DeadCount); } } diff --git a/src/native/managed/cdacreader/src/Data/ThreadStore.cs b/src/native/managed/cdacreader/src/Data/ThreadStore.cs index 1ba13596359b50..ae131cc4ae0b29 100644 --- a/src/native/managed/cdacreader/src/Data/ThreadStore.cs +++ b/src/native/managed/cdacreader/src/Data/ThreadStore.cs @@ -5,16 +5,22 @@ namespace Microsoft.Diagnostics.DataContractReader.Data; internal sealed class ThreadStore { - public ThreadStore(Target target, TargetPointer pointer) + public ThreadStore(Target target, TargetPointer address) { Target.TypeInfo type = target.GetTypeInfo(DataType.ThreadStore); - TargetPointer addr = target.ReadPointer(pointer.Value); - ThreadCount = target.Read(addr.Value + (ulong)type.Fields[nameof(ThreadCount)].Offset); - FirstThread = TargetPointer.Null; + ThreadCount = target.Read(address + (ulong)type.Fields[nameof(ThreadCount)].Offset); + FirstThreadLink = target.ReadPointer(address + (ulong)type.Fields[nameof(FirstThreadLink)].Offset); + UnstartedCount = target.Read(address + (ulong)type.Fields[nameof(UnstartedCount)].Offset); + BackgroundCount = target.Read(address + (ulong)type.Fields[nameof(BackgroundCount)].Offset); + PendingCount = target.Read(address + (ulong)type.Fields[nameof(PendingCount)].Offset); + DeadCount = target.Read(address + (ulong)type.Fields[nameof(DeadCount)].Offset); } public int ThreadCount { get; init; } - - public TargetPointer FirstThread { get; init; } + public TargetPointer FirstThreadLink { get; init; } + public int UnstartedCount { get; init; } + public int BackgroundCount { get; init; } + public int PendingCount { get; init; } + public int DeadCount { get; init; } } diff --git a/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs b/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs index 5cea71a68b8a90..3ea7a7d86d6d68 100644 --- a/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs +++ b/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs @@ -11,6 +11,11 @@ namespace Microsoft.Diagnostics.DataContractReader.Legacy; /// Implementation of ISOSDacInterface* interfaces intended to be passed out to consumers /// interacting with the DAC via those COM interfaces. /// +/// +/// Functions on are defined with PreserveSig. Target and Contracts +/// throw on errors. Implementations in this class should wrap logic in a try-catch and return the +/// corresponding error code. +/// [GeneratedComClass] internal sealed partial class SOSDacImpl : ISOSDacInterface, ISOSDacInterface9 { @@ -114,15 +119,24 @@ public unsafe int GetThreadStoreData(DacpThreadStoreData* data) Contracts.IThread thread = _target.Contracts.Thread; Contracts.ThreadStoreData threadStoreData = thread.GetThreadStoreData(); data->threadCount = threadStoreData.ThreadCount; - data->firstThread = threadStoreData.FirstThread.Value; - data->fHostConfig = 0; + data->firstThread = threadStoreData.FirstThread; + data->finalizerThread = threadStoreData.FinalizerThread; + data->gcThread = threadStoreData.GCThread; + + Contracts.ThreadStoreCounts threadCounts = thread.GetThreadCounts(); + data->unstartedThreadCount = threadCounts.UnstartedThreadCount; + data->backgroundThreadCount = threadCounts.BackgroundThreadCount; + data->pendingThreadCount = threadCounts.PendingThreadCount; + data->deadThreadCount = threadCounts.DeadThreadCount; + + data->fHostConfig = 0; // Always 0 for non-Framework } catch (Exception ex) { return ex.HResult; } - return HResults.E_NOTIMPL; + return HResults.S_OK; } public unsafe int GetTLSIndex(uint* pIndex) => HResults.E_NOTIMPL; diff --git a/src/native/managed/cdacreader/src/Target.cs b/src/native/managed/cdacreader/src/Target.cs index 0202efaea38eda..1e7a84f976a579 100644 --- a/src/native/managed/cdacreader/src/Target.cs +++ b/src/native/managed/cdacreader/src/Target.cs @@ -15,8 +15,20 @@ public struct TargetPointer public 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); } +/// +/// Representation of the target under inspection +/// +/// +/// This class provides APIs used by contracts for reading from the target and getting type and globals +/// information based on the target's contract descriptor. Like the contracts themselves in cdacreader, +/// these are throwing APIs. Any callers at the boundaries (for example, unmanaged entry points, COM) +/// should handle any exceptions. +/// public sealed unsafe class Target { public record struct TypeInfo From 890f9c6a6c3a73886dfe6c3e283d969cec92d422 Mon Sep 17 00:00:00 2001 From: Elinor Fung Date: Fri, 17 May 2024 13:58:36 -0700 Subject: [PATCH 02/62] Add placeholder for getting thread data --- docs/design/datacontracts/Thread.md | 2 +- src/coreclr/debug/daccess/dacimpl.h | 1 + src/coreclr/debug/daccess/request.cpp | 48 +++++++++++++++++-- .../debug/runtimeinfo/datadescriptor.h | 3 ++ src/coreclr/vm/threads.h | 3 ++ .../cdacreader/src/Contracts/Thread.cs | 36 +++++++++++++- .../managed/cdacreader/src/Data/Thread.cs | 18 +++++++ .../cdacreader/src/Legacy/SOSDacImpl.cs | 19 +++++++- 8 files changed, 122 insertions(+), 8 deletions(-) create mode 100644 src/native/managed/cdacreader/src/Data/Thread.cs diff --git a/docs/design/datacontracts/Thread.md b/docs/design/datacontracts/Thread.md index 7febe0716e0ce6..92382eacf32d53 100644 --- a/docs/design/datacontracts/Thread.md +++ b/docs/design/datacontracts/Thread.md @@ -159,7 +159,7 @@ ThreadData GetThreadData(TargetPointer threadPointer) AllocContextLimit : thread.m_alloc_context.alloc_limit, Frame : thread.m_pFrame, TEB : thread.Has_m_pTEB ? thread.m_pTEB : TargetPointer.Null, - LastThreadObjectHandle : new DacGCHandle(thread.m_LastThrownObjectHandle), + LastThrownObjectHandle : new DacGCHandle(thread.m_LastThrownObjectHandle), FirstNestedException : firstNestedException, NextThread : ThreadListReader.GetHead.GetNext(threadPointer) ); diff --git a/src/coreclr/debug/daccess/dacimpl.h b/src/coreclr/debug/daccess/dacimpl.h index 8b1771d7132e36..4b05e401a06b1b 100644 --- a/src/coreclr/debug/daccess/dacimpl.h +++ b/src/coreclr/debug/daccess/dacimpl.h @@ -1229,6 +1229,7 @@ class ClrDataAccess HRESULT Initialize(void); + HRESULT GetThreadDataImpl(CLRDATA_ADDRESS threadAddr, struct DacpThreadData *threadData); HRESULT GetThreadStoreDataImpl(struct DacpThreadStoreData *data); BOOL IsExceptionFromManagedCode(EXCEPTION_RECORD * pExceptionRecord); diff --git a/src/coreclr/debug/daccess/request.cpp b/src/coreclr/debug/daccess/request.cpp index 4657aadf22965f..5353c93a892289 100644 --- a/src/coreclr/debug/daccess/request.cpp +++ b/src/coreclr/debug/daccess/request.cpp @@ -761,11 +761,52 @@ ClrDataAccess::GetHeapAllocData(unsigned int count, struct DacpGenerationAllocDa return hr; } -HRESULT -ClrDataAccess::GetThreadData(CLRDATA_ADDRESS threadAddr, struct DacpThreadData *threadData) +HRESULT ClrDataAccess::GetThreadData(CLRDATA_ADDRESS threadAddr, struct DacpThreadData* threadData) { SOSDacEnter(); + if (m_cdacSos != NULL) + { + hr = m_cdacSos->GetThreadData(threadAddr, threadData); + if (FAILED(hr)) + { + hr = GetThreadDataImpl(threadAddr, threadData); + } +#ifdef _DEBUG + else + { + DacpThreadData threadDataLocal; + HRESULT hrLocal = GetThreadDataImpl(threadAddr, &threadDataLocal); + _ASSERTE(hr == hrLocal); + _ASSERTE(threadData->corThreadId == threadDataLocal.corThreadId); + _ASSERTE(threadData->osThreadId == threadDataLocal.osThreadId); + _ASSERTE(threadData->state == threadDataLocal.state); + _ASSERTE(threadData->preemptiveGCDisabled == threadDataLocal.preemptiveGCDisabled); + _ASSERTE(threadData->allocContextPtr == threadDataLocal.allocContextPtr); + _ASSERTE(threadData->allocContextLimit == threadDataLocal.allocContextLimit); + _ASSERTE(threadData->context == threadDataLocal.context); + _ASSERTE(threadData->domain == threadDataLocal.domain); + _ASSERTE(threadData->pFrame == threadDataLocal.pFrame); + _ASSERTE(threadData->lockCount == threadDataLocal.lockCount); + _ASSERTE(threadData->firstNestedException == threadDataLocal.firstNestedException); + _ASSERTE(threadData->teb == threadDataLocal.teb); + _ASSERTE(threadData->fiberData == threadDataLocal.fiberData); + _ASSERTE(threadData->lastThrownObjectHandle == threadDataLocal.lastThrownObjectHandle); + _ASSERTE(threadData->nextThread == threadDataLocal.nextThread);; + } +#endif + } + else + { + hr = GetThreadDataImpl(threadAddr, threadData); + } + + SOSDacLeave(); + return hr; +} + +HRESULT ClrDataAccess::GetThreadDataImpl(CLRDATA_ADDRESS threadAddr, struct DacpThreadData *threadData) +{ // marshal the Thread object from the target Thread* thread = PTR_Thread(TO_TADDR(threadAddr)); @@ -804,8 +845,7 @@ ClrDataAccess::GetThreadData(CLRDATA_ADDRESS threadAddr, struct DacpThreadData * thread->m_ExceptionState.m_currentExInfo.m_pPrevNestedInfo); #endif // FEATURE_EH_FUNCLETS - SOSDacLeave(); - return hr; + return S_OK; } #ifdef FEATURE_REJIT diff --git a/src/coreclr/debug/runtimeinfo/datadescriptor.h b/src/coreclr/debug/runtimeinfo/datadescriptor.h index e4f75383f76835..e14d29f38ad79c 100644 --- a/src/coreclr/debug/runtimeinfo/datadescriptor.h +++ b/src/coreclr/debug/runtimeinfo/datadescriptor.h @@ -105,7 +105,10 @@ CDAC_TYPES_BEGIN() 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, GCHandle, GCHandle, cdac_offsets::ExposedObject) +CDAC_TYPE_FIELD(Thread, GCHandle, LastThrownObject, cdac_offsets::LastThrownObject) CDAC_TYPE_FIELD(Thread, pointer, LinkNext, cdac_offsets::Link) CDAC_TYPE_END(Thread) diff --git a/src/coreclr/vm/threads.h b/src/coreclr/vm/threads.h index fd3489c79a40ef..a3e2a9bc30115c 100644 --- a/src/coreclr/vm/threads.h +++ b/src/coreclr/vm/threads.h @@ -4044,7 +4044,10 @@ class Thread template<> 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 ExposedObject = offsetof(Thread, m_ExposedObject); + static constexpr size_t LastThrownObject = offsetof(Thread, m_LastThrownObjectHandle); static constexpr size_t Link = offsetof(Thread, m_Link); }; diff --git a/src/native/managed/cdacreader/src/Contracts/Thread.cs b/src/native/managed/cdacreader/src/Contracts/Thread.cs index 3839bb42006a5d..9ddd0de3be0b89 100644 --- a/src/native/managed/cdacreader/src/Contracts/Thread.cs +++ b/src/native/managed/cdacreader/src/Contracts/Thread.cs @@ -17,6 +17,10 @@ internal record struct ThreadStoreCounts( int PendingThreadCount, int DeadThreadCount); +internal record struct ThreadData( + uint Id, + TargetPointer NextThread); + internal interface IThread : IContract { static string IContract.Name { get; } = nameof(Thread); @@ -33,6 +37,7 @@ static IContract IContract.Create(Target target, int version) public virtual ThreadStoreData GetThreadStoreData() => throw new NotImplementedException(); public virtual ThreadStoreCounts GetThreadCounts() => throw new NotImplementedException(); + public virtual ThreadData GetThreadData(TargetPointer thread) => throw new NotImplementedException(); } internal readonly struct Thread : IThread @@ -51,8 +56,10 @@ internal Thread_1(Target target, TargetPointer threadStore) _target = target; _threadStoreAddr = threadStore; + // Get the offset into Thread of the SLink. We use this to find the actual + // first thread from the linked list node contained by the first thread. Target.TypeInfo type = _target.GetTypeInfo(DataType.Thread); - _threadLinkOffset = (ulong)type.Fields["LinkNext"].Offset; + _threadLinkOffset = (ulong)type.Fields[nameof(Data.Thread.LinkNext)].Offset; } ThreadStoreData IThread.GetThreadStoreData() @@ -68,7 +75,7 @@ ThreadStoreData IThread.GetThreadStoreData() return new ThreadStoreData( threadStore.ThreadCount, - new TargetPointer(threadStore.FirstThreadLink - _threadLinkOffset), + GetThreadFromLink(threadStore.FirstThreadLink), _target.ReadGlobalPointer(Constants.Globals.FinalizerThread), _target.ReadGlobalPointer(Constants.Globals.GCThread)); } @@ -90,4 +97,29 @@ ThreadStoreCounts IThread.GetThreadCounts() threadStore.PendingCount, threadStore.DeadCount); } + + ThreadData IThread.GetThreadData(TargetPointer threadPointer) + { + Data.Thread? thread; + if (!_target.ProcessedData.TryGet(threadPointer, out thread)) + { + thread = new Data.Thread(_target, threadPointer); + + // Still okay if processed data is already registered by someone else. + _ = _target.ProcessedData.TryRegister(threadPointer, thread); + } + + return new ThreadData( + thread.Id, + GetThreadFromLink(thread.LinkNext)); + } + + private TargetPointer GetThreadFromLink(TargetPointer threadLink) + { + if (threadLink == TargetPointer.Null) + return TargetPointer.Null; + + // Get the address of the thread containing the link + return new TargetPointer(threadLink - _threadLinkOffset); + } } diff --git a/src/native/managed/cdacreader/src/Data/Thread.cs b/src/native/managed/cdacreader/src/Data/Thread.cs new file mode 100644 index 00000000000000..ad682275bebfbe --- /dev/null +++ b/src/native/managed/cdacreader/src/Data/Thread.cs @@ -0,0 +1,18 @@ +// 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 Thread +{ + public Thread(Target target, TargetPointer address) + { + Target.TypeInfo type = target.GetTypeInfo(DataType.Thread); + + Id = target.Read(address + (ulong)type.Fields[nameof(Id)].Offset); + LinkNext = target.ReadPointer(address + (ulong)type.Fields[nameof(LinkNext)].Offset); + } + + public uint Id { get; init; } + internal TargetPointer LinkNext { get; init; } +} diff --git a/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs b/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs index 3ea7a7d86d6d68..04fe0848c0395e 100644 --- a/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs +++ b/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs @@ -107,7 +107,24 @@ public int GetBreakingChangeVersion() public unsafe int GetSyncBlockCleanupData(ulong addr, void* data) => HResults.E_NOTIMPL; public unsafe int GetSyncBlockData(uint number, void* data) => HResults.E_NOTIMPL; public unsafe int GetThreadAllocData(ulong thread, void* data) => HResults.E_NOTIMPL; - public unsafe int GetThreadData(ulong thread, DacpThreadData* data) => HResults.E_NOTIMPL; + + public unsafe int GetThreadData(ulong thread, DacpThreadData* data) + { + try + { + Contracts.IThread contract = _target.Contracts.Thread; + Contracts.ThreadData threadData = contract.GetThreadData(thread); + data->corThreadId = (int)threadData.Id; + data->nextThread = threadData.NextThread; + } + catch (Exception ex) + { + return ex.HResult; + } + + // TODO: [cdac] Implement/populate rest of thread data fields + return HResults.E_NOTIMPL; + } 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; From 41ba95cff5b7505f30376f1b716e5ab85b61ceac Mon Sep 17 00:00:00 2001 From: Elinor Fung Date: Thu, 23 May 2024 12:29:59 -0700 Subject: [PATCH 03/62] Apply suggestions from code review --- docs/design/datacontracts/contract_csharp_api_design.cs | 2 +- src/native/managed/cdacreader/src/Data/Thread.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/design/datacontracts/contract_csharp_api_design.cs b/docs/design/datacontracts/contract_csharp_api_design.cs index 68230820455c88..a770c1f4576b40 100644 --- a/docs/design/datacontracts/contract_csharp_api_design.cs +++ b/docs/design/datacontracts/contract_csharp_api_design.cs @@ -78,7 +78,7 @@ interface IContract uint Version { get; } } - class Target + sealed class Target { // Users of the data contract may adjust this number to force re-reading of all data public int CurrentEpoch = 0; diff --git a/src/native/managed/cdacreader/src/Data/Thread.cs b/src/native/managed/cdacreader/src/Data/Thread.cs index ad682275bebfbe..a6c4d0febb4200 100644 --- a/src/native/managed/cdacreader/src/Data/Thread.cs +++ b/src/native/managed/cdacreader/src/Data/Thread.cs @@ -14,5 +14,5 @@ public Thread(Target target, TargetPointer address) } public uint Id { get; init; } - internal TargetPointer LinkNext { get; init; } + public TargetPointer LinkNext { get; init; } } From 29214f02e1c2c2ec212e2a006cde4993603f4671 Mon Sep 17 00:00:00 2001 From: Aleksey Kliger Date: Tue, 14 May 2024 13:57:30 -0400 Subject: [PATCH 04/62] WIP: Metadata contract --- .../cdacreader/src/Contracts/Metadata.cs | 48 +++++++++++++++++++ .../cdacreader/src/Legacy/ISOSDacInterface.cs | 21 +++++++- 2 files changed, 68 insertions(+), 1 deletion(-) create mode 100644 src/native/managed/cdacreader/src/Contracts/Metadata.cs diff --git a/src/native/managed/cdacreader/src/Contracts/Metadata.cs b/src/native/managed/cdacreader/src/Contracts/Metadata.cs new file mode 100644 index 00000000000000..2d1fa7d74ce1ac --- /dev/null +++ b/src/native/managed/cdacreader/src/Contracts/Metadata.cs @@ -0,0 +1,48 @@ +// 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 Microsoft.Diagnostics.DataContractReader.Legacy; + +namespace Microsoft.Diagnostics.DataContractReader.Contracts; + +internal struct MethodTable +{ +} + +internal interface IMetadata : IContract +{ + static string IContract.Name => nameof(Metadata); + static IContract IContract.Create(Target target, int version) + { + return version switch + { + 1 => new Metadata_1(target), + _ => default(Metadata), + }; + } + + public virtual DacpMethodTableData GetMethodTableData(TargetPointer targetPointer) => throw new NotImplementedException(); +} + +internal struct Metadata : IMetadata +{ + // Everything throws NotImplementedException +} + + +internal struct Metadata_1 : IMetadata +{ + private readonly Target _target; + + internal Metadata_1(Target target) + { + _target = target; + } + + + public DacpMethodTableData GetMethodTableData(TargetPointer targetPointer) + { + throw new NotImplementedException(); + } +} diff --git a/src/native/managed/cdacreader/src/Legacy/ISOSDacInterface.cs b/src/native/managed/cdacreader/src/Legacy/ISOSDacInterface.cs index bd0d20e65b77c0..ea3c73028a472d 100644 --- a/src/native/managed/cdacreader/src/Legacy/ISOSDacInterface.cs +++ b/src/native/managed/cdacreader/src/Legacy/ISOSDacInterface.cs @@ -42,6 +42,25 @@ internal struct DacpThreadData public ulong lastThrownObjectHandle; public ulong nextThread; } + +internal struct DacpMethodTableData +{ + public int bIsFree; // everything else is NULL if this is true. + public ulong module; + public ulong @class; + public ulong parentMethodTable; + public ushort wNumInterfaces; + public ushort wNumMethods; + public ushort wNumVtableSlots; + public ushort wNumVirtuals; + public uint baseSize; + public uint componentSize; + public uint /*mdTypeDef*/ cl; // Metadata token + public uint dwAttrClass; // cached metadata + public int bIsShared; // Always false, preserved for backward compatibility + public int bIsDynamic; + public int bContainsPointers; +} #pragma warning restore CS0649 // Field is never assigned to, and will always have its default value [GeneratedComInterface] @@ -139,7 +158,7 @@ internal unsafe partial interface ISOSDacInterface [PreserveSig] int GetMethodTableName(ulong mt, uint count, char* mtName, uint* pNeeded); [PreserveSig] - int GetMethodTableData(ulong mt, /*struct DacpMethodTableData*/ void* data); + int GetMethodTableData(ulong mt, DacpMethodTableData* data); [PreserveSig] int GetMethodTableSlot(ulong mt, uint slot, ulong* value); [PreserveSig] From 5213286fd232980a796a8f81d74329c1d2d5cf40 Mon Sep 17 00:00:00 2001 From: Aleksey Kliger Date: Wed, 29 May 2024 13:01:44 -0400 Subject: [PATCH 05/62] fix build --- src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs b/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs index 04fe0848c0395e..5bc186851623a6 100644 --- a/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs +++ b/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs @@ -81,7 +81,7 @@ public int GetBreakingChangeVersion() public unsafe int GetMethodDescPtrFromFrame(ulong frameAddr, ulong* ppMD) => HResults.E_NOTIMPL; public unsafe int GetMethodDescPtrFromIP(ulong ip, ulong* ppMD) => HResults.E_NOTIMPL; public unsafe int GetMethodDescTransparencyData(ulong methodDesc, void* data) => HResults.E_NOTIMPL; - public unsafe int GetMethodTableData(ulong mt, void* data) => HResults.E_NOTIMPL; + public unsafe int GetMethodTableData(ulong mt, DacpMethodTableData* data) => HResults.E_NOTIMPL; public unsafe int GetMethodTableFieldData(ulong mt, void* data) => HResults.E_NOTIMPL; public unsafe int GetMethodTableForEEClass(ulong eeClass, ulong* value) => HResults.E_NOTIMPL; public unsafe int GetMethodTableName(ulong mt, uint count, char* mtName, uint* pNeeded) => HResults.E_NOTIMPL; From 622e01a2fbbd6785234a498c22105b38745ab34d Mon Sep 17 00:00:00 2001 From: Aleksey Kliger Date: Wed, 29 May 2024 15:58:57 -0400 Subject: [PATCH 06/62] WIP: ValidateMethodTable --- .../cdacreader/src/Contracts/Metadata.cs | 103 +++++++++++++++++- .../cdacreader/src/Data/MethodTable.cs | 19 ++++ src/native/managed/cdacreader/src/DataType.cs | 1 + src/native/managed/cdacreader/src/Target.cs | 19 +++- 4 files changed, 138 insertions(+), 4 deletions(-) create mode 100644 src/native/managed/cdacreader/src/Data/MethodTable.cs diff --git a/src/native/managed/cdacreader/src/Contracts/Metadata.cs b/src/native/managed/cdacreader/src/Contracts/Metadata.cs index 2d1fa7d74ce1ac..aafa7ef2fddd91 100644 --- a/src/native/managed/cdacreader/src/Contracts/Metadata.cs +++ b/src/native/managed/cdacreader/src/Contracts/Metadata.cs @@ -2,12 +2,22 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; -using Microsoft.Diagnostics.DataContractReader.Legacy; namespace Microsoft.Diagnostics.DataContractReader.Contracts; +// FIXME: Do we want some other name for this concept? "MethodTable" is CoreCLR specific (in mono this would be a MonoClass) internal struct MethodTable { + internal Data.MethodTable? _data; + internal bool _isFreeObjectMT; + internal MethodTable(Data.MethodTable? data, bool isFreeObjectMT) + { + _data = data; + _isFreeObjectMT = isFreeObjectMT; + } + public System.Reflection.Metadata.TypeDefinitionHandle Token => System.Reflection.Metadata.Ecma335.MetadataTokens.TypeDefinitionHandle((int)GetTypeDefRid()); // TokenFromRid(GetTypeDefRid(), mdtTypeDef); + internal int GetTypeDefRid() => _data != null ? (int)(_data.DwFlags2 >> 8) : 0; + } internal interface IMetadata : IContract @@ -22,7 +32,7 @@ static IContract IContract.Create(Target target, int version) }; } - public virtual DacpMethodTableData GetMethodTableData(TargetPointer targetPointer) => throw new NotImplementedException(); + public virtual MethodTable GetMethodTableData(TargetPointer targetPointer) => throw new NotImplementedException(); } internal struct Metadata : IMetadata @@ -40,9 +50,96 @@ internal Metadata_1(Target target) _target = target; } + private TargetPointer FreeObjectMethodTablePointer => throw new NotImplementedException(); + + public MethodTable GetMethodTableData(TargetPointer methodTablePointer) + { + if (!ValidateMethodTablePointer(methodTablePointer, out bool isFreeObjectMT)) + { + throw new ArgumentException("Invalid method table pointer"); + } + + Data.MethodTable? methodTableData; + if (!_target.ProcessedData.TryGet(methodTablePointer, out methodTableData)) + { + + // Still okay if processed data is already registered by someone else. + _ = _target.ProcessedData.TryRegister(methodTablePointer, methodTableData); + } + + return new MethodTable(methodTableData, isFreeObjectMT); + } + + + private bool ValidateMethodTablePointer(TargetPointer methodTablePointer, out bool isFree) + { + isFree = false; + // FIXME: is methodTablePointer properly sign-extended from 32-bit targets? + if (methodTablePointer == TargetPointer.Null || methodTablePointer == TargetPointer.MinusOne) + { + return false; + } + try + { + if (methodTablePointer == FreeObjectMethodTablePointer) + { + isFree = true; + return true; + } + else + { + return true; // pointer is valid + } + } + catch (Exception) + { + return false; + } + } - public DacpMethodTableData GetMethodTableData(TargetPointer targetPointer) + private bool ValidateWithPossibleAV(ref readonly MethodTable methodTable) { + //PTR_EEClass pEEClass = this->GetClassWithPossibleAV(); + //return ((pEEClass && (this == pEEClass->GetMethodTableWithPossibleAV())) || + // ((HasInstantiation() || IsArray()) && + // (pEEClass && (pEEClass->GetMethodTableWithPossibleAV()->GetClassWithPossibleAV() == pEEClass)))); throw new NotImplementedException(); } + + internal bool ValidateMethodTable(ref readonly MethodTable methodTable) + { + try + { + if (!ValidateWithPossibleAV(in methodTable)) + { + return false; + } + } + catch (Exception) + { + return false; + } + + System.Reflection.Metadata.Handle tk; + try + { + tk = methodTable.Token; + } + catch (ArgumentException) + { + return false; + } + if (!tk.IsNil && tk.Kind != System.Reflection.Metadata.HandleKind.TypeDefinition) + { + return false; + } + if (!methodTable.IsInterface && !methodTable.IsString) + { + if (methodTable.BaseSize == 0 || !_target.IsAlignedToPointerSize(methodTable.BaseSize)) + { + return false; + } + } + return true; + } } diff --git a/src/native/managed/cdacreader/src/Data/MethodTable.cs b/src/native/managed/cdacreader/src/Data/MethodTable.cs new file mode 100644 index 00000000000000..7c2cdc831aa1ed --- /dev/null +++ b/src/native/managed/cdacreader/src/Data/MethodTable.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. + +using System; + +namespace Microsoft.Diagnostics.DataContractReader.Data; + +internal sealed class MethodTable +{ + public MethodTable(Target target, TargetPointer address) + { + Target.TypeInfo type = target.GetTypeInfo(DataType.MethodTable); + + //Id = target.Read(address + (ulong)type.Fields[nameof(Id)].Offset); + //LinkNext = target.ReadPointer(address + (ulong)type.Fields[nameof(LinkNext)].Offset); + } + + public uint DwFlags2 => throw new NotImplementedException(); +} diff --git a/src/native/managed/cdacreader/src/DataType.cs b/src/native/managed/cdacreader/src/DataType.cs index c301c6a008d72c..11625a79d16186 100644 --- a/src/native/managed/cdacreader/src/DataType.cs +++ b/src/native/managed/cdacreader/src/DataType.cs @@ -22,4 +22,5 @@ public enum DataType GCHandle, Thread, ThreadStore, + MethodTable, } diff --git a/src/native/managed/cdacreader/src/Target.cs b/src/native/managed/cdacreader/src/Target.cs index 1e7a84f976a579..264b0134d34393 100644 --- a/src/native/managed/cdacreader/src/Target.cs +++ b/src/native/managed/cdacreader/src/Target.cs @@ -9,15 +9,24 @@ namespace Microsoft.Diagnostics.DataContractReader; -public struct TargetPointer +public struct TargetPointer : IEquatable { public static TargetPointer Null = new(0); + public static TargetPointer MinusOne = new(ulong.MaxValue); public 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 static bool operator ==(TargetPointer left, TargetPointer right) => left.Value == right.Value; + public static bool operator !=(TargetPointer left, TargetPointer right) => left.Value != right.Value; + + public override bool Equals(object? obj) => obj is TargetPointer pointer && Equals(pointer); + public bool Equals(TargetPointer other) => Value == other.Value; + + public override int GetHashCode() => Value.GetHashCode(); } /// @@ -287,6 +296,14 @@ private static bool TryReadPointer(ulong address, Configuration config, Reader r return false; } + public static bool IsAligned(ulong value, int alignment) + => (value & (ulong)(alignment - 1)) == 0; + + public bool IsAlignedToPointerSize(ulong value) + => IsAligned(value, _config.PointerSize); + public bool IsAlignedToPointerSize(TargetPointer pointer) + => IsAligned(pointer.Value, _config.PointerSize); + public T ReadGlobal(string name) where T : struct, INumber => ReadGlobal(name, out _); From e1124168fc33234d12a915d2f370e768f72e2f18 Mon Sep 17 00:00:00 2001 From: Elinor Fung Date: Wed, 29 May 2024 14:43:28 -0700 Subject: [PATCH 07/62] DataCache.GetOrAdd --- .../cdacreader/src/Contracts/Thread.cs | 30 ++----------------- .../managed/cdacreader/src/Data/IData.cs | 9 ++++++ .../managed/cdacreader/src/Data/Thread.cs | 5 +++- .../cdacreader/src/Data/ThreadStore.cs | 5 +++- src/native/managed/cdacreader/src/Target.cs | 26 +++++++++++++--- 5 files changed, 42 insertions(+), 33 deletions(-) create mode 100644 src/native/managed/cdacreader/src/Data/IData.cs diff --git a/src/native/managed/cdacreader/src/Contracts/Thread.cs b/src/native/managed/cdacreader/src/Contracts/Thread.cs index 9ddd0de3be0b89..58621f005d4376 100644 --- a/src/native/managed/cdacreader/src/Contracts/Thread.cs +++ b/src/native/managed/cdacreader/src/Contracts/Thread.cs @@ -64,15 +64,7 @@ internal Thread_1(Target target, TargetPointer threadStore) ThreadStoreData IThread.GetThreadStoreData() { - Data.ThreadStore? threadStore; - if (!_target.ProcessedData.TryGet(_threadStoreAddr, out threadStore)) - { - threadStore = new Data.ThreadStore(_target, _threadStoreAddr); - - // Still okay if processed data is already registered by someone else - _ = _target.ProcessedData.TryRegister(_threadStoreAddr, threadStore); - } - + Data.ThreadStore threadStore = _target.ProcessedData.GetOrAdd(_threadStoreAddr); return new ThreadStoreData( threadStore.ThreadCount, GetThreadFromLink(threadStore.FirstThreadLink), @@ -82,15 +74,7 @@ ThreadStoreData IThread.GetThreadStoreData() ThreadStoreCounts IThread.GetThreadCounts() { - Data.ThreadStore? threadStore; - if (!_target.ProcessedData.TryGet(_threadStoreAddr, out threadStore)) - { - threadStore = new Data.ThreadStore(_target, _threadStoreAddr); - - // Still okay if processed data is already registered by someone else - _ = _target.ProcessedData.TryRegister(_threadStoreAddr, threadStore); - } - + Data.ThreadStore threadStore = _target.ProcessedData.GetOrAdd(_threadStoreAddr); return new ThreadStoreCounts( threadStore.UnstartedCount, threadStore.BackgroundCount, @@ -100,15 +84,7 @@ ThreadStoreCounts IThread.GetThreadCounts() ThreadData IThread.GetThreadData(TargetPointer threadPointer) { - Data.Thread? thread; - if (!_target.ProcessedData.TryGet(threadPointer, out thread)) - { - thread = new Data.Thread(_target, threadPointer); - - // Still okay if processed data is already registered by someone else. - _ = _target.ProcessedData.TryRegister(threadPointer, thread); - } - + Data.Thread thread = _target.ProcessedData.GetOrAdd(threadPointer); return new ThreadData( thread.Id, GetThreadFromLink(thread.LinkNext)); diff --git a/src/native/managed/cdacreader/src/Data/IData.cs b/src/native/managed/cdacreader/src/Data/IData.cs new file mode 100644 index 00000000000000..65b7be805c838e --- /dev/null +++ b/src/native/managed/cdacreader/src/Data/IData.cs @@ -0,0 +1,9 @@ +// 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 interface IData where TSelf : IData +{ + static abstract TSelf Create(Target target, TargetPointer address); +} diff --git a/src/native/managed/cdacreader/src/Data/Thread.cs b/src/native/managed/cdacreader/src/Data/Thread.cs index a6c4d0febb4200..3f29b90126a36f 100644 --- a/src/native/managed/cdacreader/src/Data/Thread.cs +++ b/src/native/managed/cdacreader/src/Data/Thread.cs @@ -3,8 +3,11 @@ namespace Microsoft.Diagnostics.DataContractReader.Data; -internal sealed class Thread +internal sealed class Thread : IData { + static Thread IData.Create(Target target, TargetPointer address) + => new Thread(target, address); + public Thread(Target target, TargetPointer address) { Target.TypeInfo type = target.GetTypeInfo(DataType.Thread); diff --git a/src/native/managed/cdacreader/src/Data/ThreadStore.cs b/src/native/managed/cdacreader/src/Data/ThreadStore.cs index ae131cc4ae0b29..afef1e835be324 100644 --- a/src/native/managed/cdacreader/src/Data/ThreadStore.cs +++ b/src/native/managed/cdacreader/src/Data/ThreadStore.cs @@ -3,8 +3,11 @@ namespace Microsoft.Diagnostics.DataContractReader.Data; -internal sealed class ThreadStore +internal sealed class ThreadStore : IData { + static ThreadStore IData.Create(Target target, TargetPointer address) + => new ThreadStore(target, address); + public ThreadStore(Target target, TargetPointer address) { Target.TypeInfo type = target.GetTypeInfo(DataType.ThreadStore); diff --git a/src/native/managed/cdacreader/src/Target.cs b/src/native/managed/cdacreader/src/Target.cs index 1e7a84f976a579..83f637034dc645 100644 --- a/src/native/managed/cdacreader/src/Target.cs +++ b/src/native/managed/cdacreader/src/Target.cs @@ -3,9 +3,11 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Numerics; using System.Runtime.CompilerServices; +using Microsoft.Diagnostics.DataContractReader.Data; namespace Microsoft.Diagnostics.DataContractReader; @@ -63,7 +65,7 @@ private readonly struct Configuration private readonly Dictionary _types = []; internal Contracts.Registry Contracts { get; } - internal DataCache ProcessedData { get; } = new DataCache(); + internal DataCache ProcessedData { get; } public static bool TryCreate(ulong contractDescriptor, delegate* unmanaged readFromTarget, void* readContext, out Target? target) { @@ -81,6 +83,7 @@ public static bool TryCreate(ulong contractDescriptor, delegate* unmanaged internal sealed class DataCache { + private readonly Target _target; private readonly Dictionary<(ulong, Type), object?> _readDataByAddress = []; - public bool TryRegister(ulong address, T data) + public DataCache(Target target) { - return _readDataByAddress.TryAdd((address, typeof(T)), data); + _target = target; } - public bool TryGet(ulong address, [NotNullWhen(true)] out T? data) + public T GetOrAdd(TargetPointer address) where T : IData + { + if (TryGet(address, out T? result)) + return result; + + T constructed = T.Create(_target, address); + if (_readDataByAddress.TryAdd((address, typeof(T)), constructed)) + return constructed; + + bool found = TryGet(address, out result); + Debug.Assert(found); + return result!; + } + + private bool TryGet(ulong address, [NotNullWhen(true)] out T? data) { data = default; if (!_readDataByAddress.TryGetValue((address, typeof(T)), out object? dataObj)) From ae1eac98c25cd55c4ac2aacb89d0019ef22860b5 Mon Sep 17 00:00:00 2001 From: Aleksey Kliger Date: Thu, 30 May 2024 13:26:31 -0400 Subject: [PATCH 08/62] wip --- src/coreclr/debug/daccess/request.cpp | 4 ++ .../debug/runtimeinfo/contractpointerdata.cpp | 3 +- .../debug/runtimeinfo/datadescriptor.h | 1 + .../managed/cdacreader/src/Constants.cs | 2 + .../cdacreader/src/Contracts/Metadata.cs | 43 ++++++++++++++++--- 5 files changed, 45 insertions(+), 8 deletions(-) diff --git a/src/coreclr/debug/daccess/request.cpp b/src/coreclr/debug/daccess/request.cpp index 5353c93a892289..02862b26c630f8 100644 --- a/src/coreclr/debug/daccess/request.cpp +++ b/src/coreclr/debug/daccess/request.cpp @@ -161,10 +161,14 @@ BOOL DacValidateMethodTable(PTR_MethodTable pMT, BOOL &bIsFree) // In rare cases, we've seen the standard check above pass when it shouldn't. // Insert additional/ad-hoc tests below. + // FIXME(cdac): ak - this check is trivially true - GetCl() runs in the DAC and synthesizes a token with a mdtTypeDef table + // so the check is always true. +#if 0 // Metadata token should look valid for a class mdTypeDef td = pMT->GetCl(); if (td != mdTokenNil && TypeFromToken(td) != mdtTypeDef) goto BadMethodTable; +#endif // BaseSize should always be greater than 0 for valid objects (unless it's an interface) // For strings, baseSize is not ptr-aligned diff --git a/src/coreclr/debug/runtimeinfo/contractpointerdata.cpp b/src/coreclr/debug/runtimeinfo/contractpointerdata.cpp index ae1440af4219a2..74653be024471d 100644 --- a/src/coreclr/debug/runtimeinfo/contractpointerdata.cpp +++ b/src/coreclr/debug/runtimeinfo/contractpointerdata.cpp @@ -7,10 +7,11 @@ #include #include "threads.h" +#include "vars.hpp" extern "C" { - + // without an extern declaration, clang does not emit this global into the object file extern const uintptr_t contractDescriptorPointerData[]; diff --git a/src/coreclr/debug/runtimeinfo/datadescriptor.h b/src/coreclr/debug/runtimeinfo/datadescriptor.h index e14d29f38ad79c..f275feb4dc9b65 100644 --- a/src/coreclr/debug/runtimeinfo/datadescriptor.h +++ b/src/coreclr/debug/runtimeinfo/datadescriptor.h @@ -138,6 +138,7 @@ CDAC_GLOBAL(FeatureEHFunclets, uint8, 1) CDAC_GLOBAL(FeatureEHFunclets, uint8, 0) #endif CDAC_GLOBAL(SOSBreakingChangeVersion, uint8, SOS_BREAKING_CHANGE_VERSION) +CDAC_GLOBAL_POINTER(FreeObjectMethodTable, &::g_pFreeObjectMethodTable) CDAC_GLOBALS_END() #undef CDAC_BASELINE diff --git a/src/native/managed/cdacreader/src/Constants.cs b/src/native/managed/cdacreader/src/Constants.cs index be4fd418b802d5..6258811c5bc8e9 100644 --- a/src/native/managed/cdacreader/src/Constants.cs +++ b/src/native/managed/cdacreader/src/Constants.cs @@ -13,5 +13,7 @@ internal static class Globals internal const string GCThread = nameof(GCThread); internal const string SOSBreakingChangeVersion = nameof(SOSBreakingChangeVersion); + + internal const string FreeObjectMethodTable = nameof(FreeObjectMethodTable); } } diff --git a/src/native/managed/cdacreader/src/Contracts/Metadata.cs b/src/native/managed/cdacreader/src/Contracts/Metadata.cs index aafa7ef2fddd91..2013c77ac239de 100644 --- a/src/native/managed/cdacreader/src/Contracts/Metadata.cs +++ b/src/native/managed/cdacreader/src/Contracts/Metadata.cs @@ -5,17 +5,30 @@ namespace Microsoft.Diagnostics.DataContractReader.Contracts; -// FIXME: Do we want some other name for this concept? "MethodTable" is CoreCLR specific (in mono this would be a MonoClass) -internal struct MethodTable + +// GC Heap may contain pointers that are not valid MethodTable pointers. +// see Metadata_1.ValidateMethodTablePointer +internal struct UntrustedMethodTable { internal Data.MethodTable? _data; internal bool _isFreeObjectMT; - internal MethodTable(Data.MethodTable? data, bool isFreeObjectMT) + internal UntrustedMethodTable(Data.MethodTable? data, bool isFreeObjectMT) + { + _data = data; + _isFreeObjectMT = isFreeObjectMT; + } + internal int GetTypeDefRid() => _data != null ? (int)(_data.DwFlags2 >> 8) : 0; + +} +internal struct MethodTable +{ + internal Data.MethodTable _data; + internal bool _isFreeObjectMT; + internal MethodTable(Data.MethodTable data, bool isFreeObjectMT) { _data = data; _isFreeObjectMT = isFreeObjectMT; } - public System.Reflection.Metadata.TypeDefinitionHandle Token => System.Reflection.Metadata.Ecma335.MetadataTokens.TypeDefinitionHandle((int)GetTypeDefRid()); // TokenFromRid(GetTypeDefRid(), mdtTypeDef); internal int GetTypeDefRid() => _data != null ? (int)(_data.DwFlags2 >> 8) : 0; } @@ -25,9 +38,11 @@ internal interface IMetadata : IContract static string IContract.Name => nameof(Metadata); static IContract IContract.Create(Target target, int version) { + TargetPointer targetPointer = target.ReadGlobalPointer(Constants.Globals.FreeObjectMethodTable); + TargetPointer freeObjectMethodTable = target.ReadPointer(targetPointer); return version switch { - 1 => new Metadata_1(target), + 1 => new Metadata_1(target, freeObjectMethodTable), _ => default(Metadata), }; } @@ -44,13 +59,27 @@ internal struct Metadata : IMetadata internal struct Metadata_1 : IMetadata { private readonly Target _target; + private readonly TargetPointer _freeObjectMethodTablePointer; - internal Metadata_1(Target target) + internal Metadata_1(Target target, TargetPointer freeObjectMethodTablePointer) { _target = target; + _freeObjectMethodTablePointer = freeObjectMethodTablePointer; } - private TargetPointer FreeObjectMethodTablePointer => throw new NotImplementedException(); + public TargetPointer FreeObjectMethodTablePointer => _freeObjectMethodTablePointer; + + internal UntrustedMethodTable GetUntrustedMethodTableData(TargetPointer methodTablePointer) + { + Data.MethodTable? methodTableData; + if (!_target.ProcessedData.TryGet(methodTablePointer, out methodTableData)) + { + + // Still okay if processed data is already registered by someone else. + _ = _target.ProcessedData.TryRegister(methodTablePointer, methodTableData); + } + return new UntrustedMethodTable(methodTableData, methodTablePointer == FreeObjectMethodTablePointer); + } public MethodTable GetMethodTableData(TargetPointer methodTablePointer) { From 5c696da34ac2767cfffaa14fd37b8e7895ad53af Mon Sep 17 00:00:00 2001 From: Aleksey Kliger Date: Thu, 30 May 2024 15:54:42 -0400 Subject: [PATCH 09/62] checkpoint: ValidateWithPossibleAV --- .../debug/runtimeinfo/datadescriptor.cpp | 1 + .../debug/runtimeinfo/datadescriptor.h | 9 + src/coreclr/vm/cdacoffsets.h | 4 +- src/coreclr/vm/methodtable.h | 9 + .../cdacreader/src/Contracts/Metadata.cs | 249 ++++++++++++------ .../cdacreader/src/Data/MethodTable.cs | 10 +- src/native/managed/cdacreader/src/Target.cs | 4 +- 7 files changed, 195 insertions(+), 91 deletions(-) diff --git a/src/coreclr/debug/runtimeinfo/datadescriptor.cpp b/src/coreclr/debug/runtimeinfo/datadescriptor.cpp index 99fe1cca7eeca7..bea29213783ebc 100644 --- a/src/coreclr/debug/runtimeinfo/datadescriptor.cpp +++ b/src/coreclr/debug/runtimeinfo/datadescriptor.cpp @@ -9,6 +9,7 @@ #include "static_assert.h" #include +#include "methodtable.h" #include "threads.h" // begin blob definition diff --git a/src/coreclr/debug/runtimeinfo/datadescriptor.h b/src/coreclr/debug/runtimeinfo/datadescriptor.h index f275feb4dc9b65..af400647865375 100644 --- a/src/coreclr/debug/runtimeinfo/datadescriptor.h +++ b/src/coreclr/debug/runtimeinfo/datadescriptor.h @@ -126,6 +126,15 @@ CDAC_TYPE_BEGIN(GCHandle) CDAC_TYPE_SIZE(sizeof(OBJECTHANDLE)) CDAC_TYPE_END(GCHandle) +// Metadata + +CDAC_TYPE_BEGIN(MethodTable) +CDAC_TYPE_INDETERMINATE(MethodTable) +CDAC_TYPE_FIELD(MethodTable, /*uint32*/, DwFlags, cdac_offsets::m_dwFlags) +CDAC_TYPE_FIELD(MethodTable, /*uint32*/, BaseSize, cdac_offsets::m_BaseSize) +CDAC_TYPE_FIELD(MethodTable, /*uint32*/, DwFlags2, cdac_offsets::m_dwFlags2) +CDAC_TYPE_END(MethodTable) + CDAC_TYPES_END() CDAC_GLOBALS_BEGIN() diff --git a/src/coreclr/vm/cdacoffsets.h b/src/coreclr/vm/cdacoffsets.h index 317ef41f736037..38c5b316c4244f 100644 --- a/src/coreclr/vm/cdacoffsets.h +++ b/src/coreclr/vm/cdacoffsets.h @@ -8,8 +8,8 @@ // // If the offset of some field F in class C must be provided to cDAC, but the field is private, the // class C should declare cdac_offsets as a friend: -// -// friend template struct cdac_offsets; +// +// template friend struct ::cdac_offsets; // // and provide a specialization cdac_offsets with a constexpr size_t member providing the offset: // diff --git a/src/coreclr/vm/methodtable.h b/src/coreclr/vm/methodtable.h index cb8a7ce9f9c240..6782b921231eda 100644 --- a/src/coreclr/vm/methodtable.h +++ b/src/coreclr/vm/methodtable.h @@ -3659,8 +3659,17 @@ public : public: BOOL Validate (); + + template friend struct ::cdac_offsets; }; // class MethodTable +template<> struct ::cdac_offsets +{ + static constexpr size_t m_dwFlags = offsetof(MethodTable, m_dwFlags); + static constexpr size_t m_BaseSize = offsetof(MethodTable, m_BaseSize); + static constexpr size_t m_dwFlags2 = offsetof(MethodTable, m_dwFlags2); +}; + #ifndef CROSSBITNESS_COMPILE static_assert_no_msg(sizeof(MethodTable) == SIZEOF__MethodTable_); #endif diff --git a/src/native/managed/cdacreader/src/Contracts/Metadata.cs b/src/native/managed/cdacreader/src/Contracts/Metadata.cs index 2013c77ac239de..1931f0db09dafa 100644 --- a/src/native/managed/cdacreader/src/Contracts/Metadata.cs +++ b/src/native/managed/cdacreader/src/Contracts/Metadata.cs @@ -2,37 +2,12 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; +using UntrustedMethodTable = Microsoft.Diagnostics.DataContractReader.Contracts.UntrustedMethodTable_1; +using MethodTable = Microsoft.Diagnostics.DataContractReader.Contracts.MethodTable_1; +using System.Reflection; namespace Microsoft.Diagnostics.DataContractReader.Contracts; - -// GC Heap may contain pointers that are not valid MethodTable pointers. -// see Metadata_1.ValidateMethodTablePointer -internal struct UntrustedMethodTable -{ - internal Data.MethodTable? _data; - internal bool _isFreeObjectMT; - internal UntrustedMethodTable(Data.MethodTable? data, bool isFreeObjectMT) - { - _data = data; - _isFreeObjectMT = isFreeObjectMT; - } - internal int GetTypeDefRid() => _data != null ? (int)(_data.DwFlags2 >> 8) : 0; - -} -internal struct MethodTable -{ - internal Data.MethodTable _data; - internal bool _isFreeObjectMT; - internal MethodTable(Data.MethodTable data, bool isFreeObjectMT) - { - _data = data; - _isFreeObjectMT = isFreeObjectMT; - } - internal int GetTypeDefRid() => _data != null ? (int)(_data.DwFlags2 >> 8) : 0; - -} - internal interface IMetadata : IContract { static string IContract.Name => nameof(Metadata); @@ -55,12 +30,138 @@ internal struct Metadata : IMetadata // Everything throws NotImplementedException } +internal interface IMethodTableFlags +{ + public uint DwFlags { get; } + public uint DwFlags2 { get; } + public uint BaseSize { get; } + private int GetTypeDefRid() => (int)(DwFlags2 >> Metadata_1.Constants.MethodTableDwFlags2TypeDefRidShift); + + public uint GetFlag(Metadata_1.WFLAGS_HIGH mask) => DwFlags & (uint)mask; + public bool IsInterface => GetFlag(Metadata_1.WFLAGS_HIGH.Category_Mask) == (uint)Metadata_1.WFLAGS_HIGH.Category_Interface; + public bool IsString => HasComponentSize() && !IsArray() && RawGetComponentSize() == 2; + + public bool HasComponentSize() => GetFlag(Metadata_1.WFLAGS_HIGH.HasComponentSize) != 0; + + public bool IsArray() => GetFlag(Metadata_1.WFLAGS_HIGH.Category_Array_Mask) == (uint)Metadata_1.WFLAGS_HIGH.Category_Array; + + public ushort RawGetComponentSize() => (ushort)(DwFlags >> 16); + +} + +// GC Heap corruption may create situations where a putative pointer to a MethodTable +// may point to garbage. So this struct represents a MethodTable that we don't necessarily +// trust to be valid. +// see Metadata_1.ValidateMethodTablePointer +internal struct UntrustedMethodTable_1 : IMethodTableFlags +{ + private readonly Target _target; + private readonly Target.TypeInfo _type; + public TargetPointer MethodTablePointer { get; init; } + + internal UntrustedMethodTable_1(Target target, TargetPointer methodTablePointer) + { + _target = target; + _type = target.GetTypeInfo(DataType.MethodTable); + MethodTablePointer = methodTablePointer; + } + + // all these accessors might throw if MethodTablePointer is invalid + public uint DwFlags => _target.Read(MethodTablePointer + (ulong)_type.Fields[nameof(DwFlags2)].Offset); + public uint DwFlags2 => _target.Read(MethodTablePointer + (ulong)_type.Fields[nameof(DwFlags)].Offset); + public uint BaseSize => _target.Read(MethodTablePointer + (ulong)_type.Fields[nameof(BaseSize)].Offset); + +} + + +internal struct MethodTable_1 : IMethodTableFlags +{ + public Data.MethodTable MethodTableData { get; init; } + public bool IsFreeObjectMethodTable { get; init; } + internal MethodTable_1(Data.MethodTable data, bool isFreeObjectMT) + { + MethodTableData = data; + IsFreeObjectMethodTable = isFreeObjectMT; + } + + public uint DwFlags => MethodTableData.DwFlags; + public uint DwFlags2 => MethodTableData.DwFlags2; + public uint BaseSize => MethodTableData.BaseSize; +} + + internal struct Metadata_1 : IMetadata { private readonly Target _target; private readonly TargetPointer _freeObjectMethodTablePointer; + internal static class Constants + { + internal const int MethodTableDwFlags2TypeDefRidShift = 8; + } + + [Flags] + internal enum WFLAGS_HIGH : uint + { + Category_Mask = 0x000F0000, + + Category_Class = 0x00000000, + Category_Unused_1 = 0x00010000, + Category_Unused_2 = 0x00020000, + Category_Unused_3 = 0x00030000, + + Category_ValueType = 0x00040000, + Category_ValueType_Mask = 0x000C0000, + Category_Nullable = 0x00050000, // sub-category of ValueType + Category_PrimitiveValueType = 0x00060000, // sub-category of ValueType, Enum or primitive value type + Category_TruePrimitive = 0x00070000, // sub-category of ValueType, Primitive (ELEMENT_TYPE_I, etc.) + + Category_Array = 0x00080000, + Category_Array_Mask = 0x000C0000, + // Category_IfArrayThenUnused = 0x00010000, // sub-category of Array + Category_IfArrayThenSzArray = 0x00020000, // sub-category of Array + + Category_Interface = 0x000C0000, + Category_Unused_4 = 0x000D0000, + Category_Unused_5 = 0x000E0000, + Category_Unused_6 = 0x000F0000, + + Category_ElementTypeMask = 0x000E0000, // bits that matter for element type mask + + // GC depends on this bit + HasFinalizer = 0x00100000, // instances require finalization + + IDynamicInterfaceCastable = 0x10000000, // class implements IDynamicInterfaceCastable interface + + ICastable = 0x00400000, // class implements ICastable interface + + RequiresAlign8 = 0x00800000, // Type requires 8-byte alignment (only set on platforms that require this and don't get it implicitly) + + ContainsPointers = 0x01000000, + + HasTypeEquivalence = 0x02000000, // can be equivalent to another type + + IsTrackedReferenceWithFinalizer = 0x04000000, + + // GC depends on this bit + Collectible = 0x00200000, + ContainsGenericVariables = 0x20000000, // we cache this flag to help detect these efficiently and + // to detect this condition when restoring + + ComObject = 0x40000000, // class is a com object + + HasComponentSize = 0x80000000, // This is set if component size is used for flags. + + // Types that require non-trivial interface cast have this bit set in the category + NonTrivialInterfaceCast = Category_Array + | ComObject + | ICastable + | IDynamicInterfaceCastable + | Category_ValueType + + } + internal Metadata_1(Target target, TargetPointer freeObjectMethodTablePointer) { _target = target; @@ -69,64 +170,65 @@ internal Metadata_1(Target target, TargetPointer freeObjectMethodTablePointer) public TargetPointer FreeObjectMethodTablePointer => _freeObjectMethodTablePointer; - internal UntrustedMethodTable GetUntrustedMethodTableData(TargetPointer methodTablePointer) + private UntrustedMethodTable GetUntrustedMethodTableData(TargetPointer methodTablePointer) { - Data.MethodTable? methodTableData; - if (!_target.ProcessedData.TryGet(methodTablePointer, out methodTableData)) - { - - // Still okay if processed data is already registered by someone else. - _ = _target.ProcessedData.TryRegister(methodTablePointer, methodTableData); - } - return new UntrustedMethodTable(methodTableData, methodTablePointer == FreeObjectMethodTablePointer); + return new UntrustedMethodTable(_target, methodTablePointer); } public MethodTable GetMethodTableData(TargetPointer methodTablePointer) { - if (!ValidateMethodTablePointer(methodTablePointer, out bool isFreeObjectMT)) + // Check if we cached it already + if (_target.ProcessedData.TryGet(methodTablePointer, out Data.MethodTable? methodTableData)) { - throw new ArgumentException("Invalid method table pointer"); + return new MethodTable(methodTableData, methodTablePointer == FreeObjectMethodTablePointer); } - Data.MethodTable? methodTableData; - if (!_target.ProcessedData.TryGet(methodTablePointer, out methodTableData)) - { + // Otherwse, don't trust it yet + UntrustedMethodTable untrustedMethodTable = GetUntrustedMethodTableData(methodTablePointer); - // Still okay if processed data is already registered by someone else. - _ = _target.ProcessedData.TryRegister(methodTablePointer, methodTableData); + // if it's the free object method table, we can trust it + if (methodTablePointer == FreeObjectMethodTablePointer) + { + Data.MethodTable freeObjectMethodTableData = _target.ProcessedData.GetOrAdd(methodTablePointer); + return new MethodTable(freeObjectMethodTableData, isFreeObjectMT: true); } - - return new MethodTable(methodTableData, isFreeObjectMT); + if (!ValidateMethodTablePointer(in untrustedMethodTable)) + { + throw new ArgumentException("Invalid method table pointer"); + } + // ok, we trust it, cache the data + Data.MethodTable trustedMethodTableData = _target.ProcessedData.GetOrAdd(methodTablePointer); + return new MethodTable(trustedMethodTableData, isFreeObjectMT: false); } - - private bool ValidateMethodTablePointer(TargetPointer methodTablePointer, out bool isFree) + private bool ValidateMethodTablePointer(ref readonly UntrustedMethodTable umt) { - isFree = false; // FIXME: is methodTablePointer properly sign-extended from 32-bit targets? - if (methodTablePointer == TargetPointer.Null || methodTablePointer == TargetPointer.MinusOne) - { - return false; - } + // FIXME2: do we need this? Data.MethodTable probably would throw if methodTablePointer is invalid + //if (umt.MethodTablePointer == TargetPointer.Null || umt.MethodTablePointer == TargetPointer.MinusOne) + //{ + // return false; + //} try { - if (methodTablePointer == FreeObjectMethodTablePointer) + if (!ValidateWithPossibleAV(in umt)) { - isFree = true; - return true; + return false; } - else + if (!ValidateMethodTable(in umt)) { - return true; // pointer is valid + return false; } } catch (Exception) { + // FIXME: maybe don't swallow all exceptions? return false; } + return true; } - private bool ValidateWithPossibleAV(ref readonly MethodTable methodTable) + private bool ValidateWithPossibleAV(ref readonly UntrustedMethodTable methodTable) { //PTR_EEClass pEEClass = this->GetClassWithPossibleAV(); //return ((pEEClass && (this == pEEClass->GetMethodTableWithPossibleAV())) || @@ -135,34 +237,9 @@ private bool ValidateWithPossibleAV(ref readonly MethodTable methodTable) throw new NotImplementedException(); } - internal bool ValidateMethodTable(ref readonly MethodTable methodTable) + internal bool ValidateMethodTable(ref readonly UntrustedMethodTable methodTable) { - try - { - if (!ValidateWithPossibleAV(in methodTable)) - { - return false; - } - } - catch (Exception) - { - return false; - } - - System.Reflection.Metadata.Handle tk; - try - { - tk = methodTable.Token; - } - catch (ArgumentException) - { - return false; - } - if (!tk.IsNil && tk.Kind != System.Reflection.Metadata.HandleKind.TypeDefinition) - { - return false; - } - if (!methodTable.IsInterface && !methodTable.IsString) + if (!((IMethodTableFlags)methodTable).IsInterface && !((IMethodTableFlags)methodTable).IsString) { if (methodTable.BaseSize == 0 || !_target.IsAlignedToPointerSize(methodTable.BaseSize)) { diff --git a/src/native/managed/cdacreader/src/Data/MethodTable.cs b/src/native/managed/cdacreader/src/Data/MethodTable.cs index 7c2cdc831aa1ed..1e4c8ff8f10916 100644 --- a/src/native/managed/cdacreader/src/Data/MethodTable.cs +++ b/src/native/managed/cdacreader/src/Data/MethodTable.cs @@ -5,15 +5,21 @@ namespace Microsoft.Diagnostics.DataContractReader.Data; -internal sealed class MethodTable +internal sealed class MethodTable : IData { + static MethodTable IData.Create(Target target, TargetPointer address) => new MethodTable(target, address); public MethodTable(Target target, TargetPointer address) { Target.TypeInfo type = target.GetTypeInfo(DataType.MethodTable); //Id = target.Read(address + (ulong)type.Fields[nameof(Id)].Offset); //LinkNext = target.ReadPointer(address + (ulong)type.Fields[nameof(LinkNext)].Offset); + DwFlags = target.Read(address + (ulong)type.Fields[nameof(DwFlags)].Offset); + BaseSize = target.Read(address + (ulong)type.Fields[nameof(BaseSize)].Offset); + DwFlags2 = target.Read(address + (ulong)type.Fields[nameof(DwFlags2)].Offset); } - public uint DwFlags2 => throw new NotImplementedException(); + public uint DwFlags { get; init; } + public uint BaseSize { get; init; } + public uint DwFlags2 { get; init; } } diff --git a/src/native/managed/cdacreader/src/Target.cs b/src/native/managed/cdacreader/src/Target.cs index 057dad5cab1ae2..4fc9ee52f38f5f 100644 --- a/src/native/managed/cdacreader/src/Target.cs +++ b/src/native/managed/cdacreader/src/Target.cs @@ -302,6 +302,8 @@ private static bool TryReadPointer(ulong address, Configuration config, Reader r public static bool IsAligned(ulong value, int alignment) => (value & (ulong)(alignment - 1)) == 0; + public bool IsAlignedToPointerSize(uint value) + => IsAligned(value, _config.PointerSize); public bool IsAlignedToPointerSize(ulong value) => IsAligned(value, _config.PointerSize); public bool IsAlignedToPointerSize(TargetPointer pointer) @@ -382,7 +384,7 @@ public T GetOrAdd(TargetPointer address) where T : IData return result!; } - private bool TryGet(ulong address, [NotNullWhen(true)] out T? data) + public bool TryGet(ulong address, [NotNullWhen(true)] out T? data) { data = default; if (!_readDataByAddress.TryGetValue((address, typeof(T)), out object? dataObj)) From 3a7808d38fcbd9143235e0bcb0f7730a9d7baf13 Mon Sep 17 00:00:00 2001 From: Aleksey Kliger Date: Fri, 31 May 2024 12:20:31 -0400 Subject: [PATCH 10/62] checkpoint EEClass from MethodTable --- .../debug/runtimeinfo/datadescriptor.h | 6 ++ src/coreclr/vm/class.h | 6 ++ src/coreclr/vm/methodtable.h | 1 + .../cdacreader/src/Contracts/Metadata.cs | 84 +++++++++++++++++-- .../managed/cdacreader/src/Data/EEClass.cs | 20 +++++ .../cdacreader/src/Data/MethodTable.cs | 2 + src/native/managed/cdacreader/src/DataType.cs | 1 + src/native/managed/cdacreader/src/Target.cs | 2 +- 8 files changed, 115 insertions(+), 7 deletions(-) create mode 100644 src/native/managed/cdacreader/src/Data/EEClass.cs diff --git a/src/coreclr/debug/runtimeinfo/datadescriptor.h b/src/coreclr/debug/runtimeinfo/datadescriptor.h index af400647865375..698c2d7824d5fc 100644 --- a/src/coreclr/debug/runtimeinfo/datadescriptor.h +++ b/src/coreclr/debug/runtimeinfo/datadescriptor.h @@ -133,8 +133,14 @@ CDAC_TYPE_INDETERMINATE(MethodTable) CDAC_TYPE_FIELD(MethodTable, /*uint32*/, DwFlags, cdac_offsets::m_dwFlags) CDAC_TYPE_FIELD(MethodTable, /*uint32*/, BaseSize, cdac_offsets::m_BaseSize) CDAC_TYPE_FIELD(MethodTable, /*uint32*/, DwFlags2, cdac_offsets::m_dwFlags2) +CDAC_TYPE_FIELD(MethodTable, /*nuint*/, EEClassOrCanonMT, cdac_offsets::m_pEEClassOrCanonMT) CDAC_TYPE_END(MethodTable) +CDAC_TYPE_BEGIN(EEClass) +CDAC_TYPE_INDETERMINATE(EEClass) +CDAC_TYPE_FIELD(EEClass, /*pointer*/, MethodTable, cdac_offsets::m_pMethodTable) +CDAC_TYPE_END(EEClass) + CDAC_TYPES_END() CDAC_GLOBALS_BEGIN() diff --git a/src/coreclr/vm/class.h b/src/coreclr/vm/class.h index 75003bae383d57..cc4762dd2db84d 100644 --- a/src/coreclr/vm/class.h +++ b/src/coreclr/vm/class.h @@ -1817,6 +1817,12 @@ class EEClass // DO NOT CREATE A NEW EEClass USING NEW! } #endif // !DACCESS_COMPILE + template friend struct ::cdac_offsets; +}; + +template<> struct ::cdac_offsets +{ + static constexpr size_t m_pMethodTable = offsetof(EEClass, m_pMethodTable); }; // -------------------------------------------------------------------------------------------- diff --git a/src/coreclr/vm/methodtable.h b/src/coreclr/vm/methodtable.h index 6782b921231eda..20e36256b4caf9 100644 --- a/src/coreclr/vm/methodtable.h +++ b/src/coreclr/vm/methodtable.h @@ -3668,6 +3668,7 @@ template<> struct ::cdac_offsets static constexpr size_t m_dwFlags = offsetof(MethodTable, m_dwFlags); static constexpr size_t m_BaseSize = offsetof(MethodTable, m_BaseSize); static constexpr size_t m_dwFlags2 = offsetof(MethodTable, m_dwFlags2); + static constexpr size_t m_pEEClassOrCanonMT = offsetof(MethodTable, m_pEEClass); }; #ifndef CROSSBITNESS_COMPILE diff --git a/src/native/managed/cdacreader/src/Contracts/Metadata.cs b/src/native/managed/cdacreader/src/Contracts/Metadata.cs index 1931f0db09dafa..0a380aa66f64b6 100644 --- a/src/native/managed/cdacreader/src/Contracts/Metadata.cs +++ b/src/native/managed/cdacreader/src/Contracts/Metadata.cs @@ -4,7 +4,8 @@ using System; using UntrustedMethodTable = Microsoft.Diagnostics.DataContractReader.Contracts.UntrustedMethodTable_1; using MethodTable = Microsoft.Diagnostics.DataContractReader.Contracts.MethodTable_1; -using System.Reflection; +using UntrustedEEClass = Microsoft.Diagnostics.DataContractReader.Contracts.UntrustedEEClass_1; +using EEClass = Microsoft.Diagnostics.DataContractReader.Contracts.EEClass_1; namespace Microsoft.Diagnostics.DataContractReader.Contracts; @@ -47,12 +48,15 @@ internal interface IMethodTableFlags public ushort RawGetComponentSize() => (ushort)(DwFlags >> 16); + public bool HasInstantiation() => throw new NotImplementedException(); } // GC Heap corruption may create situations where a putative pointer to a MethodTable // may point to garbage. So this struct represents a MethodTable that we don't necessarily // trust to be valid. // see Metadata_1.ValidateMethodTablePointer +// This doesn't need as many properties as MethodTable because we dont' want to be operating on +// an UntrustedMethodTable for too long internal struct UntrustedMethodTable_1 : IMethodTableFlags { private readonly Target _target; @@ -71,6 +75,26 @@ internal UntrustedMethodTable_1(Target target, TargetPointer methodTablePointer) public uint DwFlags2 => _target.Read(MethodTablePointer + (ulong)_type.Fields[nameof(DwFlags)].Offset); public uint BaseSize => _target.Read(MethodTablePointer + (ulong)_type.Fields[nameof(BaseSize)].Offset); + public TargetPointer EEClassOrCanonMT => _target.ReadPointer(MethodTablePointer + (ulong)_type.Fields[nameof(EEClassOrCanonMT)].Offset); + public TargetPointer EEClass => (EEClassOrCanonMT & (ulong)Metadata_1.EEClassOrCanonMTBits.Mask) == (ulong)Metadata_1.EEClassOrCanonMTBits.EEClass ? EEClassOrCanonMT : throw new InvalidOperationException("not an EEClass"); + +} + +internal struct UntrustedEEClass_1 +{ + public readonly Target _target; + private readonly Target.TypeInfo _type; + + public TargetPointer EEClassPointer { get; init; } + + internal UntrustedEEClass_1(Target target, TargetPointer eeClassPointer) + { + _target = target; + EEClassPointer = eeClassPointer; + _type = target.GetTypeInfo(DataType.EEClass); + } + + public TargetPointer MethodTable => _target.ReadPointer(EEClassPointer + (ulong)_type.Fields[nameof(MethodTable)].Offset); } @@ -87,8 +111,21 @@ internal MethodTable_1(Data.MethodTable data, bool isFreeObjectMT) public uint DwFlags => MethodTableData.DwFlags; public uint DwFlags2 => MethodTableData.DwFlags2; public uint BaseSize => MethodTableData.BaseSize; + public TargetPointer EEClassOrCanonMT => MethodTableData.EEClassOrCanonMT; + + public TargetPointer EEClass => (EEClassOrCanonMT & (ulong)Metadata_1.EEClassOrCanonMTBits.Mask) == (ulong)Metadata_1.EEClassOrCanonMTBits.EEClass ? EEClassOrCanonMT : throw new InvalidOperationException("not an EEClass"); } +internal struct EEClass_1 +{ + public Data.EEClass EEClassData { get; init; } + internal EEClass_1(Data.EEClass eeClassData) + { + EEClassData = eeClassData; + } + + public TargetPointer MethodTable => EEClassData.MethodTable; +} internal struct Metadata_1 : IMetadata @@ -162,6 +199,14 @@ internal enum WFLAGS_HIGH : uint } + [Flags] + internal enum EEClassOrCanonMTBits + { + EEClass = 0, + CanonMT = 1, + Mask = 1, + } + internal Metadata_1(Target target, TargetPointer freeObjectMethodTablePointer) { _target = target; @@ -175,6 +220,11 @@ private UntrustedMethodTable GetUntrustedMethodTableData(TargetPointer methodTab return new UntrustedMethodTable(_target, methodTablePointer); } + private UntrustedEEClass GetUntrustedEEClassData(TargetPointer eeClassPointer) + { + return new UntrustedEEClass(_target, eeClassPointer); + } + public MethodTable GetMethodTableData(TargetPointer methodTablePointer) { // Check if we cached it already @@ -230,11 +280,23 @@ private bool ValidateMethodTablePointer(ref readonly UntrustedMethodTable umt) private bool ValidateWithPossibleAV(ref readonly UntrustedMethodTable methodTable) { - //PTR_EEClass pEEClass = this->GetClassWithPossibleAV(); - //return ((pEEClass && (this == pEEClass->GetMethodTableWithPossibleAV())) || - // ((HasInstantiation() || IsArray()) && - // (pEEClass && (pEEClass->GetMethodTableWithPossibleAV()->GetClassWithPossibleAV() == pEEClass)))); - throw new NotImplementedException(); + TargetPointer eeClassPtr = GetClassWithPossibleAV(in methodTable); + if (eeClassPtr != TargetPointer.Null) + { + UntrustedEEClass eeClass = GetUntrustedEEClassData(eeClassPtr); + TargetPointer methodTablePtrFromClass = GetMethodTablePointerWithPossibleAV(in eeClass); + if (methodTable.MethodTablePointer == methodTablePtrFromClass) + { + return true; + } + if (((IMethodTableFlags)methodTable).HasInstantiation() || ((IMethodTableFlags)methodTable).IsArray()) + { + UntrustedMethodTable methodTableFromClass = GetUntrustedMethodTableData(methodTablePtrFromClass); + TargetPointer classFromMethodTable = GetClassWithPossibleAV(in methodTableFromClass); + return classFromMethodTable == eeClassPtr; + } + } + return false; } internal bool ValidateMethodTable(ref readonly UntrustedMethodTable methodTable) @@ -248,4 +310,14 @@ internal bool ValidateMethodTable(ref readonly UntrustedMethodTable methodTable) } return true; } + + private TargetPointer GetClassWithPossibleAV(ref readonly UntrustedMethodTable methodTable) + { + throw new NotImplementedException("TODO"); + } + + private TargetPointer GetMethodTablePointerWithPossibleAV(ref readonly UntrustedEEClass eeClass) + { + throw new NotImplementedException("TODO"); + } } diff --git a/src/native/managed/cdacreader/src/Data/EEClass.cs b/src/native/managed/cdacreader/src/Data/EEClass.cs new file mode 100644 index 00000000000000..5f122cc794b52d --- /dev/null +++ b/src/native/managed/cdacreader/src/Data/EEClass.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. + +using System; + +namespace Microsoft.Diagnostics.DataContractReader.Data; + +public sealed class EEClass : IData +{ + static EEClass IData.Create(Target target, TargetPointer address) => new EEClass(target, address); + public EEClass(Target target, TargetPointer address) + { + Target.TypeInfo type = target.GetTypeInfo(DataType.EEClass); + + MethodTable = target.ReadPointer(address + (ulong)type.Fields[nameof(MethodTable)].Offset); + } + + public TargetPointer MethodTable { get; init; } + +} diff --git a/src/native/managed/cdacreader/src/Data/MethodTable.cs b/src/native/managed/cdacreader/src/Data/MethodTable.cs index 1e4c8ff8f10916..5a455228ba1639 100644 --- a/src/native/managed/cdacreader/src/Data/MethodTable.cs +++ b/src/native/managed/cdacreader/src/Data/MethodTable.cs @@ -17,9 +17,11 @@ public MethodTable(Target target, TargetPointer address) DwFlags = target.Read(address + (ulong)type.Fields[nameof(DwFlags)].Offset); BaseSize = target.Read(address + (ulong)type.Fields[nameof(BaseSize)].Offset); DwFlags2 = target.Read(address + (ulong)type.Fields[nameof(DwFlags2)].Offset); + EEClassOrCanonMT = target.ReadPointer(address + (ulong)type.Fields[nameof(EEClassOrCanonMT)].Offset); } public uint DwFlags { get; init; } public uint BaseSize { get; init; } public uint DwFlags2 { get; init; } + public TargetPointer EEClassOrCanonMT { get; init; } } diff --git a/src/native/managed/cdacreader/src/DataType.cs b/src/native/managed/cdacreader/src/DataType.cs index 11625a79d16186..58e881dad5c737 100644 --- a/src/native/managed/cdacreader/src/DataType.cs +++ b/src/native/managed/cdacreader/src/DataType.cs @@ -23,4 +23,5 @@ public enum DataType Thread, ThreadStore, MethodTable, + EEClass, } diff --git a/src/native/managed/cdacreader/src/Target.cs b/src/native/managed/cdacreader/src/Target.cs index 4fc9ee52f38f5f..5cc0609d9ed82b 100644 --- a/src/native/managed/cdacreader/src/Target.cs +++ b/src/native/managed/cdacreader/src/Target.cs @@ -16,7 +16,7 @@ public struct TargetPointer : IEquatable public static TargetPointer Null = new(0); public static TargetPointer MinusOne = new(ulong.MaxValue); - public ulong Value; + public readonly ulong Value; public TargetPointer(ulong value) => Value = value; public static implicit operator ulong(TargetPointer p) => p.Value; From 66e5476fb3e7ee06db037bf406bbceeda4276d9e Mon Sep 17 00:00:00 2001 From: Aleksey Kliger Date: Fri, 31 May 2024 15:27:13 -0400 Subject: [PATCH 11/62] checkpoint: ValidateMethodTablePointer --- .../Contracts/Metadata.MethodTableFlags.cs | 170 ++++++++++++++++++ .../cdacreader/src/Contracts/Metadata.cs | 147 +++++---------- 2 files changed, 216 insertions(+), 101 deletions(-) create mode 100644 src/native/managed/cdacreader/src/Contracts/Metadata.MethodTableFlags.cs diff --git a/src/native/managed/cdacreader/src/Contracts/Metadata.MethodTableFlags.cs b/src/native/managed/cdacreader/src/Contracts/Metadata.MethodTableFlags.cs new file mode 100644 index 00000000000000..7585ac82c865b3 --- /dev/null +++ b/src/native/managed/cdacreader/src/Contracts/Metadata.MethodTableFlags.cs @@ -0,0 +1,170 @@ +// 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 UntrustedMethodTable = Microsoft.Diagnostics.DataContractReader.Contracts.UntrustedMethodTable_1; +using MethodTable = Microsoft.Diagnostics.DataContractReader.Contracts.MethodTable_1; +using UntrustedEEClass = Microsoft.Diagnostics.DataContractReader.Contracts.UntrustedEEClass_1; +using EEClass = Microsoft.Diagnostics.DataContractReader.Contracts.EEClass_1; + +namespace Microsoft.Diagnostics.DataContractReader.Contracts; + +internal partial struct Metadata_1 +{ + [Flags] + internal enum WFLAGS_LOW : uint + { + // We are overloading the low 2 bytes of m_dwFlags to be a component size for Strings + // and Arrays and some set of flags which we can be assured are of a specified state + // for Strings / Arrays, currently these will be a bunch of generics flags which don't + // apply to Strings / Arrays. + + UNUSED_ComponentSize_1 = 0x00000001, + // GC depends on this bit + HasCriticalFinalizer = 0x00000002, // finalizer must be run on Appdomain Unload + StaticsMask = 0x0000000C, + StaticsMask_NonDynamic = 0x00000000, + StaticsMask_Dynamic = 0x00000008, // dynamic statics (EnC, reflection.emit) + StaticsMask_Generics = 0x00000004, // generics statics + StaticsMask_CrossModuleGenerics = 0x0000000C, // cross module generics statics (NGen) + StaticsMask_IfGenericsThenCrossModule = 0x00000008, // helper constant to get rid of unnecessary check + + + GenericsMask = 0x00000030, + GenericsMask_NonGeneric = 0x00000000, // no instantiation + GenericsMask_GenericInst = 0x00000010, // regular instantiation, e.g. List + GenericsMask_SharedInst = 0x00000020, // shared instantiation, e.g. List<__Canon> or List> + GenericsMask_TypicalInst = 0x00000030, // the type instantiated at its formal parameters, e.g. List + + HasVariance = 0x00000100, // This is an instantiated type some of whose type parameters are co- or contra-variant + + HasDefaultCtor = 0x00000200, + HasPreciseInitCctors = 0x00000400, // Do we need to run class constructors at allocation time? (Not perf important, could be moved to EEClass + + // if FEATURE_HFA + IsHFA = 0x00000800, // This type is an HFA (Homogeneous Floating-point Aggregate) + + // if UNIX_AMD64_ABI + IsRegStructPassed = 0x00000800, // This type is a System V register passed struct. + + IsByRefLike = 0x00001000, + + HasBoxedRegularStatics = 0x00002000, + HasBoxedThreadStatics = 0x00004000, + + // In a perfect world we would fill these flags using other flags that we already have + // which have a constant value for something which has a component size. + UNUSED_ComponentSize_7 = 0x00008000, + + // IMPORTANT! IMPORTANT! IMPORTANT! + // + // As you change the flags in WFLAGS_LOW_ENUM you also need to change this + // to be up to date to reflect the default values of those flags for the + // case where this MethodTable is for a String or Array + StringArrayValues = //SET_FALSE(enum_flag_HasCriticalFinalizer) | + StaticsMask_NonDynamic | + //SET_FALSE(enum_flag_HasBoxedRegularStatics) | + //SET_FALSE(enum_flag_HasBoxedThreadStatics) | + GenericsMask_NonGeneric | + //SET_FALSE(enum_flag_HasVariance) | + //SET_FALSE(enum_flag_HasDefaultCtor) | + //SET_FALSE(enum_flag_HasPreciseInitCctors) + 0, + } + + [Flags] + internal enum WFLAGS_HIGH : uint + { + Category_Mask = 0x000F0000, + + Category_Class = 0x00000000, + Category_Unused_1 = 0x00010000, + Category_Unused_2 = 0x00020000, + Category_Unused_3 = 0x00030000, + + Category_ValueType = 0x00040000, + Category_ValueType_Mask = 0x000C0000, + Category_Nullable = 0x00050000, // sub-category of ValueType + Category_PrimitiveValueType = 0x00060000, // sub-category of ValueType, Enum or primitive value type + Category_TruePrimitive = 0x00070000, // sub-category of ValueType, Primitive (ELEMENT_TYPE_I, etc.) + + Category_Array = 0x00080000, + Category_Array_Mask = 0x000C0000, + // Category_IfArrayThenUnused = 0x00010000, // sub-category of Array + Category_IfArrayThenSzArray = 0x00020000, // sub-category of Array + + Category_Interface = 0x000C0000, + Category_Unused_4 = 0x000D0000, + Category_Unused_5 = 0x000E0000, + Category_Unused_6 = 0x000F0000, + + Category_ElementTypeMask = 0x000E0000, // bits that matter for element type mask + + // GC depends on this bit + HasFinalizer = 0x00100000, // instances require finalization + + IDynamicInterfaceCastable = 0x10000000, // class implements IDynamicInterfaceCastable interface + + ICastable = 0x00400000, // class implements ICastable interface + + RequiresAlign8 = 0x00800000, // Type requires 8-byte alignment (only set on platforms that require this and don't get it implicitly) + + ContainsPointers = 0x01000000, + + HasTypeEquivalence = 0x02000000, // can be equivalent to another type + + IsTrackedReferenceWithFinalizer = 0x04000000, + + // GC depends on this bit + Collectible = 0x00200000, + ContainsGenericVariables = 0x20000000, // we cache this flag to help detect these efficiently and + // to detect this condition when restoring + + ComObject = 0x40000000, // class is a com object + + HasComponentSize = 0x80000000, // This is set if component size is used for flags. + + // Types that require non-trivial interface cast have this bit set in the category + NonTrivialInterfaceCast = Category_Array + | ComObject + | ICastable + | IDynamicInterfaceCastable + | Category_ValueType + + } +} +internal interface IMethodTableFlags +{ + public uint DwFlags { get; } + public uint DwFlags2 { get; } + public uint BaseSize { get; } + + private Metadata_1.WFLAGS_HIGH FlagsHigh => (Metadata_1.WFLAGS_HIGH)DwFlags; + private Metadata_1.WFLAGS_LOW FlagsLow => (Metadata_1.WFLAGS_LOW)DwFlags; + private int GetTypeDefRid() => (int)(DwFlags2 >> Metadata_1.Constants.MethodTableDwFlags2TypeDefRidShift); + + public Metadata_1.WFLAGS_LOW GetFlag(Metadata_1.WFLAGS_LOW mask) => throw new NotImplementedException("TODO"); + public Metadata_1.WFLAGS_HIGH GetFlag(Metadata_1.WFLAGS_HIGH mask) => FlagsHigh & mask; + public bool IsInterface => GetFlag(Metadata_1.WFLAGS_HIGH.Category_Mask) == Metadata_1.WFLAGS_HIGH.Category_Interface; + public bool IsString => HasComponentSize && !IsArray && RawGetComponentSize() == 2; + + public bool HasComponentSize => GetFlag(Metadata_1.WFLAGS_HIGH.HasComponentSize) != 0; + + public bool IsArray => GetFlag(Metadata_1.WFLAGS_HIGH.Category_Array_Mask) == Metadata_1.WFLAGS_HIGH.Category_Array; + + public bool IsStringOrArray => HasComponentSize; + public ushort RawGetComponentSize() => (ushort)(DwFlags >> 16); + + public bool TestFlagWithMask(Metadata_1.WFLAGS_LOW mask, Metadata_1.WFLAGS_LOW flag) + { + if (IsStringOrArray) + { + return (Metadata_1.WFLAGS_LOW.StringArrayValues & mask) == flag; + } + else + { + return (FlagsLow & mask) == flag; + } + } + public bool HasInstantiation => !TestFlagWithMask(Metadata_1.WFLAGS_LOW.GenericsMask, Metadata_1.WFLAGS_LOW.GenericsMask_NonGeneric); +} diff --git a/src/native/managed/cdacreader/src/Contracts/Metadata.cs b/src/native/managed/cdacreader/src/Contracts/Metadata.cs index 0a380aa66f64b6..240f4cada28374 100644 --- a/src/native/managed/cdacreader/src/Contracts/Metadata.cs +++ b/src/native/managed/cdacreader/src/Contracts/Metadata.cs @@ -31,26 +31,6 @@ internal struct Metadata : IMetadata // Everything throws NotImplementedException } -internal interface IMethodTableFlags -{ - public uint DwFlags { get; } - public uint DwFlags2 { get; } - public uint BaseSize { get; } - private int GetTypeDefRid() => (int)(DwFlags2 >> Metadata_1.Constants.MethodTableDwFlags2TypeDefRidShift); - - public uint GetFlag(Metadata_1.WFLAGS_HIGH mask) => DwFlags & (uint)mask; - public bool IsInterface => GetFlag(Metadata_1.WFLAGS_HIGH.Category_Mask) == (uint)Metadata_1.WFLAGS_HIGH.Category_Interface; - public bool IsString => HasComponentSize() && !IsArray() && RawGetComponentSize() == 2; - - public bool HasComponentSize() => GetFlag(Metadata_1.WFLAGS_HIGH.HasComponentSize) != 0; - - public bool IsArray() => GetFlag(Metadata_1.WFLAGS_HIGH.Category_Array_Mask) == (uint)Metadata_1.WFLAGS_HIGH.Category_Array; - - public ushort RawGetComponentSize() => (ushort)(DwFlags >> 16); - - public bool HasInstantiation() => throw new NotImplementedException(); -} - // GC Heap corruption may create situations where a putative pointer to a MethodTable // may point to garbage. So this struct represents a MethodTable that we don't necessarily // trust to be valid. @@ -61,23 +41,23 @@ internal struct UntrustedMethodTable_1 : IMethodTableFlags { private readonly Target _target; private readonly Target.TypeInfo _type; - public TargetPointer MethodTablePointer { get; init; } + public TargetPointer Address { get; init; } internal UntrustedMethodTable_1(Target target, TargetPointer methodTablePointer) { _target = target; _type = target.GetTypeInfo(DataType.MethodTable); - MethodTablePointer = methodTablePointer; + Address = methodTablePointer; } // all these accessors might throw if MethodTablePointer is invalid - public uint DwFlags => _target.Read(MethodTablePointer + (ulong)_type.Fields[nameof(DwFlags2)].Offset); - public uint DwFlags2 => _target.Read(MethodTablePointer + (ulong)_type.Fields[nameof(DwFlags)].Offset); - public uint BaseSize => _target.Read(MethodTablePointer + (ulong)_type.Fields[nameof(BaseSize)].Offset); - - public TargetPointer EEClassOrCanonMT => _target.ReadPointer(MethodTablePointer + (ulong)_type.Fields[nameof(EEClassOrCanonMT)].Offset); - public TargetPointer EEClass => (EEClassOrCanonMT & (ulong)Metadata_1.EEClassOrCanonMTBits.Mask) == (ulong)Metadata_1.EEClassOrCanonMTBits.EEClass ? EEClassOrCanonMT : throw new InvalidOperationException("not an EEClass"); + public uint DwFlags => _target.Read(Address + (ulong)_type.Fields[nameof(DwFlags2)].Offset); + public uint DwFlags2 => _target.Read(Address + (ulong)_type.Fields[nameof(DwFlags)].Offset); + public uint BaseSize => _target.Read(Address + (ulong)_type.Fields[nameof(BaseSize)].Offset); + public TargetPointer EEClassOrCanonMT => _target.ReadPointer(Address + (ulong)_type.Fields[nameof(EEClassOrCanonMT)].Offset); + public TargetPointer EEClass => Metadata_1.GetEEClassOrCanonMTBits(EEClassOrCanonMT) == Metadata_1.EEClassOrCanonMTBits.EEClass ? EEClassOrCanonMT : throw new InvalidOperationException("not an EEClass"); + public TargetPointer CanonMT => Metadata_1.GetEEClassOrCanonMTBits(EEClassOrCanonMT) == Metadata_1.EEClassOrCanonMTBits.CanonMT ? EEClassOrCanonMT : throw new InvalidOperationException("not a canonical method table"); } internal struct UntrustedEEClass_1 @@ -85,16 +65,16 @@ internal struct UntrustedEEClass_1 public readonly Target _target; private readonly Target.TypeInfo _type; - public TargetPointer EEClassPointer { get; init; } + public TargetPointer Address { get; init; } internal UntrustedEEClass_1(Target target, TargetPointer eeClassPointer) { _target = target; - EEClassPointer = eeClassPointer; + Address = eeClassPointer; _type = target.GetTypeInfo(DataType.EEClass); } - public TargetPointer MethodTable => _target.ReadPointer(EEClassPointer + (ulong)_type.Fields[nameof(MethodTable)].Offset); + public TargetPointer MethodTable => _target.ReadPointer(Address + (ulong)_type.Fields[nameof(MethodTable)].Offset); } @@ -113,7 +93,7 @@ internal MethodTable_1(Data.MethodTable data, bool isFreeObjectMT) public uint BaseSize => MethodTableData.BaseSize; public TargetPointer EEClassOrCanonMT => MethodTableData.EEClassOrCanonMT; - public TargetPointer EEClass => (EEClassOrCanonMT & (ulong)Metadata_1.EEClassOrCanonMTBits.Mask) == (ulong)Metadata_1.EEClassOrCanonMTBits.EEClass ? EEClassOrCanonMT : throw new InvalidOperationException("not an EEClass"); + public TargetPointer EEClass => Metadata_1.GetEEClassOrCanonMTBits(EEClassOrCanonMT) == Metadata_1.EEClassOrCanonMTBits.EEClass ? EEClassOrCanonMT : throw new InvalidOperationException("not an EEClass"); } internal struct EEClass_1 @@ -128,7 +108,7 @@ internal EEClass_1(Data.EEClass eeClassData) } -internal struct Metadata_1 : IMetadata +internal partial struct Metadata_1 : IMetadata { private readonly Target _target; private readonly TargetPointer _freeObjectMethodTablePointer; @@ -138,67 +118,6 @@ internal static class Constants internal const int MethodTableDwFlags2TypeDefRidShift = 8; } - [Flags] - internal enum WFLAGS_HIGH : uint - { - Category_Mask = 0x000F0000, - - Category_Class = 0x00000000, - Category_Unused_1 = 0x00010000, - Category_Unused_2 = 0x00020000, - Category_Unused_3 = 0x00030000, - - Category_ValueType = 0x00040000, - Category_ValueType_Mask = 0x000C0000, - Category_Nullable = 0x00050000, // sub-category of ValueType - Category_PrimitiveValueType = 0x00060000, // sub-category of ValueType, Enum or primitive value type - Category_TruePrimitive = 0x00070000, // sub-category of ValueType, Primitive (ELEMENT_TYPE_I, etc.) - - Category_Array = 0x00080000, - Category_Array_Mask = 0x000C0000, - // Category_IfArrayThenUnused = 0x00010000, // sub-category of Array - Category_IfArrayThenSzArray = 0x00020000, // sub-category of Array - - Category_Interface = 0x000C0000, - Category_Unused_4 = 0x000D0000, - Category_Unused_5 = 0x000E0000, - Category_Unused_6 = 0x000F0000, - - Category_ElementTypeMask = 0x000E0000, // bits that matter for element type mask - - // GC depends on this bit - HasFinalizer = 0x00100000, // instances require finalization - - IDynamicInterfaceCastable = 0x10000000, // class implements IDynamicInterfaceCastable interface - - ICastable = 0x00400000, // class implements ICastable interface - - RequiresAlign8 = 0x00800000, // Type requires 8-byte alignment (only set on platforms that require this and don't get it implicitly) - - ContainsPointers = 0x01000000, - - HasTypeEquivalence = 0x02000000, // can be equivalent to another type - - IsTrackedReferenceWithFinalizer = 0x04000000, - - // GC depends on this bit - Collectible = 0x00200000, - ContainsGenericVariables = 0x20000000, // we cache this flag to help detect these efficiently and - // to detect this condition when restoring - - ComObject = 0x40000000, // class is a com object - - HasComponentSize = 0x80000000, // This is set if component size is used for flags. - - // Types that require non-trivial interface cast have this bit set in the category - NonTrivialInterfaceCast = Category_Array - | ComObject - | ICastable - | IDynamicInterfaceCastable - | Category_ValueType - - } - [Flags] internal enum EEClassOrCanonMTBits { @@ -280,16 +199,27 @@ private bool ValidateMethodTablePointer(ref readonly UntrustedMethodTable umt) private bool ValidateWithPossibleAV(ref readonly UntrustedMethodTable methodTable) { + // For non-generic classes, we can rely on comparing + // object->methodtable->class->methodtable + // to + // object->methodtable + // + // However, for generic instantiation this does not work. There we must + // compare + // + // object->methodtable->class->methodtable->class + // to + // object->methodtable->class TargetPointer eeClassPtr = GetClassWithPossibleAV(in methodTable); if (eeClassPtr != TargetPointer.Null) { UntrustedEEClass eeClass = GetUntrustedEEClassData(eeClassPtr); - TargetPointer methodTablePtrFromClass = GetMethodTablePointerWithPossibleAV(in eeClass); - if (methodTable.MethodTablePointer == methodTablePtrFromClass) + TargetPointer methodTablePtrFromClass = GetMethodTableWithPossibleAV(in eeClass); + if (methodTable.Address == methodTablePtrFromClass) { return true; } - if (((IMethodTableFlags)methodTable).HasInstantiation() || ((IMethodTableFlags)methodTable).IsArray()) + if (((IMethodTableFlags)methodTable).HasInstantiation || ((IMethodTableFlags)methodTable).IsArray) { UntrustedMethodTable methodTableFromClass = GetUntrustedMethodTableData(methodTablePtrFromClass); TargetPointer classFromMethodTable = GetClassWithPossibleAV(in methodTableFromClass); @@ -299,7 +229,7 @@ private bool ValidateWithPossibleAV(ref readonly UntrustedMethodTable methodTabl return false; } - internal bool ValidateMethodTable(ref readonly UntrustedMethodTable methodTable) + private bool ValidateMethodTable(ref readonly UntrustedMethodTable methodTable) { if (!((IMethodTableFlags)methodTable).IsInterface && !((IMethodTableFlags)methodTable).IsString) { @@ -311,13 +241,28 @@ internal bool ValidateMethodTable(ref readonly UntrustedMethodTable methodTable) return true; } + internal static EEClassOrCanonMTBits GetEEClassOrCanonMTBits(TargetPointer eeClassOrCanonMTPtr) + { + return (EEClassOrCanonMTBits)(eeClassOrCanonMTPtr & (ulong)EEClassOrCanonMTBits.Mask); + } private TargetPointer GetClassWithPossibleAV(ref readonly UntrustedMethodTable methodTable) { - throw new NotImplementedException("TODO"); + TargetPointer eeClassOrCanonMT = methodTable.EEClassOrCanonMT; + + if (GetEEClassOrCanonMTBits(eeClassOrCanonMT) == EEClassOrCanonMTBits.EEClass) + { + return methodTable.EEClass; + } + else + { + TargetPointer canonicalMethodTablePtr = methodTable.CanonMT; + UntrustedMethodTable umt = GetUntrustedMethodTableData(canonicalMethodTablePtr); + return umt.EEClass; + } } - private TargetPointer GetMethodTablePointerWithPossibleAV(ref readonly UntrustedEEClass eeClass) + private static TargetPointer GetMethodTableWithPossibleAV(ref readonly UntrustedEEClass eeClass) { - throw new NotImplementedException("TODO"); + return eeClass.MethodTable; } } From 95914b8bc6d9885a2deef5e694b20b473af4f813 Mon Sep 17 00:00:00 2001 From: Aleksey Kliger Date: Fri, 31 May 2024 15:52:16 -0400 Subject: [PATCH 12/62] cp: delegate from legacy dac --- src/coreclr/debug/daccess/dacimpl.h | 1 + src/coreclr/debug/daccess/request.cpp | 35 +++++++++++++++++-- .../cdacreader/src/Contracts/Registry.cs | 1 + .../cdacreader/src/Legacy/SOSDacImpl.cs | 18 +++++++++- 4 files changed, 51 insertions(+), 4 deletions(-) diff --git a/src/coreclr/debug/daccess/dacimpl.h b/src/coreclr/debug/daccess/dacimpl.h index 4b05e401a06b1b..c4439070a445a2 100644 --- a/src/coreclr/debug/daccess/dacimpl.h +++ b/src/coreclr/debug/daccess/dacimpl.h @@ -1231,6 +1231,7 @@ class ClrDataAccess HRESULT GetThreadDataImpl(CLRDATA_ADDRESS threadAddr, struct DacpThreadData *threadData); HRESULT GetThreadStoreDataImpl(struct DacpThreadStoreData *data); + HRESULT GetMethodTableDataImpl(CLRDATA_ADDRESS mt, struct DacpMethodTableData *data); BOOL IsExceptionFromManagedCode(EXCEPTION_RECORD * pExceptionRecord); #ifndef TARGET_UNIX diff --git a/src/coreclr/debug/daccess/request.cpp b/src/coreclr/debug/daccess/request.cpp index 02862b26c630f8..4c094ec6a3440d 100644 --- a/src/coreclr/debug/daccess/request.cpp +++ b/src/coreclr/debug/daccess/request.cpp @@ -1769,12 +1769,42 @@ ClrDataAccess::GetMethodTableData(CLRDATA_ADDRESS mt, struct DacpMethodTableData return E_INVALIDARG; SOSDacEnter(); + if (m_cdacSos != NULL) + { + // Try the cDAC first - it will return E_NOTIMPL if it doesn't support this method yet. Fall back to the DAC. + hr = m_cdacSos->GetMethodTableData(mt, MTData); + if (FAILED(hr)) + { + hr = GetMethodTableDataImpl(mt, MTData); + } +#ifdef _DEBUG + else + { + // Assert that the data is the same as what we get from the DAC. + DacpMethodTableData mtDataLocal; + HRESULT hrLocal = GetMethodTableDataImpl(mt, &mtDataLocal); + _ASSERTE(hr == hrLocal); + //_ASSERTE(threadStoreData->threadCount == threadStoreDataLocal.threadCount); + // TODO(cdac) + } +#endif + } + else + { + hr = GetMethodTableDataImpl (mt, MTData); + } + SOSDacLeave(); + return hr; +} +HRESULT +ClrDataAccess::GetMethodTableDataImpl(CLRDATA_ADDRESS mt, struct DacpMethodTableData *MTData) +{ PTR_MethodTable pMT = PTR_MethodTable(TO_TADDR(mt)); BOOL bIsFree = FALSE; if (!DacValidateMethodTable(pMT, bIsFree)) { - hr = E_INVALIDARG; + return E_INVALIDARG; } else { @@ -1801,8 +1831,7 @@ ClrDataAccess::GetMethodTableData(CLRDATA_ADDRESS mt, struct DacpMethodTableData } } - SOSDacLeave(); - return hr; + return S_OK; } HRESULT diff --git a/src/native/managed/cdacreader/src/Contracts/Registry.cs b/src/native/managed/cdacreader/src/Contracts/Registry.cs index aad64ce5271804..d52c3ff6a2dba9 100644 --- a/src/native/managed/cdacreader/src/Contracts/Registry.cs +++ b/src/native/managed/cdacreader/src/Contracts/Registry.cs @@ -19,6 +19,7 @@ public Registry(Target target) } public IThread Thread => GetContract(); + public IMetadata Metadata => GetContract(); private T GetContract() where T : IContract { diff --git a/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs b/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs index 5bc186851623a6..3ee9ebda91f7bf 100644 --- a/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs +++ b/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs @@ -4,6 +4,7 @@ using System; using System.Runtime.InteropServices; using System.Runtime.InteropServices.Marshalling; +using Microsoft.Diagnostics.DataContractReader.Data; namespace Microsoft.Diagnostics.DataContractReader.Legacy; @@ -81,7 +82,22 @@ public int GetBreakingChangeVersion() public unsafe int GetMethodDescPtrFromFrame(ulong frameAddr, ulong* ppMD) => HResults.E_NOTIMPL; public unsafe int GetMethodDescPtrFromIP(ulong ip, ulong* ppMD) => HResults.E_NOTIMPL; public unsafe int GetMethodDescTransparencyData(ulong methodDesc, void* data) => HResults.E_NOTIMPL; - public unsafe int GetMethodTableData(ulong mt, DacpMethodTableData* data) => HResults.E_NOTIMPL; + public unsafe int GetMethodTableData(ulong mt, DacpMethodTableData* data) + { + if (mt == 0 || data == null) + return HResults.E_INVALIDARG; + try + { + Contracts.IMetadata contract = _target.Contracts.Metadata; + Contracts.MethodTable_1 methodTable = contract.GetMethodTableData(mt); + + return HResults.E_NOTIMPL; + } + catch (Exception ex) + { + return ex.HResult; + } + } public unsafe int GetMethodTableFieldData(ulong mt, void* data) => HResults.E_NOTIMPL; public unsafe int GetMethodTableForEEClass(ulong eeClass, ulong* value) => HResults.E_NOTIMPL; public unsafe int GetMethodTableName(ulong mt, uint count, char* mtName, uint* pNeeded) => HResults.E_NOTIMPL; From 2b8fda3509e39ff1c2b316d07129b00c3208af7e Mon Sep 17 00:00:00 2001 From: Aleksey Kliger Date: Tue, 11 Jun 2024 15:46:55 -0400 Subject: [PATCH 13/62] add Metadata to runtime contract descriptor --- src/coreclr/debug/runtimeinfo/contracts.jsonc | 1 + 1 file changed, 1 insertion(+) diff --git a/src/coreclr/debug/runtimeinfo/contracts.jsonc b/src/coreclr/debug/runtimeinfo/contracts.jsonc index ab82fbc38c40a0..e6fe0aa42728bd 100644 --- a/src/coreclr/debug/runtimeinfo/contracts.jsonc +++ b/src/coreclr/debug/runtimeinfo/contracts.jsonc @@ -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 { + "Metadata": 1, "Thread": 1, "SOSBreakingChangeVersion": 1 // example contract: "runtime exports an SOS breaking change version global" } From 5c7d2acf7e53905197ba9eda1a3949680b85e423 Mon Sep 17 00:00:00 2001 From: Aleksey Kliger Date: Wed, 12 Jun 2024 11:14:28 -0400 Subject: [PATCH 14/62] checkpoint: more MethodTable fields --- .../debug/runtimeinfo/datadescriptor.h | 2 + src/coreclr/vm/methodtable.h | 2 + .../cdacreader/src/Contracts/Metadata.cs | 38 ++++++++++++++++++- .../cdacreader/src/Data/MethodTable.cs | 2 + .../cdacreader/src/Legacy/SOSDacImpl.cs | 27 ++++++++++++- 5 files changed, 69 insertions(+), 2 deletions(-) diff --git a/src/coreclr/debug/runtimeinfo/datadescriptor.h b/src/coreclr/debug/runtimeinfo/datadescriptor.h index 698c2d7824d5fc..f68a0abc176b2a 100644 --- a/src/coreclr/debug/runtimeinfo/datadescriptor.h +++ b/src/coreclr/debug/runtimeinfo/datadescriptor.h @@ -134,6 +134,8 @@ CDAC_TYPE_FIELD(MethodTable, /*uint32*/, DwFlags, cdac_offsets::m_d CDAC_TYPE_FIELD(MethodTable, /*uint32*/, BaseSize, cdac_offsets::m_BaseSize) CDAC_TYPE_FIELD(MethodTable, /*uint32*/, DwFlags2, cdac_offsets::m_dwFlags2) CDAC_TYPE_FIELD(MethodTable, /*nuint*/, EEClassOrCanonMT, cdac_offsets::m_pEEClassOrCanonMT) +CDAC_TYPE_FIELD(MethodTable, /*pointer*/, Module, cdac_offsets::m_pModule) +CDAC_TYPE_FIELD(MethodTable, /*pointer*/, AuxiliaryData, cdac_offsets::m_pAuxiliaryData) CDAC_TYPE_END(MethodTable) CDAC_TYPE_BEGIN(EEClass) diff --git a/src/coreclr/vm/methodtable.h b/src/coreclr/vm/methodtable.h index 20e36256b4caf9..67744c07b14c11 100644 --- a/src/coreclr/vm/methodtable.h +++ b/src/coreclr/vm/methodtable.h @@ -3669,6 +3669,8 @@ template<> struct ::cdac_offsets static constexpr size_t m_BaseSize = offsetof(MethodTable, m_BaseSize); static constexpr size_t m_dwFlags2 = offsetof(MethodTable, m_dwFlags2); static constexpr size_t m_pEEClassOrCanonMT = offsetof(MethodTable, m_pEEClass); + static constexpr size_t m_pModule = offsetof(MethodTable, m_pModule); + static constexpr size_t m_pAuxiliaryData = offsetof(MethodTable, m_pAuxiliaryData); }; #ifndef CROSSBITNESS_COMPILE diff --git a/src/native/managed/cdacreader/src/Contracts/Metadata.cs b/src/native/managed/cdacreader/src/Contracts/Metadata.cs index 240f4cada28374..7bc92457b3b02c 100644 --- a/src/native/managed/cdacreader/src/Contracts/Metadata.cs +++ b/src/native/managed/cdacreader/src/Contracts/Metadata.cs @@ -24,6 +24,8 @@ static IContract IContract.Create(Target target, int version) } public virtual MethodTable GetMethodTableData(TargetPointer targetPointer) => throw new NotImplementedException(); + + public virtual TargetPointer GetClass(in MethodTable methodTable) => throw new NotImplementedException(); } internal struct Metadata : IMetadata @@ -57,7 +59,20 @@ internal UntrustedMethodTable_1(Target target, TargetPointer methodTablePointer) public TargetPointer EEClassOrCanonMT => _target.ReadPointer(Address + (ulong)_type.Fields[nameof(EEClassOrCanonMT)].Offset); public TargetPointer EEClass => Metadata_1.GetEEClassOrCanonMTBits(EEClassOrCanonMT) == Metadata_1.EEClassOrCanonMTBits.EEClass ? EEClassOrCanonMT : throw new InvalidOperationException("not an EEClass"); - public TargetPointer CanonMT => Metadata_1.GetEEClassOrCanonMTBits(EEClassOrCanonMT) == Metadata_1.EEClassOrCanonMTBits.CanonMT ? EEClassOrCanonMT : throw new InvalidOperationException("not a canonical method table"); + public TargetPointer CanonMT + { + get + { + if (Metadata_1.GetEEClassOrCanonMTBits(EEClassOrCanonMT) == Metadata_1.EEClassOrCanonMTBits.CanonMT) + { + return new TargetPointer((ulong)EEClassOrCanonMT & ~(ulong)Metadata_1.EEClassOrCanonMTBits.Mask); + } + else + { + throw new InvalidOperationException("not a canonical method table"); + } + } + } } internal struct UntrustedEEClass_1 @@ -92,8 +107,14 @@ internal MethodTable_1(Data.MethodTable data, bool isFreeObjectMT) public uint DwFlags2 => MethodTableData.DwFlags2; public uint BaseSize => MethodTableData.BaseSize; public TargetPointer EEClassOrCanonMT => MethodTableData.EEClassOrCanonMT; + public TargetPointer Module => MethodTableData.Module; public TargetPointer EEClass => Metadata_1.GetEEClassOrCanonMTBits(EEClassOrCanonMT) == Metadata_1.EEClassOrCanonMTBits.EEClass ? EEClassOrCanonMT : throw new InvalidOperationException("not an EEClass"); + + public bool IsString => ((IMethodTableFlags)this).IsString; + + public int GetComponentSize() => ((IMethodTableFlags)this).HasComponentSize ? ((IMethodTableFlags)this).RawGetComponentSize() : 0; + } internal struct EEClass_1 @@ -265,4 +286,19 @@ private static TargetPointer GetMethodTableWithPossibleAV(ref readonly Untrusted { return eeClass.MethodTable; } + + internal TargetPointer GetClass(ref readonly MethodTable methodTable) + { + switch (GetEEClassOrCanonMTBits(methodTable.EEClassOrCanonMT)) + { + case EEClassOrCanonMTBits.EEClass: + return methodTable.EEClassOrCanonMT; + case EEClassOrCanonMTBits.CanonMT: + TargetPointer canonMTPtr = new TargetPointer((ulong)methodTable.EEClassOrCanonMT & ~(ulong)Metadata_1.EEClassOrCanonMTBits.Mask); + MethodTable canonMT = GetMethodTableData(canonMTPtr); + return canonMT.EEClassOrCanonMT; // canonical method table EEClassOrCanonMT is always EEClass + default: + throw new InvalidOperationException(); + } + } } diff --git a/src/native/managed/cdacreader/src/Data/MethodTable.cs b/src/native/managed/cdacreader/src/Data/MethodTable.cs index 5a455228ba1639..60b30839cf18ee 100644 --- a/src/native/managed/cdacreader/src/Data/MethodTable.cs +++ b/src/native/managed/cdacreader/src/Data/MethodTable.cs @@ -18,10 +18,12 @@ public MethodTable(Target target, TargetPointer address) BaseSize = target.Read(address + (ulong)type.Fields[nameof(BaseSize)].Offset); DwFlags2 = target.Read(address + (ulong)type.Fields[nameof(DwFlags2)].Offset); EEClassOrCanonMT = target.ReadPointer(address + (ulong)type.Fields[nameof(EEClassOrCanonMT)].Offset); + Module = target.ReadPointer(address + (ulong)type.Fields[(nameof(Module))].Offset); } public uint DwFlags { get; init; } public uint BaseSize { get; init; } public uint DwFlags2 { get; init; } public TargetPointer EEClassOrCanonMT { get; init; } + public TargetPointer Module { get; init; } } diff --git a/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs b/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs index 3ee9ebda91f7bf..18564b49486537 100644 --- a/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs +++ b/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs @@ -91,7 +91,32 @@ public unsafe int GetMethodTableData(ulong mt, DacpMethodTableData* data) Contracts.IMetadata contract = _target.Contracts.Metadata; Contracts.MethodTable_1 methodTable = contract.GetMethodTableData(mt); - return HResults.E_NOTIMPL; + DacpMethodTableData result = default; + result.baseSize = methodTable.BaseSize; + if (methodTable.IsString) + result.baseSize -= 2 /*sizeof(WCHAR) */; + result.componentSize = checked((uint)methodTable.GetComponentSize()); + result.bIsFree = methodTable.IsFreeObjectMethodTable ? 1 : 0; + if (!methodTable.IsFreeObjectMethodTable) + { + result.module = methodTable.Module; + result.@class = contract.GetClass(in methodTable); +#if false + // TODO + MTData->ParentMethodTable = HOST_CDADDR(pMT->GetParentMethodTable()); ; + MTData->wNumInterfaces = (WORD)pMT->GetNumInterfaces(); + MTData->wNumMethods = pMT->GetNumMethods(); + MTData->wNumVtableSlots = pMT->GetNumVtableSlots(); + MTData->wNumVirtuals = pMT->GetNumVirtuals(); + MTData->cl = pMT->GetCl(); + MTData->dwAttrClass = pMT->GetAttrClass(); + MTData->bContainsPointers = pMT->ContainsPointers(); + MTData->bIsShared = FALSE; + MTData->bIsDynamic = pMT->IsDynamicStatics(); +#endif + return HResults.E_NOTIMPL; + } + return HResults.S_OK; } catch (Exception ex) { From 7be5c6079c795a028b0458502eedbf1fc5b6ff95 Mon Sep 17 00:00:00 2001 From: Aleksey Kliger Date: Thu, 13 Jun 2024 14:55:28 -0400 Subject: [PATCH 15/62] checkpoint GetMethodTableData implemented --- .../debug/runtimeinfo/datadescriptor.h | 11 +++ src/coreclr/vm/class.h | 3 + src/coreclr/vm/methodtable.h | 3 + .../Contracts/Metadata.MethodTableFlags.cs | 6 +- .../cdacreader/src/Contracts/Metadata.cs | 92 +++++++++++++++++-- .../managed/cdacreader/src/Data/EEClass.cs | 7 +- .../cdacreader/src/Data/MethodTable.cs | 10 +- .../src/Data/MethodTableAuxiliaryData.cs | 21 +++++ src/native/managed/cdacreader/src/DataType.cs | 1 + .../cdacreader/src/Legacy/SOSDacImpl.cs | 27 +++--- 10 files changed, 152 insertions(+), 29 deletions(-) create mode 100644 src/native/managed/cdacreader/src/Data/MethodTableAuxiliaryData.cs diff --git a/src/coreclr/debug/runtimeinfo/datadescriptor.h b/src/coreclr/debug/runtimeinfo/datadescriptor.h index f68a0abc176b2a..f705fe0f85e03a 100644 --- a/src/coreclr/debug/runtimeinfo/datadescriptor.h +++ b/src/coreclr/debug/runtimeinfo/datadescriptor.h @@ -136,13 +136,24 @@ CDAC_TYPE_FIELD(MethodTable, /*uint32*/, DwFlags2, cdac_offsets::m_ CDAC_TYPE_FIELD(MethodTable, /*nuint*/, EEClassOrCanonMT, cdac_offsets::m_pEEClassOrCanonMT) CDAC_TYPE_FIELD(MethodTable, /*pointer*/, Module, cdac_offsets::m_pModule) CDAC_TYPE_FIELD(MethodTable, /*pointer*/, AuxiliaryData, cdac_offsets::m_pAuxiliaryData) +CDAC_TYPE_FIELD(MethodTable, /*pointer*/, ParentMethodTable, cdac_offsets::m_pParentMethodTable) +CDAC_TYPE_FIELD(MethodTable, /*uint16*/, NumInterfaces, cdac_offsets::m_wNumInterfaces) +CDAC_TYPE_FIELD(MethodTable, /*uint16*/, NumVirtuals, cdac_offsets::m_wNumVirtuals) CDAC_TYPE_END(MethodTable) CDAC_TYPE_BEGIN(EEClass) CDAC_TYPE_INDETERMINATE(EEClass) CDAC_TYPE_FIELD(EEClass, /*pointer*/, MethodTable, cdac_offsets::m_pMethodTable) +CDAC_TYPE_FIELD(EEClass, /*uint16*/, NumMethods, cdac_offsets::m_NumMethods) +CDAC_TYPE_FIELD(EEClass, /*uint16*/, NumNonVirtualSlots, cdac_offsets::m_NumNonVirtualSlots) +CDAC_TYPE_FIELD(EEClass, /*uint32*/, DwAttrClass, cdac_offsets::m_dwAttrClass) CDAC_TYPE_END(EEClass) +CDAC_TYPE_BEGIN(MethodTableAuxiliaryData) +CDAC_TYPE_INDETERMINATE(MethodTableAuxiliaryData) +CDAC_TYPE_FIELD(MethodTableAuxiliaryData, /*uint32*/, DwFlags, offsetof(MethodTableAuxiliaryData, m_dwFlags)) +CDAC_TYPE_END(MethodTableAuxiliaryData) + CDAC_TYPES_END() CDAC_GLOBALS_BEGIN() diff --git a/src/coreclr/vm/class.h b/src/coreclr/vm/class.h index cc4762dd2db84d..c3e18fd0fb3dc0 100644 --- a/src/coreclr/vm/class.h +++ b/src/coreclr/vm/class.h @@ -1823,6 +1823,9 @@ class EEClass // DO NOT CREATE A NEW EEClass USING NEW! template<> struct ::cdac_offsets { static constexpr size_t m_pMethodTable = offsetof(EEClass, m_pMethodTable); + static constexpr size_t m_NumMethods = offsetof(EEClass, m_NumMethods); + static constexpr size_t m_NumNonVirtualSlots = offsetof(EEClass, m_NumNonVirtualSlots); + static constexpr size_t m_dwAttrClass = offsetof(EEClass, m_dwAttrClass); }; // -------------------------------------------------------------------------------------------- diff --git a/src/coreclr/vm/methodtable.h b/src/coreclr/vm/methodtable.h index 67744c07b14c11..0dd9e0495971fa 100644 --- a/src/coreclr/vm/methodtable.h +++ b/src/coreclr/vm/methodtable.h @@ -3671,6 +3671,9 @@ template<> struct ::cdac_offsets static constexpr size_t m_pEEClassOrCanonMT = offsetof(MethodTable, m_pEEClass); static constexpr size_t m_pModule = offsetof(MethodTable, m_pModule); static constexpr size_t m_pAuxiliaryData = offsetof(MethodTable, m_pAuxiliaryData); + static constexpr size_t m_pParentMethodTable = offsetof(MethodTable, m_pParentMethodTable) + static constexpr size_t m_wNumInterfaces = offsetof(MethodTable, m_wNumInterfaces) + static constexpr size_t m_wNumVirtuals = offsetof(MethodTable, m_wNumVirtuals) }; #ifndef CROSSBITNESS_COMPILE diff --git a/src/native/managed/cdacreader/src/Contracts/Metadata.MethodTableFlags.cs b/src/native/managed/cdacreader/src/Contracts/Metadata.MethodTableFlags.cs index 7585ac82c865b3..1c9cabac699e92 100644 --- a/src/native/managed/cdacreader/src/Contracts/Metadata.MethodTableFlags.cs +++ b/src/native/managed/cdacreader/src/Contracts/Metadata.MethodTableFlags.cs @@ -141,7 +141,7 @@ internal interface IMethodTableFlags private Metadata_1.WFLAGS_HIGH FlagsHigh => (Metadata_1.WFLAGS_HIGH)DwFlags; private Metadata_1.WFLAGS_LOW FlagsLow => (Metadata_1.WFLAGS_LOW)DwFlags; - private int GetTypeDefRid() => (int)(DwFlags2 >> Metadata_1.Constants.MethodTableDwFlags2TypeDefRidShift); + public int GetTypeDefRid() => (int)(DwFlags2 >> Metadata_1.Constants.MethodTableDwFlags2TypeDefRidShift); public Metadata_1.WFLAGS_LOW GetFlag(Metadata_1.WFLAGS_LOW mask) => throw new NotImplementedException("TODO"); public Metadata_1.WFLAGS_HIGH GetFlag(Metadata_1.WFLAGS_HIGH mask) => FlagsHigh & mask; @@ -167,4 +167,8 @@ public bool TestFlagWithMask(Metadata_1.WFLAGS_LOW mask, Metadata_1.WFLAGS_LOW f } } public bool HasInstantiation => !TestFlagWithMask(Metadata_1.WFLAGS_LOW.GenericsMask, Metadata_1.WFLAGS_LOW.GenericsMask_NonGeneric); + + public bool ContainsPointers => GetFlag(Metadata_1.WFLAGS_HIGH.ContainsPointers) != 0; + + public bool IsDynamicStatics => !TestFlagWithMask(Metadata_1.WFLAGS_LOW.StaticsMask, Metadata_1.WFLAGS_LOW.StaticsMask_Dynamic); } diff --git a/src/native/managed/cdacreader/src/Contracts/Metadata.cs b/src/native/managed/cdacreader/src/Contracts/Metadata.cs index 7bc92457b3b02c..537d30d792182b 100644 --- a/src/native/managed/cdacreader/src/Contracts/Metadata.cs +++ b/src/native/managed/cdacreader/src/Contracts/Metadata.cs @@ -6,6 +6,7 @@ using MethodTable = Microsoft.Diagnostics.DataContractReader.Contracts.MethodTable_1; using UntrustedEEClass = Microsoft.Diagnostics.DataContractReader.Contracts.UntrustedEEClass_1; using EEClass = Microsoft.Diagnostics.DataContractReader.Contracts.EEClass_1; +using System.Diagnostics.SymbolStore; namespace Microsoft.Diagnostics.DataContractReader.Contracts; @@ -26,6 +27,12 @@ static IContract IContract.Create(Target target, int version) public virtual MethodTable GetMethodTableData(TargetPointer targetPointer) => throw new NotImplementedException(); public virtual TargetPointer GetClass(in MethodTable methodTable) => throw new NotImplementedException(); + + public virtual ushort GetNumMethods(in MethodTable methodTable) => throw new NotImplementedException(); + + public virtual ushort GetNumVtableSlots(in MethodTable methodTable) => throw new NotImplementedException(); + + public virtual ushort GetTypeDefTypeAttributes(in MethodTable methodTable) => throw new NotImplementedException(); } internal struct Metadata : IMetadata @@ -106,6 +113,7 @@ internal MethodTable_1(Data.MethodTable data, bool isFreeObjectMT) public uint DwFlags => MethodTableData.DwFlags; public uint DwFlags2 => MethodTableData.DwFlags2; public uint BaseSize => MethodTableData.BaseSize; + public int GetTypeDefRid() => ((IMethodTableFlags)this).GetTypeDefRid(); public TargetPointer EEClassOrCanonMT => MethodTableData.EEClassOrCanonMT; public TargetPointer Module => MethodTableData.Module; @@ -115,6 +123,12 @@ internal MethodTable_1(Data.MethodTable data, bool isFreeObjectMT) public int GetComponentSize() => ((IMethodTableFlags)this).HasComponentSize ? ((IMethodTableFlags)this).RawGetComponentSize() : 0; + public TargetPointer ParentMethodTable => MethodTableData.ParentMethodTable; + public ushort NumInterfaces => MethodTableData.NumInterfaces; + public ushort NumVirtuals => MethodTableData.NumVirtuals; + + public bool ContainsPointers => ((IMethodTableFlags)this).ContainsPointers; + public bool IsDynamicStatics => ((IMethodTableFlags)this).IsDynamicStatics; } internal struct EEClass_1 @@ -126,6 +140,10 @@ internal EEClass_1(Data.EEClass eeClassData) } public TargetPointer MethodTable => EEClassData.MethodTable; + public ushort NumMethods => EEClassData.NumMethods; + public ushort NumNonVirtualSlots => EEClassData.NumNonVirtualSlots; + + public ushort TypeDefTypeAttributes => EEClassData.DwAttrClass; } @@ -191,7 +209,7 @@ public MethodTable GetMethodTableData(TargetPointer methodTablePointer) return new MethodTable(trustedMethodTableData, isFreeObjectMT: false); } - private bool ValidateMethodTablePointer(ref readonly UntrustedMethodTable umt) + private bool ValidateMethodTablePointer(in UntrustedMethodTable umt) { // FIXME: is methodTablePointer properly sign-extended from 32-bit targets? // FIXME2: do we need this? Data.MethodTable probably would throw if methodTablePointer is invalid @@ -201,11 +219,11 @@ private bool ValidateMethodTablePointer(ref readonly UntrustedMethodTable umt) //} try { - if (!ValidateWithPossibleAV(in umt)) + if (!ValidateWithPossibleAV(umt)) { return false; } - if (!ValidateMethodTable(in umt)) + if (!ValidateMethodTable(umt)) { return false; } @@ -218,7 +236,7 @@ private bool ValidateMethodTablePointer(ref readonly UntrustedMethodTable umt) return true; } - private bool ValidateWithPossibleAV(ref readonly UntrustedMethodTable methodTable) + private bool ValidateWithPossibleAV(in UntrustedMethodTable methodTable) { // For non-generic classes, we can rely on comparing // object->methodtable->class->methodtable @@ -231,7 +249,7 @@ private bool ValidateWithPossibleAV(ref readonly UntrustedMethodTable methodTabl // object->methodtable->class->methodtable->class // to // object->methodtable->class - TargetPointer eeClassPtr = GetClassWithPossibleAV(in methodTable); + TargetPointer eeClassPtr = GetClassWithPossibleAV(methodTable); if (eeClassPtr != TargetPointer.Null) { UntrustedEEClass eeClass = GetUntrustedEEClassData(eeClassPtr); @@ -250,7 +268,7 @@ private bool ValidateWithPossibleAV(ref readonly UntrustedMethodTable methodTabl return false; } - private bool ValidateMethodTable(ref readonly UntrustedMethodTable methodTable) + private bool ValidateMethodTable(in UntrustedMethodTable methodTable) { if (!((IMethodTableFlags)methodTable).IsInterface && !((IMethodTableFlags)methodTable).IsString) { @@ -266,7 +284,7 @@ internal static EEClassOrCanonMTBits GetEEClassOrCanonMTBits(TargetPointer eeCla { return (EEClassOrCanonMTBits)(eeClassOrCanonMTPtr & (ulong)EEClassOrCanonMTBits.Mask); } - private TargetPointer GetClassWithPossibleAV(ref readonly UntrustedMethodTable methodTable) + private TargetPointer GetClassWithPossibleAV(in UntrustedMethodTable methodTable) { TargetPointer eeClassOrCanonMT = methodTable.EEClassOrCanonMT; @@ -282,12 +300,12 @@ private TargetPointer GetClassWithPossibleAV(ref readonly UntrustedMethodTable m } } - private static TargetPointer GetMethodTableWithPossibleAV(ref readonly UntrustedEEClass eeClass) + private static TargetPointer GetMethodTableWithPossibleAV(in UntrustedEEClass eeClass) { return eeClass.MethodTable; } - internal TargetPointer GetClass(ref readonly MethodTable methodTable) + public TargetPointer GetClass(in MethodTable methodTable) { switch (GetEEClassOrCanonMTBits(methodTable.EEClassOrCanonMT)) { @@ -301,4 +319,60 @@ internal TargetPointer GetClass(ref readonly MethodTable methodTable) throw new InvalidOperationException(); } } + + // only called on trusted method tables, so we always trust the resulting EEClass + private EEClass GetClassData(in MethodTable methodTable) + { + TargetPointer clsPtr = GetClass(in methodTable); + // Check if we cached it already + if (_target.ProcessedData.TryGet(clsPtr, out Data.EEClass? eeClassData)) + { + return new EEClass_1(eeClassData); + } + eeClassData = _target.ProcessedData.GetOrAdd(clsPtr); + return new EEClass_1(eeClassData); + } + + public ushort GetNumMethods(in MethodTable methodTable) + { + EEClass cls = GetClassData(in methodTable); + return cls.NumMethods; + } + + private ushort GetNumNonVirtualSlots(in MethodTable methodTable) + { + TargetPointer eeClassOrCanonMT = methodTable.EEClassOrCanonMT; + if (GetEEClassOrCanonMTBits(eeClassOrCanonMT) == EEClassOrCanonMTBits.EEClass) + { + return GetClassData(methodTable).NumNonVirtualSlots; + } + else + { + return 0; + } + } + + public ushort GetNumVtableSlots(in MethodTable methodTable) + { + return checked((ushort)(methodTable.NumVirtuals + GetNumNonVirtualSlots(methodTable))); + } + + public ushort GetTypeDefTypeAttributes(in MethodTable methodTable) + { + return GetClassData(methodTable).TypeDefTypeAttributes; + } + + + [Flags] + internal enum MethodTableAuxiliaryDataFlags : uint + { + CanCompareBitsOrUseFastGetHashCode = 0x0001, // Is any field type or sub field type overrode Equals or GetHashCode + HasCheckedCanCompareBitsOrUseFastGetHashCode = 0x0002, // Whether we have checked the overridden Equals or GetHashCode + HasApproxParent = 0x0010, + IsNotFullyLoaded = 0x0040, + DependenciesLoaded = 0x0080, // class and all dependencies loaded up to CLASS_LOADED_BUT_NOT_VERIFIED + MayHaveOpenInterfaceInInterfaceMap = 0x0100, + DebugOnly_ParentMethodTablePointerValid = 0x4000, + DebugOnly_HasInjectedInterfaceDuplicates = 0x8000, + } } diff --git a/src/native/managed/cdacreader/src/Data/EEClass.cs b/src/native/managed/cdacreader/src/Data/EEClass.cs index 5f122cc794b52d..122cf2c477f9ab 100644 --- a/src/native/managed/cdacreader/src/Data/EEClass.cs +++ b/src/native/managed/cdacreader/src/Data/EEClass.cs @@ -13,8 +13,13 @@ public EEClass(Target target, TargetPointer address) Target.TypeInfo type = target.GetTypeInfo(DataType.EEClass); MethodTable = target.ReadPointer(address + (ulong)type.Fields[nameof(MethodTable)].Offset); + NumMethods = target.Read(address + (ulong)type.Fields[nameof(NumMethods)].Offset); + NumNonVirtualSlots = target.Read(address + (ulong)type.Fields[nameof(NumNonVirtualSlots)].Offset); + DwAttrClass = target.Read(address + (ulong)type.Fields[nameof(DwAttrClass)].Offset); } public TargetPointer MethodTable { get; init; } - + public ushort NumMethods { get; init; } + public ushort NumNonVirtualSlots { get; init; } + public uint DwAttrClass { get; init; } } diff --git a/src/native/managed/cdacreader/src/Data/MethodTable.cs b/src/native/managed/cdacreader/src/Data/MethodTable.cs index 60b30839cf18ee..9fa82511cfb5e3 100644 --- a/src/native/managed/cdacreader/src/Data/MethodTable.cs +++ b/src/native/managed/cdacreader/src/Data/MethodTable.cs @@ -12,13 +12,14 @@ public MethodTable(Target target, TargetPointer address) { Target.TypeInfo type = target.GetTypeInfo(DataType.MethodTable); - //Id = target.Read(address + (ulong)type.Fields[nameof(Id)].Offset); - //LinkNext = target.ReadPointer(address + (ulong)type.Fields[nameof(LinkNext)].Offset); DwFlags = target.Read(address + (ulong)type.Fields[nameof(DwFlags)].Offset); BaseSize = target.Read(address + (ulong)type.Fields[nameof(BaseSize)].Offset); DwFlags2 = target.Read(address + (ulong)type.Fields[nameof(DwFlags2)].Offset); EEClassOrCanonMT = target.ReadPointer(address + (ulong)type.Fields[nameof(EEClassOrCanonMT)].Offset); - Module = target.ReadPointer(address + (ulong)type.Fields[(nameof(Module))].Offset); + Module = target.ReadPointer(address + (ulong)type.Fields[nameof(Module)].Offset); + ParentMethodTable = target.ReadPointer(address + (ulong)type.Fields[nameof(ParentMethodTable)].Offset); + NumInterfaces = target.Read(address + (ulong)type.Fields[nameof(NumInterfaces)].Offset); + NumVirtuals = target.Read(address + (ulong)type.Fields[nameof(NumVirtuals)].Offset); } public uint DwFlags { get; init; } @@ -26,4 +27,7 @@ public MethodTable(Target target, TargetPointer address) public uint DwFlags2 { get; init; } public TargetPointer EEClassOrCanonMT { get; init; } public TargetPointer Module { get; init; } + public TargetPointer ParentMethodTable { get; init; } + public ushort NumInterfaces { get; init; } + public ushort NumVirtuals { get; init; } } diff --git a/src/native/managed/cdacreader/src/Data/MethodTableAuxiliaryData.cs b/src/native/managed/cdacreader/src/Data/MethodTableAuxiliaryData.cs new file mode 100644 index 00000000000000..aaba612d12e107 --- /dev/null +++ b/src/native/managed/cdacreader/src/Data/MethodTableAuxiliaryData.cs @@ -0,0 +1,21 @@ +// 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.Data; + +internal sealed class MethodTableAuxiliaryData : IData +{ + static MethodTableAuxiliaryData IData.Create(Target target, TargetPointer address) => new MethodTableAuxiliaryData(target, address); + + private MethodTableAuxiliaryData(Target target, TargetPointer address) + { + Target.TypeInfo type = target.GetTypeInfo(DataType.MethodTableAuxiliaryData); + + DwFlags = target.Read(address + (ulong)type.Fields[nameof(DwFlags)].Offset); + + } + + public uint DwFlags { get; init; } +} diff --git a/src/native/managed/cdacreader/src/DataType.cs b/src/native/managed/cdacreader/src/DataType.cs index 58e881dad5c737..842055806cfc3c 100644 --- a/src/native/managed/cdacreader/src/DataType.cs +++ b/src/native/managed/cdacreader/src/DataType.cs @@ -24,4 +24,5 @@ public enum DataType ThreadStore, MethodTable, EEClass, + MethodTableAuxiliaryData, } diff --git a/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs b/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs index 18564b49486537..2d202aeb36dd58 100644 --- a/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs +++ b/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; +using System.Reflection.Metadata.Ecma335; using System.Runtime.InteropServices; using System.Runtime.InteropServices.Marshalling; using Microsoft.Diagnostics.DataContractReader.Data; @@ -100,21 +101,17 @@ public unsafe int GetMethodTableData(ulong mt, DacpMethodTableData* data) if (!methodTable.IsFreeObjectMethodTable) { result.module = methodTable.Module; - result.@class = contract.GetClass(in methodTable); -#if false - // TODO - MTData->ParentMethodTable = HOST_CDADDR(pMT->GetParentMethodTable()); ; - MTData->wNumInterfaces = (WORD)pMT->GetNumInterfaces(); - MTData->wNumMethods = pMT->GetNumMethods(); - MTData->wNumVtableSlots = pMT->GetNumVtableSlots(); - MTData->wNumVirtuals = pMT->GetNumVirtuals(); - MTData->cl = pMT->GetCl(); - MTData->dwAttrClass = pMT->GetAttrClass(); - MTData->bContainsPointers = pMT->ContainsPointers(); - MTData->bIsShared = FALSE; - MTData->bIsDynamic = pMT->IsDynamicStatics(); -#endif - return HResults.E_NOTIMPL; + result.@class = contract.GetClass(methodTable); + result.parentMethodTable = methodTable.ParentMethodTable; + result.wNumInterfaces = methodTable.NumInterfaces; + result.wNumMethods = contract.GetNumMethods(methodTable); + result.wNumVtableSlots = contract.GetNumVtableSlots(methodTable); + result.wNumVirtuals = methodTable.NumVirtuals; + result.cl = (uint)(methodTable.GetTypeDefRid() | ((int)TableIndex.TypeDef << 24)); + result.dwAttrClass = contract.GetTypeDefTypeAttributes(methodTable); + result.bContainsPointers = methodTable.ContainsPointers ? 1 : 0; + result.bIsShared = 0; + result.bIsDynamic = methodTable.IsDynamicStatics ? 1 : 0; } return HResults.S_OK; } From 30b7b26d66ba1a81e73de71cec17da5d27bbd1f4 Mon Sep 17 00:00:00 2001 From: Aleksey Kliger Date: Thu, 13 Jun 2024 15:57:32 -0400 Subject: [PATCH 16/62] checkpoint: same answer as legacy dac --- src/coreclr/debug/daccess/request.cpp | 17 +++++++- src/coreclr/vm/methodtable.h | 6 +-- .../cdacreader/src/Contracts/Metadata.cs | 40 +++++++++++-------- .../cdacreader/src/Legacy/SOSDacImpl.cs | 10 ++--- 4 files changed, 46 insertions(+), 27 deletions(-) diff --git a/src/coreclr/debug/daccess/request.cpp b/src/coreclr/debug/daccess/request.cpp index 4c094ec6a3440d..01fe0c66030556 100644 --- a/src/coreclr/debug/daccess/request.cpp +++ b/src/coreclr/debug/daccess/request.cpp @@ -1784,8 +1784,21 @@ ClrDataAccess::GetMethodTableData(CLRDATA_ADDRESS mt, struct DacpMethodTableData DacpMethodTableData mtDataLocal; HRESULT hrLocal = GetMethodTableDataImpl(mt, &mtDataLocal); _ASSERTE(hr == hrLocal); - //_ASSERTE(threadStoreData->threadCount == threadStoreDataLocal.threadCount); - // TODO(cdac) + _ASSERTE(MTData->BaseSize == mtDataLocal.BaseSize); + _ASSERTE(MTData->ComponentSize == mtDataLocal.ComponentSize); + _ASSERTE(MTData->bIsFree == mtDataLocal.bIsFree); + _ASSERTE(MTData->Module == mtDataLocal.Module); + _ASSERTE(MTData->Class == mtDataLocal.Class); + _ASSERTE(MTData->ParentMethodTable == mtDataLocal.ParentMethodTable); + _ASSERTE(MTData->wNumInterfaces == mtDataLocal.wNumInterfaces); + _ASSERTE(MTData->wNumMethods == mtDataLocal.wNumMethods); + _ASSERTE(MTData->wNumVtableSlots == mtDataLocal.wNumVtableSlots); + _ASSERTE(MTData->wNumVirtuals == mtDataLocal.wNumVirtuals); + _ASSERTE(MTData->cl == mtDataLocal.cl); + _ASSERTE(MTData->dwAttrClass = mtDataLocal.dwAttrClass); + _ASSERTE(MTData->bContainsPointers == mtDataLocal.bContainsPointers); + _ASSERTE(MTData->bIsShared == mtDataLocal.bIsShared); + _ASSERTE(MTData->bIsDynamic == mtDataLocal.bIsDynamic); } #endif } diff --git a/src/coreclr/vm/methodtable.h b/src/coreclr/vm/methodtable.h index 0dd9e0495971fa..f00fffd7f0bf5b 100644 --- a/src/coreclr/vm/methodtable.h +++ b/src/coreclr/vm/methodtable.h @@ -3671,9 +3671,9 @@ template<> struct ::cdac_offsets static constexpr size_t m_pEEClassOrCanonMT = offsetof(MethodTable, m_pEEClass); static constexpr size_t m_pModule = offsetof(MethodTable, m_pModule); static constexpr size_t m_pAuxiliaryData = offsetof(MethodTable, m_pAuxiliaryData); - static constexpr size_t m_pParentMethodTable = offsetof(MethodTable, m_pParentMethodTable) - static constexpr size_t m_wNumInterfaces = offsetof(MethodTable, m_wNumInterfaces) - static constexpr size_t m_wNumVirtuals = offsetof(MethodTable, m_wNumVirtuals) + static constexpr size_t m_pParentMethodTable = offsetof(MethodTable, m_pParentMethodTable); + static constexpr size_t m_wNumInterfaces = offsetof(MethodTable, m_wNumInterfaces); + static constexpr size_t m_wNumVirtuals = offsetof(MethodTable, m_wNumVirtuals); }; #ifndef CROSSBITNESS_COMPILE diff --git a/src/native/managed/cdacreader/src/Contracts/Metadata.cs b/src/native/managed/cdacreader/src/Contracts/Metadata.cs index 537d30d792182b..0ef4ddd0d4a295 100644 --- a/src/native/managed/cdacreader/src/Contracts/Metadata.cs +++ b/src/native/managed/cdacreader/src/Contracts/Metadata.cs @@ -2,11 +2,11 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; +using System.Reflection.Metadata.Ecma335; using UntrustedMethodTable = Microsoft.Diagnostics.DataContractReader.Contracts.UntrustedMethodTable_1; using MethodTable = Microsoft.Diagnostics.DataContractReader.Contracts.MethodTable_1; using UntrustedEEClass = Microsoft.Diagnostics.DataContractReader.Contracts.UntrustedEEClass_1; using EEClass = Microsoft.Diagnostics.DataContractReader.Contracts.EEClass_1; -using System.Diagnostics.SymbolStore; namespace Microsoft.Diagnostics.DataContractReader.Contracts; @@ -28,11 +28,15 @@ static IContract IContract.Create(Target target, int version) public virtual TargetPointer GetClass(in MethodTable methodTable) => throw new NotImplementedException(); + public virtual bool IsString(in MethodTable methodTable) => throw new NotImplementedException(); + public virtual bool ContainsPointers(in MethodTable methodTable) => throw new NotImplementedException(); + public virtual bool IsDynamicStatics(in MethodTable methodTable) => throw new NotImplementedException(); + public virtual uint GetTypeDefToken(in MethodTable methodTable) => throw new NotImplementedException(); public virtual ushort GetNumMethods(in MethodTable methodTable) => throw new NotImplementedException(); public virtual ushort GetNumVtableSlots(in MethodTable methodTable) => throw new NotImplementedException(); - public virtual ushort GetTypeDefTypeAttributes(in MethodTable methodTable) => throw new NotImplementedException(); + public virtual uint GetTypeDefTypeAttributes(in MethodTable methodTable) => throw new NotImplementedException(); } internal struct Metadata : IMetadata @@ -64,9 +68,9 @@ internal UntrustedMethodTable_1(Target target, TargetPointer methodTablePointer) public uint DwFlags2 => _target.Read(Address + (ulong)_type.Fields[nameof(DwFlags)].Offset); public uint BaseSize => _target.Read(Address + (ulong)_type.Fields[nameof(BaseSize)].Offset); - public TargetPointer EEClassOrCanonMT => _target.ReadPointer(Address + (ulong)_type.Fields[nameof(EEClassOrCanonMT)].Offset); - public TargetPointer EEClass => Metadata_1.GetEEClassOrCanonMTBits(EEClassOrCanonMT) == Metadata_1.EEClassOrCanonMTBits.EEClass ? EEClassOrCanonMT : throw new InvalidOperationException("not an EEClass"); - public TargetPointer CanonMT + internal TargetPointer EEClassOrCanonMT => _target.ReadPointer(Address + (ulong)_type.Fields[nameof(EEClassOrCanonMT)].Offset); + internal TargetPointer EEClass => Metadata_1.GetEEClassOrCanonMTBits(EEClassOrCanonMT) == Metadata_1.EEClassOrCanonMTBits.EEClass ? EEClassOrCanonMT : throw new InvalidOperationException("not an EEClass"); + internal TargetPointer CanonMT { get { @@ -102,8 +106,8 @@ internal UntrustedEEClass_1(Target target, TargetPointer eeClassPointer) internal struct MethodTable_1 : IMethodTableFlags { - public Data.MethodTable MethodTableData { get; init; } - public bool IsFreeObjectMethodTable { get; init; } + private Data.MethodTable MethodTableData { get; init; } + internal bool IsFreeObjectMethodTable { get; init; } internal MethodTable_1(Data.MethodTable data, bool isFreeObjectMT) { MethodTableData = data; @@ -113,13 +117,11 @@ internal MethodTable_1(Data.MethodTable data, bool isFreeObjectMT) public uint DwFlags => MethodTableData.DwFlags; public uint DwFlags2 => MethodTableData.DwFlags2; public uint BaseSize => MethodTableData.BaseSize; - public int GetTypeDefRid() => ((IMethodTableFlags)this).GetTypeDefRid(); - public TargetPointer EEClassOrCanonMT => MethodTableData.EEClassOrCanonMT; - public TargetPointer Module => MethodTableData.Module; + internal TargetPointer EEClassOrCanonMT => MethodTableData.EEClassOrCanonMT; + internal TargetPointer Module => MethodTableData.Module; public TargetPointer EEClass => Metadata_1.GetEEClassOrCanonMTBits(EEClassOrCanonMT) == Metadata_1.EEClassOrCanonMTBits.EEClass ? EEClassOrCanonMT : throw new InvalidOperationException("not an EEClass"); - public bool IsString => ((IMethodTableFlags)this).IsString; public int GetComponentSize() => ((IMethodTableFlags)this).HasComponentSize ? ((IMethodTableFlags)this).RawGetComponentSize() : 0; @@ -127,8 +129,6 @@ internal MethodTable_1(Data.MethodTable data, bool isFreeObjectMT) public ushort NumInterfaces => MethodTableData.NumInterfaces; public ushort NumVirtuals => MethodTableData.NumVirtuals; - public bool ContainsPointers => ((IMethodTableFlags)this).ContainsPointers; - public bool IsDynamicStatics => ((IMethodTableFlags)this).IsDynamicStatics; } internal struct EEClass_1 @@ -143,7 +143,7 @@ internal EEClass_1(Data.EEClass eeClassData) public ushort NumMethods => EEClassData.NumMethods; public ushort NumNonVirtualSlots => EEClassData.NumNonVirtualSlots; - public ushort TypeDefTypeAttributes => EEClassData.DwAttrClass; + public uint TypeDefTypeAttributes => EEClassData.DwAttrClass; } @@ -333,6 +333,15 @@ private EEClass GetClassData(in MethodTable methodTable) return new EEClass_1(eeClassData); } + public bool IsString(in MethodTable methodTable) => ((IMethodTableFlags)methodTable).IsString; + public bool ContainsPointers(in MethodTable methodTable) => ((IMethodTableFlags)methodTable).ContainsPointers; + public bool IsDynamicStatics(in MethodTable methodTable) => ((IMethodTableFlags)methodTable).IsDynamicStatics; + + public uint GetTypeDefToken(in MethodTable methodTable) + { + return (uint)(((IMethodTableFlags)methodTable).GetTypeDefRid() | ((int)TableIndex.TypeDef << 24)); + } + public ushort GetNumMethods(in MethodTable methodTable) { EEClass cls = GetClassData(in methodTable); @@ -357,12 +366,11 @@ public ushort GetNumVtableSlots(in MethodTable methodTable) return checked((ushort)(methodTable.NumVirtuals + GetNumNonVirtualSlots(methodTable))); } - public ushort GetTypeDefTypeAttributes(in MethodTable methodTable) + public uint GetTypeDefTypeAttributes(in MethodTable methodTable) { return GetClassData(methodTable).TypeDefTypeAttributes; } - [Flags] internal enum MethodTableAuxiliaryDataFlags : uint { diff --git a/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs b/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs index 2d202aeb36dd58..136bd49fda720f 100644 --- a/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs +++ b/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs @@ -2,10 +2,8 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; -using System.Reflection.Metadata.Ecma335; using System.Runtime.InteropServices; using System.Runtime.InteropServices.Marshalling; -using Microsoft.Diagnostics.DataContractReader.Data; namespace Microsoft.Diagnostics.DataContractReader.Legacy; @@ -94,7 +92,7 @@ public unsafe int GetMethodTableData(ulong mt, DacpMethodTableData* data) DacpMethodTableData result = default; result.baseSize = methodTable.BaseSize; - if (methodTable.IsString) + if (contract.IsString(methodTable)) result.baseSize -= 2 /*sizeof(WCHAR) */; result.componentSize = checked((uint)methodTable.GetComponentSize()); result.bIsFree = methodTable.IsFreeObjectMethodTable ? 1 : 0; @@ -107,11 +105,11 @@ public unsafe int GetMethodTableData(ulong mt, DacpMethodTableData* data) result.wNumMethods = contract.GetNumMethods(methodTable); result.wNumVtableSlots = contract.GetNumVtableSlots(methodTable); result.wNumVirtuals = methodTable.NumVirtuals; - result.cl = (uint)(methodTable.GetTypeDefRid() | ((int)TableIndex.TypeDef << 24)); + result.cl = contract.GetTypeDefToken(methodTable); result.dwAttrClass = contract.GetTypeDefTypeAttributes(methodTable); - result.bContainsPointers = methodTable.ContainsPointers ? 1 : 0; + result.bContainsPointers = contract.ContainsPointers(methodTable) ? 1 : 0; result.bIsShared = 0; - result.bIsDynamic = methodTable.IsDynamicStatics ? 1 : 0; + result.bIsDynamic = contract.IsDynamicStatics(methodTable) ? 1 : 0; } return HResults.S_OK; } From f0d1fcbe17851334c524f0995d426c17aae325e0 Mon Sep 17 00:00:00 2001 From: Aleksey Kliger Date: Fri, 14 Jun 2024 13:14:39 -0400 Subject: [PATCH 17/62] new flags for statics --- src/coreclr/vm/methodtable.h | 2 +- .../Contracts/Metadata.MethodTableFlags.cs | 84 +++++++++++-------- .../cdacreader/src/Contracts/Metadata.cs | 22 +++-- 3 files changed, 68 insertions(+), 40 deletions(-) diff --git a/src/coreclr/vm/methodtable.h b/src/coreclr/vm/methodtable.h index 1a1477a7252496..967a7109bd1ef2 100644 --- a/src/coreclr/vm/methodtable.h +++ b/src/coreclr/vm/methodtable.h @@ -582,7 +582,7 @@ struct DynamicStaticsInfo // If it has, then we don't need to do anything return false; } - + oldValFromInterlockedOp = InterlockedCompareExchangeT(pAddr, newVal | oldVal, oldVal); } while(oldValFromInterlockedOp != oldVal); return true; diff --git a/src/native/managed/cdacreader/src/Contracts/Metadata.MethodTableFlags.cs b/src/native/managed/cdacreader/src/Contracts/Metadata.MethodTableFlags.cs index 1c9cabac699e92..fd3a44b75f6311 100644 --- a/src/native/managed/cdacreader/src/Contracts/Metadata.MethodTableFlags.cs +++ b/src/native/managed/cdacreader/src/Contracts/Metadata.MethodTableFlags.cs @@ -22,12 +22,6 @@ internal enum WFLAGS_LOW : uint UNUSED_ComponentSize_1 = 0x00000001, // GC depends on this bit HasCriticalFinalizer = 0x00000002, // finalizer must be run on Appdomain Unload - StaticsMask = 0x0000000C, - StaticsMask_NonDynamic = 0x00000000, - StaticsMask_Dynamic = 0x00000008, // dynamic statics (EnC, reflection.emit) - StaticsMask_Generics = 0x00000004, // generics statics - StaticsMask_CrossModuleGenerics = 0x0000000C, // cross module generics statics (NGen) - StaticsMask_IfGenericsThenCrossModule = 0x00000008, // helper constant to get rid of unnecessary check GenericsMask = 0x00000030, @@ -41,10 +35,10 @@ internal enum WFLAGS_LOW : uint HasDefaultCtor = 0x00000200, HasPreciseInitCctors = 0x00000400, // Do we need to run class constructors at allocation time? (Not perf important, could be moved to EEClass - // if FEATURE_HFA + // if defined(FEATURE_HFA) IsHFA = 0x00000800, // This type is an HFA (Homogeneous Floating-point Aggregate) - // if UNIX_AMD64_ABI + // if defined(UNIX_AMD64_ABI) IsRegStructPassed = 0x00000800, // This type is a System V register passed struct. IsByRefLike = 0x00001000, @@ -61,20 +55,27 @@ internal enum WFLAGS_LOW : uint // As you change the flags in WFLAGS_LOW_ENUM you also need to change this // to be up to date to reflect the default values of those flags for the // case where this MethodTable is for a String or Array - StringArrayValues = //SET_FALSE(enum_flag_HasCriticalFinalizer) | - StaticsMask_NonDynamic | - //SET_FALSE(enum_flag_HasBoxedRegularStatics) | - //SET_FALSE(enum_flag_HasBoxedThreadStatics) | + StringArrayValues = // SET_FALSE(enum_flag_HasCriticalFinalizer) | + // SET_FALSE(enum_flag_HasBoxedRegularStatics) | + // SET_FALSE(enum_flag_HasBoxedThreadStatics) | GenericsMask_NonGeneric | - //SET_FALSE(enum_flag_HasVariance) | - //SET_FALSE(enum_flag_HasDefaultCtor) | - //SET_FALSE(enum_flag_HasPreciseInitCctors) + // SET_FALSE(enum_flag_HasVariance) | + // SET_FALSE(enum_flag_HasDefaultCtor) | + // SET_FALSE(enum_flag_HasPreciseInitCctors), 0, } [Flags] internal enum WFLAGS_HIGH : uint { + // DO NOT use flags that have bits set in the low 2 bytes. + // These flags are DWORD sized so that our atomic masking + // operations can operate on the entire 4-byte aligned DWORD + // instead of the logical non-aligned WORD of flags. The + // low WORD of flags is reserved for the component size. + + // The following bits describe mutually exclusive locations of the type + // in the type hierarchy. Category_Mask = 0x000F0000, Category_Class = 0x00000000, @@ -90,7 +91,7 @@ internal enum WFLAGS_HIGH : uint Category_Array = 0x00080000, Category_Array_Mask = 0x000C0000, - // Category_IfArrayThenUnused = 0x00010000, // sub-category of Array + // enum_flag_Category_IfArrayThenUnused = 0x00010000, // sub-category of Array Category_IfArrayThenSzArray = 0x00020000, // sub-category of Array Category_Interface = 0x000C0000, @@ -100,29 +101,23 @@ internal enum WFLAGS_HIGH : uint Category_ElementTypeMask = 0x000E0000, // bits that matter for element type mask - // GC depends on this bit - HasFinalizer = 0x00100000, // instances require finalization - - IDynamicInterfaceCastable = 0x10000000, // class implements IDynamicInterfaceCastable interface - + HasFinalizer = 0x00100000, // instances require finalization. GC depends on this bit. + Collectible = 0x00200000, // GC depends on this bit. ICastable = 0x00400000, // class implements ICastable interface - RequiresAlign8 = 0x00800000, // Type requires 8-byte alignment (only set on platforms that require this and don't get it implicitly) - - ContainsPointers = 0x01000000, + // ifdef FEATURE_64BIT_ALIGNMENT + eRequiresAlign8 = 0x00800000, // Type requires 8-byte alignment (only set on platforms that require this and don't get it implicitly) + ContainsPointers = 0x01000000, // Contains object references HasTypeEquivalence = 0x02000000, // can be equivalent to another type - IsTrackedReferenceWithFinalizer = 0x04000000, + // unused = 0x08000000, - // GC depends on this bit - Collectible = 0x00200000, - ContainsGenericVariables = 0x20000000, // we cache this flag to help detect these efficiently and - // to detect this condition when restoring - + IDynamicInterfaceCastable = 0x10000000, // class implements IDynamicInterfaceCastable interface + ContainsGenericVariables = 0x20000000, // we cache this flag to help detect these efficiently and + // to detect this condition when restoring ComObject = 0x40000000, // class is a com object - - HasComponentSize = 0x80000000, // This is set if component size is used for flags. + HasComponentSize = 0x80000000, // This is set if component size is used for flags. // Types that require non-trivial interface cast have this bit set in the category NonTrivialInterfaceCast = Category_Array @@ -130,7 +125,22 @@ internal enum WFLAGS_HIGH : uint | ICastable | IDynamicInterfaceCastable | Category_ValueType + } + [Flags] + internal enum WFLAGS2_ENUM : uint + { + HasPerInstInfo = 0x0001, + DynamicStatics = 0x0002, + HasDispatchMapSlot = 0x0004, + + // wflags2_unused_2 = 0x0008, + HasModuleDependencies = 0x0010, + IsIntrinsicType = 0x0020, + HasCctor = 0x0040, + HasVirtualStaticMethods = 0x0080, + + TokenMask = 0xFFFFFF00, } } internal interface IMethodTableFlags @@ -145,6 +155,8 @@ internal interface IMethodTableFlags public Metadata_1.WFLAGS_LOW GetFlag(Metadata_1.WFLAGS_LOW mask) => throw new NotImplementedException("TODO"); public Metadata_1.WFLAGS_HIGH GetFlag(Metadata_1.WFLAGS_HIGH mask) => FlagsHigh & mask; + + public Metadata_1.WFLAGS2_ENUM GetFlag(Metadata_1.WFLAGS2_ENUM mask) => (Metadata_1.WFLAGS2_ENUM)DwFlags2 & mask; public bool IsInterface => GetFlag(Metadata_1.WFLAGS_HIGH.Category_Mask) == Metadata_1.WFLAGS_HIGH.Category_Interface; public bool IsString => HasComponentSize && !IsArray && RawGetComponentSize() == 2; @@ -166,9 +178,13 @@ public bool TestFlagWithMask(Metadata_1.WFLAGS_LOW mask, Metadata_1.WFLAGS_LOW f return (FlagsLow & mask) == flag; } } + + public bool TestFlagWithMask(Metadata_1.WFLAGS2_ENUM mask, Metadata_1.WFLAGS2_ENUM flag) + { + return ((Metadata_1.WFLAGS2_ENUM)DwFlags2 & mask) == flag; + } + public bool HasInstantiation => !TestFlagWithMask(Metadata_1.WFLAGS_LOW.GenericsMask, Metadata_1.WFLAGS_LOW.GenericsMask_NonGeneric); public bool ContainsPointers => GetFlag(Metadata_1.WFLAGS_HIGH.ContainsPointers) != 0; - - public bool IsDynamicStatics => !TestFlagWithMask(Metadata_1.WFLAGS_LOW.StaticsMask, Metadata_1.WFLAGS_LOW.StaticsMask_Dynamic); } diff --git a/src/native/managed/cdacreader/src/Contracts/Metadata.cs b/src/native/managed/cdacreader/src/Contracts/Metadata.cs index 0ef4ddd0d4a295..176940120f4d28 100644 --- a/src/native/managed/cdacreader/src/Contracts/Metadata.cs +++ b/src/native/managed/cdacreader/src/Contracts/Metadata.cs @@ -335,7 +335,6 @@ private EEClass GetClassData(in MethodTable methodTable) public bool IsString(in MethodTable methodTable) => ((IMethodTableFlags)methodTable).IsString; public bool ContainsPointers(in MethodTable methodTable) => ((IMethodTableFlags)methodTable).ContainsPointers; - public bool IsDynamicStatics(in MethodTable methodTable) => ((IMethodTableFlags)methodTable).IsDynamicStatics; public uint GetTypeDefToken(in MethodTable methodTable) { @@ -371,16 +370,29 @@ public uint GetTypeDefTypeAttributes(in MethodTable methodTable) return GetClassData(methodTable).TypeDefTypeAttributes; } + public bool IsDynamicStatics(in MethodTable methodTable) => ((IMethodTableFlags)methodTable).GetFlag(WFLAGS2_ENUM.DynamicStatics) != 0; + [Flags] internal enum MethodTableAuxiliaryDataFlags : uint { - CanCompareBitsOrUseFastGetHashCode = 0x0001, // Is any field type or sub field type overrode Equals or GetHashCode + Initialized = 0x0001, HasCheckedCanCompareBitsOrUseFastGetHashCode = 0x0002, // Whether we have checked the overridden Equals or GetHashCode + CanCompareBitsOrUseFastGetHashCode = 0x0004, // Is any field type or sub field type overridden Equals or GetHashCode + HasApproxParent = 0x0010, + // enum_unused = 0x0020, IsNotFullyLoaded = 0x0040, DependenciesLoaded = 0x0080, // class and all dependencies loaded up to CLASS_LOADED_BUT_NOT_VERIFIED - MayHaveOpenInterfaceInInterfaceMap = 0x0100, - DebugOnly_ParentMethodTablePointerValid = 0x4000, - DebugOnly_HasInjectedInterfaceDuplicates = 0x8000, + + IsInitError = 0x0100, + IsStaticDataAllocated = 0x0200, + // unum_unused = 0x0400, + IsTlsIndexAllocated = 0x0800, + MayHaveOpenInterfaceInInterfaceMap = 0x1000, + // enum_unused = 0x2000, + + // ifdef _DEBUG + DEBUG_ParentMethodTablePointerValid = 0x4000, + DEBUG_HasInjectedInterfaceDuplicates = 0x8000, } } From c53db3695d36b0e820af15064362cdf7857af50f Mon Sep 17 00:00:00 2001 From: Aleksey Kliger Date: Fri, 14 Jun 2024 15:05:44 -0400 Subject: [PATCH 18/62] fix GCC build --- src/coreclr/vm/class.h | 2 +- src/coreclr/vm/methodtable.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/coreclr/vm/class.h b/src/coreclr/vm/class.h index 870536682aba76..b2e722cd4a0ef1 100644 --- a/src/coreclr/vm/class.h +++ b/src/coreclr/vm/class.h @@ -1801,7 +1801,7 @@ class EEClass // DO NOT CREATE A NEW EEClass USING NEW! template friend struct ::cdac_offsets; }; -template<> struct ::cdac_offsets +template<> struct cdac_offsets { static constexpr size_t m_pMethodTable = offsetof(EEClass, m_pMethodTable); static constexpr size_t m_NumMethods = offsetof(EEClass, m_NumMethods); diff --git a/src/coreclr/vm/methodtable.h b/src/coreclr/vm/methodtable.h index 967a7109bd1ef2..865284d43e536b 100644 --- a/src/coreclr/vm/methodtable.h +++ b/src/coreclr/vm/methodtable.h @@ -3862,7 +3862,7 @@ public : template friend struct ::cdac_offsets; }; // class MethodTable -template<> struct ::cdac_offsets +template<> struct cdac_offsets { static constexpr size_t m_dwFlags = offsetof(MethodTable, m_dwFlags); static constexpr size_t m_BaseSize = offsetof(MethodTable, m_BaseSize); From b70bb1d0d6f93a53f8ad4000b12f732f8756fc05 Mon Sep 17 00:00:00 2001 From: Aleksey Kliger Date: Mon, 17 Jun 2024 16:14:58 -0400 Subject: [PATCH 19/62] WIP: opaque MethodTableHandle --- .../cdacreader/src/Contracts/Metadata.cs | 390 ++--------------- ...ableFlags.cs => Metadata_1.MethodFlags.cs} | 0 .../cdacreader/src/Contracts/Metadata_1.cs | 391 ++++++++++++++++++ .../cdacreader/src/Legacy/SOSDacImpl.cs | 22 +- 4 files changed, 428 insertions(+), 375 deletions(-) rename src/native/managed/cdacreader/src/Contracts/{Metadata.MethodTableFlags.cs => Metadata_1.MethodFlags.cs} (100%) create mode 100644 src/native/managed/cdacreader/src/Contracts/Metadata_1.cs diff --git a/src/native/managed/cdacreader/src/Contracts/Metadata.cs b/src/native/managed/cdacreader/src/Contracts/Metadata.cs index 176940120f4d28..d8f9128382d0f5 100644 --- a/src/native/managed/cdacreader/src/Contracts/Metadata.cs +++ b/src/native/managed/cdacreader/src/Contracts/Metadata.cs @@ -3,13 +3,20 @@ using System; using System.Reflection.Metadata.Ecma335; -using UntrustedMethodTable = Microsoft.Diagnostics.DataContractReader.Contracts.UntrustedMethodTable_1; -using MethodTable = Microsoft.Diagnostics.DataContractReader.Contracts.MethodTable_1; -using UntrustedEEClass = Microsoft.Diagnostics.DataContractReader.Contracts.UntrustedEEClass_1; -using EEClass = Microsoft.Diagnostics.DataContractReader.Contracts.EEClass_1; namespace Microsoft.Diagnostics.DataContractReader.Contracts; +// an opaque handle to a method table. See IMetadata.GetMethodTableData +internal readonly struct MethodTableHandle +{ + internal MethodTableHandle(TargetPointer address) + { + Address = address; + } + + internal TargetPointer Address { get; } +} + internal interface IMetadata : IContract { static string IContract.Name => nameof(Metadata); @@ -24,375 +31,26 @@ static IContract IContract.Create(Target target, int version) }; } - public virtual MethodTable GetMethodTableData(TargetPointer targetPointer) => throw new NotImplementedException(); + public virtual MethodTableHandle GetMethodTableData(TargetPointer targetPointer) => throw new NotImplementedException(); + + public virtual TargetPointer GetClass(MethodTableHandle methodTable) => throw new NotImplementedException(); - public virtual TargetPointer GetClass(in MethodTable methodTable) => throw new NotImplementedException(); + public virtual uint GetBaseSize(MethodTableHandle methodTable) => throw new NotImplementedException(); + public virtual uint GetComponentSize(MethodTableHandle methodTable) => throw new NotImplementedException(); - public virtual bool IsString(in MethodTable methodTable) => throw new NotImplementedException(); - public virtual bool ContainsPointers(in MethodTable methodTable) => throw new NotImplementedException(); - public virtual bool IsDynamicStatics(in MethodTable methodTable) => throw new NotImplementedException(); - public virtual uint GetTypeDefToken(in MethodTable methodTable) => throw new NotImplementedException(); - public virtual ushort GetNumMethods(in MethodTable methodTable) => throw new NotImplementedException(); + public virtual bool IsFreeObjectMethodTable(MethodTableHandle methodTable) => throw new NotImplementedException(); + public virtual bool IsString(MethodTableHandle methodTable) => throw new NotImplementedException(); + public virtual bool ContainsPointers(MethodTableHandle methodTable) => throw new NotImplementedException(); + public virtual bool IsDynamicStatics(MethodTableHandle methodTable) => throw new NotImplementedException(); + public virtual uint GetTypeDefToken(MethodTableHandle methodTable) => throw new NotImplementedException(); + public virtual ushort GetNumMethods(MethodTableHandle methodTable) => throw new NotImplementedException(); - public virtual ushort GetNumVtableSlots(in MethodTable methodTable) => throw new NotImplementedException(); + public virtual ushort GetNumVtableSlots(MethodTableHandle methodTable) => throw new NotImplementedException(); - public virtual uint GetTypeDefTypeAttributes(in MethodTable methodTable) => throw new NotImplementedException(); + public virtual uint GetTypeDefTypeAttributes(MethodTableHandle methodTable) => throw new NotImplementedException(); } internal struct Metadata : IMetadata { // Everything throws NotImplementedException } - -// GC Heap corruption may create situations where a putative pointer to a MethodTable -// may point to garbage. So this struct represents a MethodTable that we don't necessarily -// trust to be valid. -// see Metadata_1.ValidateMethodTablePointer -// This doesn't need as many properties as MethodTable because we dont' want to be operating on -// an UntrustedMethodTable for too long -internal struct UntrustedMethodTable_1 : IMethodTableFlags -{ - private readonly Target _target; - private readonly Target.TypeInfo _type; - public TargetPointer Address { get; init; } - - internal UntrustedMethodTable_1(Target target, TargetPointer methodTablePointer) - { - _target = target; - _type = target.GetTypeInfo(DataType.MethodTable); - Address = methodTablePointer; - } - - // all these accessors might throw if MethodTablePointer is invalid - public uint DwFlags => _target.Read(Address + (ulong)_type.Fields[nameof(DwFlags2)].Offset); - public uint DwFlags2 => _target.Read(Address + (ulong)_type.Fields[nameof(DwFlags)].Offset); - public uint BaseSize => _target.Read(Address + (ulong)_type.Fields[nameof(BaseSize)].Offset); - - internal TargetPointer EEClassOrCanonMT => _target.ReadPointer(Address + (ulong)_type.Fields[nameof(EEClassOrCanonMT)].Offset); - internal TargetPointer EEClass => Metadata_1.GetEEClassOrCanonMTBits(EEClassOrCanonMT) == Metadata_1.EEClassOrCanonMTBits.EEClass ? EEClassOrCanonMT : throw new InvalidOperationException("not an EEClass"); - internal TargetPointer CanonMT - { - get - { - if (Metadata_1.GetEEClassOrCanonMTBits(EEClassOrCanonMT) == Metadata_1.EEClassOrCanonMTBits.CanonMT) - { - return new TargetPointer((ulong)EEClassOrCanonMT & ~(ulong)Metadata_1.EEClassOrCanonMTBits.Mask); - } - else - { - throw new InvalidOperationException("not a canonical method table"); - } - } - } -} - -internal struct UntrustedEEClass_1 -{ - public readonly Target _target; - private readonly Target.TypeInfo _type; - - public TargetPointer Address { get; init; } - - internal UntrustedEEClass_1(Target target, TargetPointer eeClassPointer) - { - _target = target; - Address = eeClassPointer; - _type = target.GetTypeInfo(DataType.EEClass); - } - - public TargetPointer MethodTable => _target.ReadPointer(Address + (ulong)_type.Fields[nameof(MethodTable)].Offset); -} - - -internal struct MethodTable_1 : IMethodTableFlags -{ - private Data.MethodTable MethodTableData { get; init; } - internal bool IsFreeObjectMethodTable { get; init; } - internal MethodTable_1(Data.MethodTable data, bool isFreeObjectMT) - { - MethodTableData = data; - IsFreeObjectMethodTable = isFreeObjectMT; - } - - public uint DwFlags => MethodTableData.DwFlags; - public uint DwFlags2 => MethodTableData.DwFlags2; - public uint BaseSize => MethodTableData.BaseSize; - internal TargetPointer EEClassOrCanonMT => MethodTableData.EEClassOrCanonMT; - internal TargetPointer Module => MethodTableData.Module; - - public TargetPointer EEClass => Metadata_1.GetEEClassOrCanonMTBits(EEClassOrCanonMT) == Metadata_1.EEClassOrCanonMTBits.EEClass ? EEClassOrCanonMT : throw new InvalidOperationException("not an EEClass"); - - - public int GetComponentSize() => ((IMethodTableFlags)this).HasComponentSize ? ((IMethodTableFlags)this).RawGetComponentSize() : 0; - - public TargetPointer ParentMethodTable => MethodTableData.ParentMethodTable; - public ushort NumInterfaces => MethodTableData.NumInterfaces; - public ushort NumVirtuals => MethodTableData.NumVirtuals; - -} - -internal struct EEClass_1 -{ - public Data.EEClass EEClassData { get; init; } - internal EEClass_1(Data.EEClass eeClassData) - { - EEClassData = eeClassData; - } - - public TargetPointer MethodTable => EEClassData.MethodTable; - public ushort NumMethods => EEClassData.NumMethods; - public ushort NumNonVirtualSlots => EEClassData.NumNonVirtualSlots; - - public uint TypeDefTypeAttributes => EEClassData.DwAttrClass; -} - - -internal partial struct Metadata_1 : IMetadata -{ - private readonly Target _target; - private readonly TargetPointer _freeObjectMethodTablePointer; - - internal static class Constants - { - internal const int MethodTableDwFlags2TypeDefRidShift = 8; - } - - [Flags] - internal enum EEClassOrCanonMTBits - { - EEClass = 0, - CanonMT = 1, - Mask = 1, - } - - internal Metadata_1(Target target, TargetPointer freeObjectMethodTablePointer) - { - _target = target; - _freeObjectMethodTablePointer = freeObjectMethodTablePointer; - } - - public TargetPointer FreeObjectMethodTablePointer => _freeObjectMethodTablePointer; - - private UntrustedMethodTable GetUntrustedMethodTableData(TargetPointer methodTablePointer) - { - return new UntrustedMethodTable(_target, methodTablePointer); - } - - private UntrustedEEClass GetUntrustedEEClassData(TargetPointer eeClassPointer) - { - return new UntrustedEEClass(_target, eeClassPointer); - } - - public MethodTable GetMethodTableData(TargetPointer methodTablePointer) - { - // Check if we cached it already - if (_target.ProcessedData.TryGet(methodTablePointer, out Data.MethodTable? methodTableData)) - { - return new MethodTable(methodTableData, methodTablePointer == FreeObjectMethodTablePointer); - } - - // Otherwse, don't trust it yet - UntrustedMethodTable untrustedMethodTable = GetUntrustedMethodTableData(methodTablePointer); - - // if it's the free object method table, we can trust it - if (methodTablePointer == FreeObjectMethodTablePointer) - { - Data.MethodTable freeObjectMethodTableData = _target.ProcessedData.GetOrAdd(methodTablePointer); - return new MethodTable(freeObjectMethodTableData, isFreeObjectMT: true); - } - if (!ValidateMethodTablePointer(in untrustedMethodTable)) - { - throw new ArgumentException("Invalid method table pointer"); - } - // ok, we trust it, cache the data - Data.MethodTable trustedMethodTableData = _target.ProcessedData.GetOrAdd(methodTablePointer); - return new MethodTable(trustedMethodTableData, isFreeObjectMT: false); - } - - private bool ValidateMethodTablePointer(in UntrustedMethodTable umt) - { - // FIXME: is methodTablePointer properly sign-extended from 32-bit targets? - // FIXME2: do we need this? Data.MethodTable probably would throw if methodTablePointer is invalid - //if (umt.MethodTablePointer == TargetPointer.Null || umt.MethodTablePointer == TargetPointer.MinusOne) - //{ - // return false; - //} - try - { - if (!ValidateWithPossibleAV(umt)) - { - return false; - } - if (!ValidateMethodTable(umt)) - { - return false; - } - } - catch (Exception) - { - // FIXME: maybe don't swallow all exceptions? - return false; - } - return true; - } - - private bool ValidateWithPossibleAV(in UntrustedMethodTable methodTable) - { - // For non-generic classes, we can rely on comparing - // object->methodtable->class->methodtable - // to - // object->methodtable - // - // However, for generic instantiation this does not work. There we must - // compare - // - // object->methodtable->class->methodtable->class - // to - // object->methodtable->class - TargetPointer eeClassPtr = GetClassWithPossibleAV(methodTable); - if (eeClassPtr != TargetPointer.Null) - { - UntrustedEEClass eeClass = GetUntrustedEEClassData(eeClassPtr); - TargetPointer methodTablePtrFromClass = GetMethodTableWithPossibleAV(in eeClass); - if (methodTable.Address == methodTablePtrFromClass) - { - return true; - } - if (((IMethodTableFlags)methodTable).HasInstantiation || ((IMethodTableFlags)methodTable).IsArray) - { - UntrustedMethodTable methodTableFromClass = GetUntrustedMethodTableData(methodTablePtrFromClass); - TargetPointer classFromMethodTable = GetClassWithPossibleAV(in methodTableFromClass); - return classFromMethodTable == eeClassPtr; - } - } - return false; - } - - private bool ValidateMethodTable(in UntrustedMethodTable methodTable) - { - if (!((IMethodTableFlags)methodTable).IsInterface && !((IMethodTableFlags)methodTable).IsString) - { - if (methodTable.BaseSize == 0 || !_target.IsAlignedToPointerSize(methodTable.BaseSize)) - { - return false; - } - } - return true; - } - - internal static EEClassOrCanonMTBits GetEEClassOrCanonMTBits(TargetPointer eeClassOrCanonMTPtr) - { - return (EEClassOrCanonMTBits)(eeClassOrCanonMTPtr & (ulong)EEClassOrCanonMTBits.Mask); - } - private TargetPointer GetClassWithPossibleAV(in UntrustedMethodTable methodTable) - { - TargetPointer eeClassOrCanonMT = methodTable.EEClassOrCanonMT; - - if (GetEEClassOrCanonMTBits(eeClassOrCanonMT) == EEClassOrCanonMTBits.EEClass) - { - return methodTable.EEClass; - } - else - { - TargetPointer canonicalMethodTablePtr = methodTable.CanonMT; - UntrustedMethodTable umt = GetUntrustedMethodTableData(canonicalMethodTablePtr); - return umt.EEClass; - } - } - - private static TargetPointer GetMethodTableWithPossibleAV(in UntrustedEEClass eeClass) - { - return eeClass.MethodTable; - } - - public TargetPointer GetClass(in MethodTable methodTable) - { - switch (GetEEClassOrCanonMTBits(methodTable.EEClassOrCanonMT)) - { - case EEClassOrCanonMTBits.EEClass: - return methodTable.EEClassOrCanonMT; - case EEClassOrCanonMTBits.CanonMT: - TargetPointer canonMTPtr = new TargetPointer((ulong)methodTable.EEClassOrCanonMT & ~(ulong)Metadata_1.EEClassOrCanonMTBits.Mask); - MethodTable canonMT = GetMethodTableData(canonMTPtr); - return canonMT.EEClassOrCanonMT; // canonical method table EEClassOrCanonMT is always EEClass - default: - throw new InvalidOperationException(); - } - } - - // only called on trusted method tables, so we always trust the resulting EEClass - private EEClass GetClassData(in MethodTable methodTable) - { - TargetPointer clsPtr = GetClass(in methodTable); - // Check if we cached it already - if (_target.ProcessedData.TryGet(clsPtr, out Data.EEClass? eeClassData)) - { - return new EEClass_1(eeClassData); - } - eeClassData = _target.ProcessedData.GetOrAdd(clsPtr); - return new EEClass_1(eeClassData); - } - - public bool IsString(in MethodTable methodTable) => ((IMethodTableFlags)methodTable).IsString; - public bool ContainsPointers(in MethodTable methodTable) => ((IMethodTableFlags)methodTable).ContainsPointers; - - public uint GetTypeDefToken(in MethodTable methodTable) - { - return (uint)(((IMethodTableFlags)methodTable).GetTypeDefRid() | ((int)TableIndex.TypeDef << 24)); - } - - public ushort GetNumMethods(in MethodTable methodTable) - { - EEClass cls = GetClassData(in methodTable); - return cls.NumMethods; - } - - private ushort GetNumNonVirtualSlots(in MethodTable methodTable) - { - TargetPointer eeClassOrCanonMT = methodTable.EEClassOrCanonMT; - if (GetEEClassOrCanonMTBits(eeClassOrCanonMT) == EEClassOrCanonMTBits.EEClass) - { - return GetClassData(methodTable).NumNonVirtualSlots; - } - else - { - return 0; - } - } - - public ushort GetNumVtableSlots(in MethodTable methodTable) - { - return checked((ushort)(methodTable.NumVirtuals + GetNumNonVirtualSlots(methodTable))); - } - - public uint GetTypeDefTypeAttributes(in MethodTable methodTable) - { - return GetClassData(methodTable).TypeDefTypeAttributes; - } - - public bool IsDynamicStatics(in MethodTable methodTable) => ((IMethodTableFlags)methodTable).GetFlag(WFLAGS2_ENUM.DynamicStatics) != 0; - - [Flags] - internal enum MethodTableAuxiliaryDataFlags : uint - { - Initialized = 0x0001, - HasCheckedCanCompareBitsOrUseFastGetHashCode = 0x0002, // Whether we have checked the overridden Equals or GetHashCode - CanCompareBitsOrUseFastGetHashCode = 0x0004, // Is any field type or sub field type overridden Equals or GetHashCode - - HasApproxParent = 0x0010, - // enum_unused = 0x0020, - IsNotFullyLoaded = 0x0040, - DependenciesLoaded = 0x0080, // class and all dependencies loaded up to CLASS_LOADED_BUT_NOT_VERIFIED - - IsInitError = 0x0100, - IsStaticDataAllocated = 0x0200, - // unum_unused = 0x0400, - IsTlsIndexAllocated = 0x0800, - MayHaveOpenInterfaceInInterfaceMap = 0x1000, - // enum_unused = 0x2000, - - // ifdef _DEBUG - DEBUG_ParentMethodTablePointerValid = 0x4000, - DEBUG_HasInjectedInterfaceDuplicates = 0x8000, - } -} diff --git a/src/native/managed/cdacreader/src/Contracts/Metadata.MethodTableFlags.cs b/src/native/managed/cdacreader/src/Contracts/Metadata_1.MethodFlags.cs similarity index 100% rename from src/native/managed/cdacreader/src/Contracts/Metadata.MethodTableFlags.cs rename to src/native/managed/cdacreader/src/Contracts/Metadata_1.MethodFlags.cs diff --git a/src/native/managed/cdacreader/src/Contracts/Metadata_1.cs b/src/native/managed/cdacreader/src/Contracts/Metadata_1.cs new file mode 100644 index 00000000000000..23441e4f32aa56 --- /dev/null +++ b/src/native/managed/cdacreader/src/Contracts/Metadata_1.cs @@ -0,0 +1,391 @@ +// 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; +using System.Reflection.Metadata.Ecma335; +using Microsoft.Diagnostics.DataContractReader.Data; + +namespace Microsoft.Diagnostics.DataContractReader.Contracts; + +// GC Heap corruption may create situations where a putative pointer to a MethodTable +// may point to garbage. So this struct represents a MethodTable that we don't necessarily +// trust to be valid. +// see Metadata_1.ValidateMethodTablePointer +// This doesn't need as many properties as MethodTable because we dont' want to be operating on +// an UntrustedMethodTable for too long +internal struct UntrustedMethodTable_1 : IMethodTableFlags +{ + private readonly Target _target; + private readonly Target.TypeInfo _type; + public TargetPointer Address { get; init; } + + internal UntrustedMethodTable_1(Target target, TargetPointer methodTablePointer) + { + _target = target; + _type = target.GetTypeInfo(DataType.MethodTable); + Address = methodTablePointer; + } + + // all these accessors might throw if MethodTablePointer is invalid + public uint DwFlags => _target.Read(Address + (ulong)_type.Fields[nameof(DwFlags2)].Offset); + public uint DwFlags2 => _target.Read(Address + (ulong)_type.Fields[nameof(DwFlags)].Offset); + public uint BaseSize => _target.Read(Address + (ulong)_type.Fields[nameof(BaseSize)].Offset); + + internal TargetPointer EEClassOrCanonMT => _target.ReadPointer(Address + (ulong)_type.Fields[nameof(EEClassOrCanonMT)].Offset); + internal TargetPointer EEClass => Metadata_1.GetEEClassOrCanonMTBits(EEClassOrCanonMT) == Metadata_1.EEClassOrCanonMTBits.EEClass ? EEClassOrCanonMT : throw new InvalidOperationException("not an EEClass"); + internal TargetPointer CanonMT + { + get + { + if (Metadata_1.GetEEClassOrCanonMTBits(EEClassOrCanonMT) == Metadata_1.EEClassOrCanonMTBits.CanonMT) + { + return new TargetPointer((ulong)EEClassOrCanonMT & ~(ulong)Metadata_1.EEClassOrCanonMTBits.Mask); + } + else + { + throw new InvalidOperationException("not a canonical method table"); + } + } + } +} + +internal struct UntrustedEEClass_1 +{ + public readonly Target _target; + private readonly Target.TypeInfo _type; + + public TargetPointer Address { get; init; } + + internal UntrustedEEClass_1(Target target, TargetPointer eeClassPointer) + { + _target = target; + Address = eeClassPointer; + _type = target.GetTypeInfo(DataType.EEClass); + } + + public TargetPointer MethodTable => _target.ReadPointer(Address + (ulong)_type.Fields[nameof(MethodTable)].Offset); +} + + +internal struct MethodTable_1 : IMethodTableFlags +{ + private Data.MethodTable MethodTableData { get; init; } + internal bool IsFreeObjectMethodTable { get; init; } + internal MethodTable_1(Data.MethodTable data, bool isFreeObjectMT) + { + MethodTableData = data; + IsFreeObjectMethodTable = isFreeObjectMT; + } + + public uint DwFlags => MethodTableData.DwFlags; + public uint DwFlags2 => MethodTableData.DwFlags2; + public uint BaseSize => MethodTableData.BaseSize; + internal TargetPointer EEClassOrCanonMT => MethodTableData.EEClassOrCanonMT; + internal TargetPointer Module => MethodTableData.Module; + + public TargetPointer EEClass => Metadata_1.GetEEClassOrCanonMTBits(EEClassOrCanonMT) == Metadata_1.EEClassOrCanonMTBits.EEClass ? EEClassOrCanonMT : throw new InvalidOperationException("not an EEClass"); + + + public TargetPointer ParentMethodTable => MethodTableData.ParentMethodTable; + public ushort NumInterfaces => MethodTableData.NumInterfaces; + public ushort NumVirtuals => MethodTableData.NumVirtuals; + +} + +internal struct EEClass_1 +{ + public Data.EEClass EEClassData { get; init; } + internal EEClass_1(Data.EEClass eeClassData) + { + EEClassData = eeClassData; + } + + public TargetPointer MethodTable => EEClassData.MethodTable; + public ushort NumMethods => EEClassData.NumMethods; + public ushort NumNonVirtualSlots => EEClassData.NumNonVirtualSlots; + + public uint TypeDefTypeAttributes => EEClassData.DwAttrClass; +} + + +internal partial struct Metadata_1 : IMetadata +{ + private readonly Target _target; + private readonly TargetPointer _freeObjectMethodTablePointer; + + private readonly Dictionary _methodTables = new(); + + internal static class Constants + { + internal const int MethodTableDwFlags2TypeDefRidShift = 8; + } + + [Flags] + internal enum EEClassOrCanonMTBits + { + EEClass = 0, + CanonMT = 1, + Mask = 1, + } + + internal Metadata_1(Target target, TargetPointer freeObjectMethodTablePointer) + { + _target = target; + _freeObjectMethodTablePointer = freeObjectMethodTablePointer; + } + + public TargetPointer FreeObjectMethodTablePointer => _freeObjectMethodTablePointer; + + private UntrustedMethodTable_1 GetUntrustedMethodTableData(TargetPointer methodTablePointer) + { + return new UntrustedMethodTable_1(_target, methodTablePointer); + } + + private UntrustedEEClass_1 GetUntrustedEEClassData(TargetPointer eeClassPointer) + { + return new UntrustedEEClass_1(_target, eeClassPointer); + } + + public MethodTableHandle GetMethodTableData(TargetPointer methodTablePointer) + { + // if we already trust that address, return a handle + if (_methodTables.ContainsKey(methodTablePointer)) + { + return new MethodTableHandle(methodTablePointer); + } + // Check if we cached the underlying data already + if (_target.ProcessedData.TryGet(methodTablePointer, out Data.MethodTable? methodTableData)) + { + // we already cached the data, we trust it, create the representation struct for our use + MethodTable_1 trustedMethodTable = new MethodTable_1(methodTableData, methodTablePointer == FreeObjectMethodTablePointer); + _ = _methodTables.TryAdd(methodTablePointer, trustedMethodTable); + return new MethodTableHandle(methodTablePointer); + } + + // Otherwse, don't trust it yet + UntrustedMethodTable_1 untrustedMethodTable = GetUntrustedMethodTableData(methodTablePointer); + + // if it's the free object method table, we can trust it + if (methodTablePointer == FreeObjectMethodTablePointer) + { + Data.MethodTable freeObjectMethodTableData = _target.ProcessedData.GetOrAdd(methodTablePointer); + MethodTable_1 trustedMethodTable = new MethodTable_1(freeObjectMethodTableData, isFreeObjectMT: true); + _ = _methodTables.TryAdd(methodTablePointer, trustedMethodTable); + return new MethodTableHandle(methodTablePointer); + } + if (!ValidateMethodTablePointer(in untrustedMethodTable)) + { + throw new ArgumentException("Invalid method table pointer"); + } + // ok, we trust it, cache the data + Data.MethodTable trustedMethodTableData = _target.ProcessedData.GetOrAdd(methodTablePointer); + MethodTable_1 trustedMethodTableF = new MethodTable_1(trustedMethodTableData, isFreeObjectMT: false); + _ = _methodTables.TryAdd(methodTablePointer, trustedMethodTableF); + return new MethodTableHandle(methodTablePointer); + } + + private bool ValidateMethodTablePointer(in UntrustedMethodTable_1 umt) + { + // FIXME: is methodTablePointer properly sign-extended from 32-bit targets? + // FIXME2: do we need this? Data.MethodTable probably would throw if methodTablePointer is invalid + //if (umt.MethodTablePointer == TargetPointer.Null || umt.MethodTablePointer == TargetPointer.MinusOne) + //{ + // return false; + //} + try + { + if (!ValidateWithPossibleAV(umt)) + { + return false; + } + if (!ValidateMethodTable(umt)) + { + return false; + } + } + catch (Exception) + { + // FIXME: maybe don't swallow all exceptions? + return false; + } + return true; + } + + private bool ValidateWithPossibleAV(in UntrustedMethodTable_1 methodTable) + { + // For non-generic classes, we can rely on comparing + // object->methodtable->class->methodtable + // to + // object->methodtable + // + // However, for generic instantiation this does not work. There we must + // compare + // + // object->methodtable->class->methodtable->class + // to + // object->methodtable->class + TargetPointer eeClassPtr = GetClassWithPossibleAV(methodTable); + if (eeClassPtr != TargetPointer.Null) + { + UntrustedEEClass_1 eeClass = GetUntrustedEEClassData(eeClassPtr); + TargetPointer methodTablePtrFromClass = GetMethodTableWithPossibleAV(in eeClass); + if (methodTable.Address == methodTablePtrFromClass) + { + return true; + } + if (((IMethodTableFlags)methodTable).HasInstantiation || ((IMethodTableFlags)methodTable).IsArray) + { + UntrustedMethodTable_1 methodTableFromClass = GetUntrustedMethodTableData(methodTablePtrFromClass); + TargetPointer classFromMethodTable = GetClassWithPossibleAV(in methodTableFromClass); + return classFromMethodTable == eeClassPtr; + } + } + return false; + } + + private bool ValidateMethodTable(in UntrustedMethodTable_1 methodTable) + { + if (!((IMethodTableFlags)methodTable).IsInterface && !((IMethodTableFlags)methodTable).IsString) + { + if (methodTable.BaseSize == 0 || !_target.IsAlignedToPointerSize(methodTable.BaseSize)) + { + return false; + } + } + return true; + } + + internal static EEClassOrCanonMTBits GetEEClassOrCanonMTBits(TargetPointer eeClassOrCanonMTPtr) + { + return (EEClassOrCanonMTBits)(eeClassOrCanonMTPtr & (ulong)EEClassOrCanonMTBits.Mask); + } + private TargetPointer GetClassWithPossibleAV(in UntrustedMethodTable_1 methodTable) + { + TargetPointer eeClassOrCanonMT = methodTable.EEClassOrCanonMT; + + if (GetEEClassOrCanonMTBits(eeClassOrCanonMT) == EEClassOrCanonMTBits.EEClass) + { + return methodTable.EEClass; + } + else + { + TargetPointer canonicalMethodTablePtr = methodTable.CanonMT; + UntrustedMethodTable_1 umt = GetUntrustedMethodTableData(canonicalMethodTablePtr); + return umt.EEClass; + } + } + + private static TargetPointer GetMethodTableWithPossibleAV(in UntrustedEEClass_1 eeClass) + { + return eeClass.MethodTable; + } + + public uint GetBaseSize(MethodTableHandle methodTableHandle) => ((IMethodTableFlags)_methodTables[methodTableHandle.Address]).BaseSize; + + public uint GetComponentSize(MethodTableHandle methodTableHandle) + { + IMethodTableFlags methodTable = _methodTables[methodTableHandle.Address]; + return methodTable.HasComponentSize ? methodTable.RawGetComponentSize() : 0u; + } + public TargetPointer GetClass(MethodTableHandle methodTableHandle) + { + MethodTable_1 methodTable = _methodTables[methodTableHandle.Address]; + switch (GetEEClassOrCanonMTBits(methodTable.EEClassOrCanonMT)) + { + case EEClassOrCanonMTBits.EEClass: + return methodTable.EEClassOrCanonMT; + case EEClassOrCanonMTBits.CanonMT: + TargetPointer canonMTPtr = new TargetPointer((ulong)methodTable.EEClassOrCanonMT & ~(ulong)Metadata_1.EEClassOrCanonMTBits.Mask); + MethodTableHandle canonMTHandle = GetMethodTableData(canonMTPtr); + MethodTable_1 canonMT = _methodTables[canonMTHandle.Address]; + return canonMT.EEClassOrCanonMT; // canonical method table EEClassOrCanonMT is always EEClass + default: + throw new InvalidOperationException(); + } + } + + // only called on trusted method tables, so we always trust the resulting EEClass + private EEClass_1 GetClassData(MethodTableHandle methodTableHandle) + { + TargetPointer clsPtr = GetClass(methodTableHandle); + // Check if we cached it already + if (_target.ProcessedData.TryGet(clsPtr, out Data.EEClass? eeClassData)) + { + return new EEClass_1(eeClassData); + } + eeClassData = _target.ProcessedData.GetOrAdd(clsPtr); + return new EEClass_1(eeClassData); + } + + public bool IsFreeObjectMethodTable(MethodTableHandle methodTableHandle) + { + // TODO: just store the MethodTableHandle of the free object MT in the contract and do an equality comparison + // no need to store it on every MethodTable_1 instance + return _methodTables[methodTableHandle.Address].IsFreeObjectMethodTable; + } + public bool IsString(MethodTableHandle methodTableHandle) => ((IMethodTableFlags)_methodTables[methodTableHandle.Address]).IsString; + public bool ContainsPointers(MethodTableHandle methodTableHandle) => ((IMethodTableFlags)_methodTables[methodTableHandle.Address]).ContainsPointers; + + public uint GetTypeDefToken(MethodTableHandle methodTableHandle) + { + MethodTable_1 methodTable = _methodTables[methodTableHandle.Address]; + return (uint)(((IMethodTableFlags)methodTable).GetTypeDefRid() | ((int)TableIndex.TypeDef << 24)); + } + + public ushort GetNumMethods(MethodTableHandle methodTableHandle) + { + EEClass_1 cls = GetClassData(methodTableHandle); + return cls.NumMethods; + } + + private ushort GetNumNonVirtualSlots(MethodTableHandle methodTableHandle, in MethodTable_1 methodTable) + { + TargetPointer eeClassOrCanonMT = methodTable.EEClassOrCanonMT; + if (GetEEClassOrCanonMTBits(eeClassOrCanonMT) == EEClassOrCanonMTBits.EEClass) + { + return GetClassData(methodTableHandle).NumNonVirtualSlots; + } + else + { + return 0; + } + } + + public ushort GetNumVtableSlots(MethodTableHandle methodTableHandle) + { + MethodTable_1 methodTable = _methodTables[methodTableHandle.Address]; + return checked((ushort)(methodTable.NumVirtuals + GetNumNonVirtualSlots(methodTableHandle, methodTable))); + } + + public uint GetTypeDefTypeAttributes(MethodTableHandle methodTableHandle) + { + return GetClassData(methodTableHandle).TypeDefTypeAttributes; + } + + public bool IsDynamicStatics(MethodTableHandle methodTableHandle) => ((IMethodTableFlags)_methodTables[methodTableHandle.Address]).GetFlag(WFLAGS2_ENUM.DynamicStatics) != 0; + + [Flags] + internal enum MethodTableAuxiliaryDataFlags : uint + { + Initialized = 0x0001, + HasCheckedCanCompareBitsOrUseFastGetHashCode = 0x0002, // Whether we have checked the overridden Equals or GetHashCode + CanCompareBitsOrUseFastGetHashCode = 0x0004, // Is any field type or sub field type overridden Equals or GetHashCode + + HasApproxParent = 0x0010, + // enum_unused = 0x0020, + IsNotFullyLoaded = 0x0040, + DependenciesLoaded = 0x0080, // class and all dependencies loaded up to CLASS_LOADED_BUT_NOT_VERIFIED + + IsInitError = 0x0100, + IsStaticDataAllocated = 0x0200, + // unum_unused = 0x0400, + IsTlsIndexAllocated = 0x0800, + MayHaveOpenInterfaceInInterfaceMap = 0x1000, + // enum_unused = 0x2000, + + // ifdef _DEBUG + DEBUG_ParentMethodTablePointerValid = 0x4000, + DEBUG_HasInjectedInterfaceDuplicates = 0x8000, + } +} diff --git a/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs b/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs index 136bd49fda720f..dfc84e51e1979b 100644 --- a/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs +++ b/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; +using System.ComponentModel; using System.Runtime.InteropServices; using System.Runtime.InteropServices.Marshalling; @@ -88,23 +89,26 @@ public unsafe int GetMethodTableData(ulong mt, DacpMethodTableData* data) try { Contracts.IMetadata contract = _target.Contracts.Metadata; - Contracts.MethodTable_1 methodTable = contract.GetMethodTableData(mt); + Contracts.MethodTableHandle methodTable = contract.GetMethodTableData(mt); DacpMethodTableData result = default; - result.baseSize = methodTable.BaseSize; + result.baseSize = contract.GetBaseSize(methodTable); if (contract.IsString(methodTable)) result.baseSize -= 2 /*sizeof(WCHAR) */; - result.componentSize = checked((uint)methodTable.GetComponentSize()); - result.bIsFree = methodTable.IsFreeObjectMethodTable ? 1 : 0; - if (!methodTable.IsFreeObjectMethodTable) + result.componentSize = contract.GetComponentSize(methodTable); + bool isFreeObjectMT = contract.IsFreeObjectMethodTable(methodTable); + result.bIsFree = isFreeObjectMT ? 1 : 0; + if (!isFreeObjectMT) { - result.module = methodTable.Module; + result.module = contract.GetModule(methodTable); + // TODO[cdac]: it looks like this is only used in output. Can we just return the canonical MT pointer here + // instead and avoid exposing the EEClass concept from the contract? result.@class = contract.GetClass(methodTable); - result.parentMethodTable = methodTable.ParentMethodTable; - result.wNumInterfaces = methodTable.NumInterfaces; + result.parentMethodTable = contract.GetParentMethodTable(methodTable); + result.wNumInterfaces = contract.GetNumInterfaces(methodTable); result.wNumMethods = contract.GetNumMethods(methodTable); result.wNumVtableSlots = contract.GetNumVtableSlots(methodTable); - result.wNumVirtuals = methodTable.NumVirtuals; + result.wNumVirtuals = contract.GetNumVirtuals(methodTable); result.cl = contract.GetTypeDefToken(methodTable); result.dwAttrClass = contract.GetTypeDefTypeAttributes(methodTable); result.bContainsPointers = contract.ContainsPointers(methodTable) ? 1 : 0; From a65fd50ad6743daab26f5219fb5a79932d371015 Mon Sep 17 00:00:00 2001 From: Aleksey Kliger Date: Tue, 18 Jun 2024 11:06:42 -0400 Subject: [PATCH 20/62] Add contract accessors for MethodTableHandle --- .../managed/cdacreader/src/Contracts/Metadata.cs | 5 ++++- .../managed/cdacreader/src/Contracts/Metadata_1.cs | 12 +++++++++--- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/src/native/managed/cdacreader/src/Contracts/Metadata.cs b/src/native/managed/cdacreader/src/Contracts/Metadata.cs index d8f9128382d0f5..7c505887d73b48 100644 --- a/src/native/managed/cdacreader/src/Contracts/Metadata.cs +++ b/src/native/managed/cdacreader/src/Contracts/Metadata.cs @@ -33,7 +33,9 @@ static IContract IContract.Create(Target target, int version) public virtual MethodTableHandle GetMethodTableData(TargetPointer targetPointer) => throw new NotImplementedException(); + public virtual TargetPointer GetModule(MethodTableHandle methodTable) => throw new NotImplementedException(); public virtual TargetPointer GetClass(MethodTableHandle methodTable) => throw new NotImplementedException(); + public virtual TargetPointer GetParentMethodTable(MethodTableHandle methodTable) => throw new NotImplementedException(); public virtual uint GetBaseSize(MethodTableHandle methodTable) => throw new NotImplementedException(); public virtual uint GetComponentSize(MethodTableHandle methodTable) => throw new NotImplementedException(); @@ -44,7 +46,8 @@ static IContract IContract.Create(Target target, int version) public virtual bool IsDynamicStatics(MethodTableHandle methodTable) => throw new NotImplementedException(); public virtual uint GetTypeDefToken(MethodTableHandle methodTable) => throw new NotImplementedException(); public virtual ushort GetNumMethods(MethodTableHandle methodTable) => throw new NotImplementedException(); - + public virtual ushort GetNumInterfaces(MethodTableHandle methodTable) => throw new NotImplementedException(); + public virtual ushort GetNumVirtuals(MethodTableHandle methodTable) => throw new NotImplementedException(); public virtual ushort GetNumVtableSlots(MethodTableHandle methodTable) => throw new NotImplementedException(); public virtual uint GetTypeDefTypeAttributes(MethodTableHandle methodTable) => throw new NotImplementedException(); diff --git a/src/native/managed/cdacreader/src/Contracts/Metadata_1.cs b/src/native/managed/cdacreader/src/Contracts/Metadata_1.cs index 23441e4f32aa56..21bf327426e413 100644 --- a/src/native/managed/cdacreader/src/Contracts/Metadata_1.cs +++ b/src/native/managed/cdacreader/src/Contracts/Metadata_1.cs @@ -305,6 +305,9 @@ public TargetPointer GetClass(MethodTableHandle methodTableHandle) } } + public TargetPointer GetModule(MethodTableHandle methodTableHandle) => _methodTables[methodTableHandle.Address].Module; + public TargetPointer GetParentMethodTable(MethodTableHandle methodTableHandle) => _methodTables[methodTableHandle.Address].ParentMethodTable; + // only called on trusted method tables, so we always trust the resulting EEClass private EEClass_1 GetClassData(MethodTableHandle methodTableHandle) { @@ -339,8 +342,12 @@ public ushort GetNumMethods(MethodTableHandle methodTableHandle) return cls.NumMethods; } - private ushort GetNumNonVirtualSlots(MethodTableHandle methodTableHandle, in MethodTable_1 methodTable) + public ushort GetNumInterfaces(MethodTableHandle methodTableHandle) => _methodTables[methodTableHandle.Address].NumInterfaces; + + public ushort GetNumVirtuals(MethodTableHandle methodTableHandle) => _methodTables[methodTableHandle.Address].NumVirtuals; + private ushort GetNumNonVirtualSlots(MethodTableHandle methodTableHandle) { + MethodTable_1 methodTable = _methodTables[methodTableHandle.Address]; TargetPointer eeClassOrCanonMT = methodTable.EEClassOrCanonMT; if (GetEEClassOrCanonMTBits(eeClassOrCanonMT) == EEClassOrCanonMTBits.EEClass) { @@ -354,8 +361,7 @@ private ushort GetNumNonVirtualSlots(MethodTableHandle methodTableHandle, in Met public ushort GetNumVtableSlots(MethodTableHandle methodTableHandle) { - MethodTable_1 methodTable = _methodTables[methodTableHandle.Address]; - return checked((ushort)(methodTable.NumVirtuals + GetNumNonVirtualSlots(methodTableHandle, methodTable))); + return checked((ushort)(GetNumVirtuals(methodTableHandle) + GetNumNonVirtualSlots(methodTableHandle))); } public uint GetTypeDefTypeAttributes(MethodTableHandle methodTableHandle) From 6f844bff5b996798bb4f9c9052b6b59130850402 Mon Sep 17 00:00:00 2001 From: Aleksey Kliger Date: Tue, 18 Jun 2024 11:52:35 -0400 Subject: [PATCH 21/62] fixup --- src/native/managed/cdacreader/src/Contracts/Metadata_1.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/native/managed/cdacreader/src/Contracts/Metadata_1.cs b/src/native/managed/cdacreader/src/Contracts/Metadata_1.cs index 21bf327426e413..b7008979f010bf 100644 --- a/src/native/managed/cdacreader/src/Contracts/Metadata_1.cs +++ b/src/native/managed/cdacreader/src/Contracts/Metadata_1.cs @@ -283,11 +283,11 @@ private static TargetPointer GetMethodTableWithPossibleAV(in UntrustedEEClass_1 public uint GetBaseSize(MethodTableHandle methodTableHandle) => ((IMethodTableFlags)_methodTables[methodTableHandle.Address]).BaseSize; - public uint GetComponentSize(MethodTableHandle methodTableHandle) + private static uint GetComponentSize(MethodTable_1 methodTable) { - IMethodTableFlags methodTable = _methodTables[methodTableHandle.Address]; - return methodTable.HasComponentSize ? methodTable.RawGetComponentSize() : 0u; + return ((IMethodTableFlags)methodTable).HasComponentSize ? ((IMethodTableFlags)methodTable).RawGetComponentSize() : 0u; } + public uint GetComponentSize(MethodTableHandle methodTableHandle) => GetComponentSize(_methodTables[methodTableHandle.Address]); public TargetPointer GetClass(MethodTableHandle methodTableHandle) { MethodTable_1 methodTable = _methodTables[methodTableHandle.Address]; From 76e03848339b45277828999a64118cb7a113a797 Mon Sep 17 00:00:00 2001 From: Aleksey Kliger Date: Tue, 18 Jun 2024 11:56:29 -0400 Subject: [PATCH 22/62] simplify FreeObjectMethodTable handling --- .../cdacreader/src/Contracts/Metadata_1.cs | 20 +++++++------------ 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/src/native/managed/cdacreader/src/Contracts/Metadata_1.cs b/src/native/managed/cdacreader/src/Contracts/Metadata_1.cs index b7008979f010bf..b1ea1c6c6e61c4 100644 --- a/src/native/managed/cdacreader/src/Contracts/Metadata_1.cs +++ b/src/native/managed/cdacreader/src/Contracts/Metadata_1.cs @@ -71,11 +71,9 @@ internal UntrustedEEClass_1(Target target, TargetPointer eeClassPointer) internal struct MethodTable_1 : IMethodTableFlags { private Data.MethodTable MethodTableData { get; init; } - internal bool IsFreeObjectMethodTable { get; init; } - internal MethodTable_1(Data.MethodTable data, bool isFreeObjectMT) + internal MethodTable_1(Data.MethodTable data) { MethodTableData = data; - IsFreeObjectMethodTable = isFreeObjectMT; } public uint DwFlags => MethodTableData.DwFlags; @@ -86,7 +84,6 @@ internal MethodTable_1(Data.MethodTable data, bool isFreeObjectMT) public TargetPointer EEClass => Metadata_1.GetEEClassOrCanonMTBits(EEClassOrCanonMT) == Metadata_1.EEClassOrCanonMTBits.EEClass ? EEClassOrCanonMT : throw new InvalidOperationException("not an EEClass"); - public TargetPointer ParentMethodTable => MethodTableData.ParentMethodTable; public ushort NumInterfaces => MethodTableData.NumInterfaces; public ushort NumVirtuals => MethodTableData.NumVirtuals; @@ -158,7 +155,7 @@ public MethodTableHandle GetMethodTableData(TargetPointer methodTablePointer) if (_target.ProcessedData.TryGet(methodTablePointer, out Data.MethodTable? methodTableData)) { // we already cached the data, we trust it, create the representation struct for our use - MethodTable_1 trustedMethodTable = new MethodTable_1(methodTableData, methodTablePointer == FreeObjectMethodTablePointer); + MethodTable_1 trustedMethodTable = new MethodTable_1(methodTableData); _ = _methodTables.TryAdd(methodTablePointer, trustedMethodTable); return new MethodTableHandle(methodTablePointer); } @@ -170,7 +167,7 @@ public MethodTableHandle GetMethodTableData(TargetPointer methodTablePointer) if (methodTablePointer == FreeObjectMethodTablePointer) { Data.MethodTable freeObjectMethodTableData = _target.ProcessedData.GetOrAdd(methodTablePointer); - MethodTable_1 trustedMethodTable = new MethodTable_1(freeObjectMethodTableData, isFreeObjectMT: true); + MethodTable_1 trustedMethodTable = new MethodTable_1(freeObjectMethodTableData); _ = _methodTables.TryAdd(methodTablePointer, trustedMethodTable); return new MethodTableHandle(methodTablePointer); } @@ -180,7 +177,7 @@ public MethodTableHandle GetMethodTableData(TargetPointer methodTablePointer) } // ok, we trust it, cache the data Data.MethodTable trustedMethodTableData = _target.ProcessedData.GetOrAdd(methodTablePointer); - MethodTable_1 trustedMethodTableF = new MethodTable_1(trustedMethodTableData, isFreeObjectMT: false); + MethodTable_1 trustedMethodTableF = new MethodTable_1(trustedMethodTableData); _ = _methodTables.TryAdd(methodTablePointer, trustedMethodTableF); return new MethodTableHandle(methodTablePointer); } @@ -256,6 +253,7 @@ private bool ValidateMethodTable(in UntrustedMethodTable_1 methodTable) return true; } + internal static EEClassOrCanonMTBits GetEEClassOrCanonMTBits(TargetPointer eeClassOrCanonMTPtr) { return (EEClassOrCanonMTBits)(eeClassOrCanonMTPtr & (ulong)EEClassOrCanonMTBits.Mask); @@ -321,12 +319,8 @@ private EEClass_1 GetClassData(MethodTableHandle methodTableHandle) return new EEClass_1(eeClassData); } - public bool IsFreeObjectMethodTable(MethodTableHandle methodTableHandle) - { - // TODO: just store the MethodTableHandle of the free object MT in the contract and do an equality comparison - // no need to store it on every MethodTable_1 instance - return _methodTables[methodTableHandle.Address].IsFreeObjectMethodTable; - } + public bool IsFreeObjectMethodTable(MethodTableHandle methodTableHandle) => FreeObjectMethodTablePointer == methodTableHandle.Address; + public bool IsString(MethodTableHandle methodTableHandle) => ((IMethodTableFlags)_methodTables[methodTableHandle.Address]).IsString; public bool ContainsPointers(MethodTableHandle methodTableHandle) => ((IMethodTableFlags)_methodTables[methodTableHandle.Address]).ContainsPointers; From cdb7543b720a55ac996bf7469aa0807ed1060c1b Mon Sep 17 00:00:00 2001 From: Aleksey Kliger Date: Tue, 18 Jun 2024 12:40:58 -0400 Subject: [PATCH 23/62] cleanup --- .../cdacreader/src/Contracts/Metadata.cs | 2 +- ...lags.cs => Metadata_1.MethodTableFlags.cs} | 93 ++++++----- .../cdacreader/src/Contracts/Metadata_1.cs | 148 +++++++++--------- .../cdacreader/src/Legacy/SOSDacImpl.cs | 3 +- 4 files changed, 129 insertions(+), 117 deletions(-) rename src/native/managed/cdacreader/src/Contracts/{Metadata_1.MethodFlags.cs => Metadata_1.MethodTableFlags.cs} (70%) diff --git a/src/native/managed/cdacreader/src/Contracts/Metadata.cs b/src/native/managed/cdacreader/src/Contracts/Metadata.cs index 7c505887d73b48..4a0a98b19f5b3e 100644 --- a/src/native/managed/cdacreader/src/Contracts/Metadata.cs +++ b/src/native/managed/cdacreader/src/Contracts/Metadata.cs @@ -31,7 +31,7 @@ static IContract IContract.Create(Target target, int version) }; } - public virtual MethodTableHandle GetMethodTableData(TargetPointer targetPointer) => throw new NotImplementedException(); + public virtual MethodTableHandle GetMethodTableHandle(TargetPointer targetPointer) => throw new NotImplementedException(); public virtual TargetPointer GetModule(MethodTableHandle methodTable) => throw new NotImplementedException(); public virtual TargetPointer GetClass(MethodTableHandle methodTable) => throw new NotImplementedException(); diff --git a/src/native/managed/cdacreader/src/Contracts/Metadata_1.MethodFlags.cs b/src/native/managed/cdacreader/src/Contracts/Metadata_1.MethodTableFlags.cs similarity index 70% rename from src/native/managed/cdacreader/src/Contracts/Metadata_1.MethodFlags.cs rename to src/native/managed/cdacreader/src/Contracts/Metadata_1.MethodTableFlags.cs index fd3a44b75f6311..3c9c8c3928e7e5 100644 --- a/src/native/managed/cdacreader/src/Contracts/Metadata_1.MethodFlags.cs +++ b/src/native/managed/cdacreader/src/Contracts/Metadata_1.MethodTableFlags.cs @@ -2,10 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; -using UntrustedMethodTable = Microsoft.Diagnostics.DataContractReader.Contracts.UntrustedMethodTable_1; -using MethodTable = Microsoft.Diagnostics.DataContractReader.Contracts.MethodTable_1; -using UntrustedEEClass = Microsoft.Diagnostics.DataContractReader.Contracts.UntrustedEEClass_1; -using EEClass = Microsoft.Diagnostics.DataContractReader.Contracts.EEClass_1; namespace Microsoft.Diagnostics.DataContractReader.Contracts; @@ -142,49 +138,74 @@ internal enum WFLAGS2_ENUM : uint TokenMask = 0xFFFFFF00, } -} -internal interface IMethodTableFlags -{ - public uint DwFlags { get; } - public uint DwFlags2 { get; } - public uint BaseSize { get; } - private Metadata_1.WFLAGS_HIGH FlagsHigh => (Metadata_1.WFLAGS_HIGH)DwFlags; - private Metadata_1.WFLAGS_LOW FlagsLow => (Metadata_1.WFLAGS_LOW)DwFlags; - public int GetTypeDefRid() => (int)(DwFlags2 >> Metadata_1.Constants.MethodTableDwFlags2TypeDefRidShift); + [Flags] + internal enum MethodTableAuxiliaryDataFlags : uint + { + Initialized = 0x0001, + HasCheckedCanCompareBitsOrUseFastGetHashCode = 0x0002, // Whether we have checked the overridden Equals or GetHashCode + CanCompareBitsOrUseFastGetHashCode = 0x0004, // Is any field type or sub field type overridden Equals or GetHashCode + + HasApproxParent = 0x0010, + // enum_unused = 0x0020, + IsNotFullyLoaded = 0x0040, + DependenciesLoaded = 0x0080, // class and all dependencies loaded up to CLASS_LOADED_BUT_NOT_VERIFIED + + IsInitError = 0x0100, + IsStaticDataAllocated = 0x0200, + // unum_unused = 0x0400, + IsTlsIndexAllocated = 0x0800, + MayHaveOpenInterfaceInInterfaceMap = 0x1000, + // enum_unused = 0x2000, + + // ifdef _DEBUG + DEBUG_ParentMethodTablePointerValid = 0x4000, + DEBUG_HasInjectedInterfaceDuplicates = 0x8000, + } + + internal struct MethodTableFlags + { + public uint DwFlags { get; init; } + public uint DwFlags2 { get; init; } + public uint BaseSize { get; init; } - public Metadata_1.WFLAGS_LOW GetFlag(Metadata_1.WFLAGS_LOW mask) => throw new NotImplementedException("TODO"); - public Metadata_1.WFLAGS_HIGH GetFlag(Metadata_1.WFLAGS_HIGH mask) => FlagsHigh & mask; + private WFLAGS_HIGH FlagsHigh => (WFLAGS_HIGH)DwFlags; + private WFLAGS_LOW FlagsLow => (WFLAGS_LOW)DwFlags; + public int GetTypeDefRid() => (int)(DwFlags2 >> Constants.MethodTableDwFlags2TypeDefRidShift); - public Metadata_1.WFLAGS2_ENUM GetFlag(Metadata_1.WFLAGS2_ENUM mask) => (Metadata_1.WFLAGS2_ENUM)DwFlags2 & mask; - public bool IsInterface => GetFlag(Metadata_1.WFLAGS_HIGH.Category_Mask) == Metadata_1.WFLAGS_HIGH.Category_Interface; - public bool IsString => HasComponentSize && !IsArray && RawGetComponentSize() == 2; + public WFLAGS_LOW GetFlag(WFLAGS_LOW mask) => throw new NotImplementedException("TODO"); + public WFLAGS_HIGH GetFlag(WFLAGS_HIGH mask) => FlagsHigh & mask; - public bool HasComponentSize => GetFlag(Metadata_1.WFLAGS_HIGH.HasComponentSize) != 0; + public WFLAGS2_ENUM GetFlag(WFLAGS2_ENUM mask) => (WFLAGS2_ENUM)DwFlags2 & mask; + public bool IsInterface => GetFlag(WFLAGS_HIGH.Category_Mask) == WFLAGS_HIGH.Category_Interface; + public bool IsString => HasComponentSize && !IsArray && RawGetComponentSize() == 2; - public bool IsArray => GetFlag(Metadata_1.WFLAGS_HIGH.Category_Array_Mask) == Metadata_1.WFLAGS_HIGH.Category_Array; + public bool HasComponentSize => GetFlag(WFLAGS_HIGH.HasComponentSize) != 0; - public bool IsStringOrArray => HasComponentSize; - public ushort RawGetComponentSize() => (ushort)(DwFlags >> 16); + public bool IsArray => GetFlag(WFLAGS_HIGH.Category_Array_Mask) == WFLAGS_HIGH.Category_Array; - public bool TestFlagWithMask(Metadata_1.WFLAGS_LOW mask, Metadata_1.WFLAGS_LOW flag) - { - if (IsStringOrArray) + public bool IsStringOrArray => HasComponentSize; + public ushort RawGetComponentSize() => (ushort)(DwFlags >> 16); + + public bool TestFlagWithMask(WFLAGS_LOW mask, WFLAGS_LOW flag) { - return (Metadata_1.WFLAGS_LOW.StringArrayValues & mask) == flag; + if (IsStringOrArray) + { + return (WFLAGS_LOW.StringArrayValues & mask) == flag; + } + else + { + return (FlagsLow & mask) == flag; + } } - else + + public bool TestFlagWithMask(WFLAGS2_ENUM mask, WFLAGS2_ENUM flag) { - return (FlagsLow & mask) == flag; + return ((WFLAGS2_ENUM)DwFlags2 & mask) == flag; } - } - public bool TestFlagWithMask(Metadata_1.WFLAGS2_ENUM mask, Metadata_1.WFLAGS2_ENUM flag) - { - return ((Metadata_1.WFLAGS2_ENUM)DwFlags2 & mask) == flag; - } - - public bool HasInstantiation => !TestFlagWithMask(Metadata_1.WFLAGS_LOW.GenericsMask, Metadata_1.WFLAGS_LOW.GenericsMask_NonGeneric); + public bool HasInstantiation => !TestFlagWithMask(WFLAGS_LOW.GenericsMask, WFLAGS_LOW.GenericsMask_NonGeneric); - public bool ContainsPointers => GetFlag(Metadata_1.WFLAGS_HIGH.ContainsPointers) != 0; + public bool ContainsPointers => GetFlag(WFLAGS_HIGH.ContainsPointers) != 0; + } } diff --git a/src/native/managed/cdacreader/src/Contracts/Metadata_1.cs b/src/native/managed/cdacreader/src/Contracts/Metadata_1.cs index b1ea1c6c6e61c4..fc3bbda1216f7e 100644 --- a/src/native/managed/cdacreader/src/Contracts/Metadata_1.cs +++ b/src/native/managed/cdacreader/src/Contracts/Metadata_1.cs @@ -14,23 +14,39 @@ namespace Microsoft.Diagnostics.DataContractReader.Contracts; // see Metadata_1.ValidateMethodTablePointer // This doesn't need as many properties as MethodTable because we dont' want to be operating on // an UntrustedMethodTable for too long -internal struct UntrustedMethodTable_1 : IMethodTableFlags +internal struct UntrustedMethodTable_1 { private readonly Target _target; private readonly Target.TypeInfo _type; - public TargetPointer Address { get; init; } + internal TargetPointer Address { get; init; } + + private Metadata_1.MethodTableFlags? _methodTableFlags; internal UntrustedMethodTable_1(Target target, TargetPointer methodTablePointer) { _target = target; _type = target.GetTypeInfo(DataType.MethodTable); Address = methodTablePointer; + _methodTableFlags = null; + } + + private Metadata_1.MethodTableFlags EnsureFlags() + { + if (_methodTableFlags == null) + { + // note: may throw if the method table Address is corrupted + Metadata_1.MethodTableFlags flags = new Metadata_1.MethodTableFlags + { + DwFlags = _target.Read(Address + (ulong)_type.Fields[nameof(Metadata_1.MethodTableFlags.DwFlags)].Offset), + DwFlags2 = _target.Read(Address + (ulong)_type.Fields[nameof(Metadata_1.MethodTableFlags.DwFlags)].Offset), + BaseSize = _target.Read(Address + (ulong)_type.Fields[nameof(Metadata_1.MethodTableFlags.BaseSize)].Offset), + }; + _methodTableFlags = flags; + } + return _methodTableFlags.Value; } - // all these accessors might throw if MethodTablePointer is invalid - public uint DwFlags => _target.Read(Address + (ulong)_type.Fields[nameof(DwFlags2)].Offset); - public uint DwFlags2 => _target.Read(Address + (ulong)_type.Fields[nameof(DwFlags)].Offset); - public uint BaseSize => _target.Read(Address + (ulong)_type.Fields[nameof(BaseSize)].Offset); + internal Metadata_1.MethodTableFlags Flags => EnsureFlags(); internal TargetPointer EEClassOrCanonMT => _target.ReadPointer(Address + (ulong)_type.Fields[nameof(EEClassOrCanonMT)].Offset); internal TargetPointer EEClass => Metadata_1.GetEEClassOrCanonMTBits(EEClassOrCanonMT) == Metadata_1.EEClassOrCanonMTBits.EEClass ? EEClassOrCanonMT : throw new InvalidOperationException("not an EEClass"); @@ -55,7 +71,7 @@ internal struct UntrustedEEClass_1 public readonly Target _target; private readonly Target.TypeInfo _type; - public TargetPointer Address { get; init; } + internal TargetPointer Address { get; init; } internal UntrustedEEClass_1(Target target, TargetPointer eeClassPointer) { @@ -64,45 +80,47 @@ internal UntrustedEEClass_1(Target target, TargetPointer eeClassPointer) _type = target.GetTypeInfo(DataType.EEClass); } - public TargetPointer MethodTable => _target.ReadPointer(Address + (ulong)_type.Fields[nameof(MethodTable)].Offset); + internal TargetPointer MethodTable => _target.ReadPointer(Address + (ulong)_type.Fields[nameof(MethodTable)].Offset); } -internal struct MethodTable_1 : IMethodTableFlags +internal struct MethodTable_1 { - private Data.MethodTable MethodTableData { get; init; } + internal Metadata_1.MethodTableFlags Flags { get; } + internal ushort NumInterfaces { get; } + internal ushort NumVirtuals { get; } + internal TargetPointer ParentMethodTable { get; } + internal TargetPointer Module { get; } + internal TargetPointer EEClassOrCanonMT { get; } internal MethodTable_1(Data.MethodTable data) { - MethodTableData = data; + Flags = new Metadata_1.MethodTableFlags + { + DwFlags = data.DwFlags, + DwFlags2 = data.DwFlags2, + BaseSize = data.BaseSize, + }; + NumInterfaces = data.NumInterfaces; + NumVirtuals = data.NumVirtuals; + EEClassOrCanonMT = data.EEClassOrCanonMT; + Module = data.Module; + ParentMethodTable = data.ParentMethodTable; } - - public uint DwFlags => MethodTableData.DwFlags; - public uint DwFlags2 => MethodTableData.DwFlags2; - public uint BaseSize => MethodTableData.BaseSize; - internal TargetPointer EEClassOrCanonMT => MethodTableData.EEClassOrCanonMT; - internal TargetPointer Module => MethodTableData.Module; - - public TargetPointer EEClass => Metadata_1.GetEEClassOrCanonMTBits(EEClassOrCanonMT) == Metadata_1.EEClassOrCanonMTBits.EEClass ? EEClassOrCanonMT : throw new InvalidOperationException("not an EEClass"); - - public TargetPointer ParentMethodTable => MethodTableData.ParentMethodTable; - public ushort NumInterfaces => MethodTableData.NumInterfaces; - public ushort NumVirtuals => MethodTableData.NumVirtuals; - } internal struct EEClass_1 { - public Data.EEClass EEClassData { get; init; } + internal TargetPointer MethodTable { get; } + internal ushort NumMethods { get; } + internal ushort NumNonVirtualSlots { get; } + internal uint TypeDefTypeAttributes { get; } internal EEClass_1(Data.EEClass eeClassData) { - EEClassData = eeClassData; + MethodTable = eeClassData.MethodTable; + NumMethods = eeClassData.NumMethods; + NumNonVirtualSlots = eeClassData.NumNonVirtualSlots; + TypeDefTypeAttributes = eeClassData.DwAttrClass; } - - public TargetPointer MethodTable => EEClassData.MethodTable; - public ushort NumMethods => EEClassData.NumMethods; - public ushort NumNonVirtualSlots => EEClassData.NumNonVirtualSlots; - - public uint TypeDefTypeAttributes => EEClassData.DwAttrClass; } @@ -111,6 +129,7 @@ internal partial struct Metadata_1 : IMetadata private readonly Target _target; private readonly TargetPointer _freeObjectMethodTablePointer; + // FIXME: we mutate this dictionary - copies of the Metadata_1 struct share this instance private readonly Dictionary _methodTables = new(); internal static class Constants @@ -144,7 +163,7 @@ private UntrustedEEClass_1 GetUntrustedEEClassData(TargetPointer eeClassPointer) return new UntrustedEEClass_1(_target, eeClassPointer); } - public MethodTableHandle GetMethodTableData(TargetPointer methodTablePointer) + public MethodTableHandle GetMethodTableHandle(TargetPointer methodTablePointer) { // if we already trust that address, return a handle if (_methodTables.ContainsKey(methodTablePointer)) @@ -171,7 +190,7 @@ public MethodTableHandle GetMethodTableData(TargetPointer methodTablePointer) _ = _methodTables.TryAdd(methodTablePointer, trustedMethodTable); return new MethodTableHandle(methodTablePointer); } - if (!ValidateMethodTablePointer(in untrustedMethodTable)) + if (!ValidateMethodTablePointer(untrustedMethodTable)) { throw new ArgumentException("Invalid method table pointer"); } @@ -182,7 +201,7 @@ public MethodTableHandle GetMethodTableData(TargetPointer methodTablePointer) return new MethodTableHandle(methodTablePointer); } - private bool ValidateMethodTablePointer(in UntrustedMethodTable_1 umt) + private bool ValidateMethodTablePointer(UntrustedMethodTable_1 umt) { // FIXME: is methodTablePointer properly sign-extended from 32-bit targets? // FIXME2: do we need this? Data.MethodTable probably would throw if methodTablePointer is invalid @@ -209,7 +228,7 @@ private bool ValidateMethodTablePointer(in UntrustedMethodTable_1 umt) return true; } - private bool ValidateWithPossibleAV(in UntrustedMethodTable_1 methodTable) + private bool ValidateWithPossibleAV(UntrustedMethodTable_1 methodTable) { // For non-generic classes, we can rely on comparing // object->methodtable->class->methodtable @@ -226,26 +245,26 @@ private bool ValidateWithPossibleAV(in UntrustedMethodTable_1 methodTable) if (eeClassPtr != TargetPointer.Null) { UntrustedEEClass_1 eeClass = GetUntrustedEEClassData(eeClassPtr); - TargetPointer methodTablePtrFromClass = GetMethodTableWithPossibleAV(in eeClass); + TargetPointer methodTablePtrFromClass = GetMethodTableWithPossibleAV(eeClass); if (methodTable.Address == methodTablePtrFromClass) { return true; } - if (((IMethodTableFlags)methodTable).HasInstantiation || ((IMethodTableFlags)methodTable).IsArray) + if (methodTable.Flags.HasInstantiation || methodTable.Flags.IsArray) { UntrustedMethodTable_1 methodTableFromClass = GetUntrustedMethodTableData(methodTablePtrFromClass); - TargetPointer classFromMethodTable = GetClassWithPossibleAV(in methodTableFromClass); + TargetPointer classFromMethodTable = GetClassWithPossibleAV(methodTableFromClass); return classFromMethodTable == eeClassPtr; } } return false; } - private bool ValidateMethodTable(in UntrustedMethodTable_1 methodTable) + private bool ValidateMethodTable(UntrustedMethodTable_1 methodTable) { - if (!((IMethodTableFlags)methodTable).IsInterface && !((IMethodTableFlags)methodTable).IsString) + if (!methodTable.Flags.IsInterface && !methodTable.Flags.IsString) { - if (methodTable.BaseSize == 0 || !_target.IsAlignedToPointerSize(methodTable.BaseSize)) + if (methodTable.Flags.BaseSize == 0 || !_target.IsAlignedToPointerSize(methodTable.Flags.BaseSize)) { return false; } @@ -253,12 +272,11 @@ private bool ValidateMethodTable(in UntrustedMethodTable_1 methodTable) return true; } - internal static EEClassOrCanonMTBits GetEEClassOrCanonMTBits(TargetPointer eeClassOrCanonMTPtr) { return (EEClassOrCanonMTBits)(eeClassOrCanonMTPtr & (ulong)EEClassOrCanonMTBits.Mask); } - private TargetPointer GetClassWithPossibleAV(in UntrustedMethodTable_1 methodTable) + private TargetPointer GetClassWithPossibleAV(UntrustedMethodTable_1 methodTable) { TargetPointer eeClassOrCanonMT = methodTable.EEClassOrCanonMT; @@ -274,16 +292,13 @@ private TargetPointer GetClassWithPossibleAV(in UntrustedMethodTable_1 methodTab } } - private static TargetPointer GetMethodTableWithPossibleAV(in UntrustedEEClass_1 eeClass) - { - return eeClass.MethodTable; - } + private static TargetPointer GetMethodTableWithPossibleAV(UntrustedEEClass_1 eeClass) => eeClass.MethodTable; - public uint GetBaseSize(MethodTableHandle methodTableHandle) => ((IMethodTableFlags)_methodTables[methodTableHandle.Address]).BaseSize; + public uint GetBaseSize(MethodTableHandle methodTableHandle) => _methodTables[methodTableHandle.Address].Flags.BaseSize; private static uint GetComponentSize(MethodTable_1 methodTable) { - return ((IMethodTableFlags)methodTable).HasComponentSize ? ((IMethodTableFlags)methodTable).RawGetComponentSize() : 0u; + return methodTable.Flags.HasComponentSize ? methodTable.Flags.RawGetComponentSize() : 0u; } public uint GetComponentSize(MethodTableHandle methodTableHandle) => GetComponentSize(_methodTables[methodTableHandle.Address]); public TargetPointer GetClass(MethodTableHandle methodTableHandle) @@ -295,7 +310,7 @@ public TargetPointer GetClass(MethodTableHandle methodTableHandle) return methodTable.EEClassOrCanonMT; case EEClassOrCanonMTBits.CanonMT: TargetPointer canonMTPtr = new TargetPointer((ulong)methodTable.EEClassOrCanonMT & ~(ulong)Metadata_1.EEClassOrCanonMTBits.Mask); - MethodTableHandle canonMTHandle = GetMethodTableData(canonMTPtr); + MethodTableHandle canonMTHandle = GetMethodTableHandle(canonMTPtr); MethodTable_1 canonMT = _methodTables[canonMTHandle.Address]; return canonMT.EEClassOrCanonMT; // canonical method table EEClassOrCanonMT is always EEClass default: @@ -321,13 +336,13 @@ private EEClass_1 GetClassData(MethodTableHandle methodTableHandle) public bool IsFreeObjectMethodTable(MethodTableHandle methodTableHandle) => FreeObjectMethodTablePointer == methodTableHandle.Address; - public bool IsString(MethodTableHandle methodTableHandle) => ((IMethodTableFlags)_methodTables[methodTableHandle.Address]).IsString; - public bool ContainsPointers(MethodTableHandle methodTableHandle) => ((IMethodTableFlags)_methodTables[methodTableHandle.Address]).ContainsPointers; + public bool IsString(MethodTableHandle methodTableHandle) => _methodTables[methodTableHandle.Address].Flags.IsString; + public bool ContainsPointers(MethodTableHandle methodTableHandle) => _methodTables[methodTableHandle.Address].Flags.ContainsPointers; public uint GetTypeDefToken(MethodTableHandle methodTableHandle) { MethodTable_1 methodTable = _methodTables[methodTableHandle.Address]; - return (uint)(((IMethodTableFlags)methodTable).GetTypeDefRid() | ((int)TableIndex.TypeDef << 24)); + return (uint)(methodTable.Flags.GetTypeDefRid() | ((int)TableIndex.TypeDef << 24)); } public ushort GetNumMethods(MethodTableHandle methodTableHandle) @@ -363,29 +378,6 @@ public uint GetTypeDefTypeAttributes(MethodTableHandle methodTableHandle) return GetClassData(methodTableHandle).TypeDefTypeAttributes; } - public bool IsDynamicStatics(MethodTableHandle methodTableHandle) => ((IMethodTableFlags)_methodTables[methodTableHandle.Address]).GetFlag(WFLAGS2_ENUM.DynamicStatics) != 0; + public bool IsDynamicStatics(MethodTableHandle methodTableHandle) => _methodTables[methodTableHandle.Address].Flags.GetFlag(WFLAGS2_ENUM.DynamicStatics) != 0; - [Flags] - internal enum MethodTableAuxiliaryDataFlags : uint - { - Initialized = 0x0001, - HasCheckedCanCompareBitsOrUseFastGetHashCode = 0x0002, // Whether we have checked the overridden Equals or GetHashCode - CanCompareBitsOrUseFastGetHashCode = 0x0004, // Is any field type or sub field type overridden Equals or GetHashCode - - HasApproxParent = 0x0010, - // enum_unused = 0x0020, - IsNotFullyLoaded = 0x0040, - DependenciesLoaded = 0x0080, // class and all dependencies loaded up to CLASS_LOADED_BUT_NOT_VERIFIED - - IsInitError = 0x0100, - IsStaticDataAllocated = 0x0200, - // unum_unused = 0x0400, - IsTlsIndexAllocated = 0x0800, - MayHaveOpenInterfaceInInterfaceMap = 0x1000, - // enum_unused = 0x2000, - - // ifdef _DEBUG - DEBUG_ParentMethodTablePointerValid = 0x4000, - DEBUG_HasInjectedInterfaceDuplicates = 0x8000, - } } diff --git a/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs b/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs index dfc84e51e1979b..2c58bcd722a790 100644 --- a/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs +++ b/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs @@ -2,7 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; -using System.ComponentModel; using System.Runtime.InteropServices; using System.Runtime.InteropServices.Marshalling; @@ -89,7 +88,7 @@ public unsafe int GetMethodTableData(ulong mt, DacpMethodTableData* data) try { Contracts.IMetadata contract = _target.Contracts.Metadata; - Contracts.MethodTableHandle methodTable = contract.GetMethodTableData(mt); + Contracts.MethodTableHandle methodTable = contract.GetMethodTableHandle(mt); DacpMethodTableData result = default; result.baseSize = contract.GetBaseSize(methodTable); From 78830ea31e90c474a46d83c51fd47229ccd73722 Mon Sep 17 00:00:00 2001 From: Aleksey Kliger Date: Tue, 18 Jun 2024 13:21:23 -0400 Subject: [PATCH 24/62] fixup --- src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs b/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs index 2c58bcd722a790..09d40837f4beef 100644 --- a/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs +++ b/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs @@ -114,6 +114,7 @@ public unsafe int GetMethodTableData(ulong mt, DacpMethodTableData* data) result.bIsShared = 0; result.bIsDynamic = contract.IsDynamicStatics(methodTable) ? 1 : 0; } + *data = result; return HResults.S_OK; } catch (Exception ex) From 32665b21780fbb54834457d9f1b918b1dc5ea9cf Mon Sep 17 00:00:00 2001 From: Aleksey Kliger Date: Tue, 18 Jun 2024 15:16:19 -0400 Subject: [PATCH 25/62] [dac] Return canonical MethodTable instead of EEClass Instead of storing the EEClass pointerin DacpMethodTableData, store the canonical method table instead. Correspondingly, update GetMethodTableForEEClass to expect a canonical method table pointer instead of an EEClass Also update cDAC to do likewise --- src/coreclr/debug/daccess/dacimpl.h | 1 + src/coreclr/debug/daccess/request.cpp | 48 +++++++++++++++---- .../cdacreader/src/Contracts/Metadata.cs | 2 +- .../cdacreader/src/Contracts/Metadata_1.cs | 30 ++++++------ .../cdacreader/src/Legacy/SOSDacImpl.cs | 23 +++++++-- 5 files changed, 76 insertions(+), 28 deletions(-) diff --git a/src/coreclr/debug/daccess/dacimpl.h b/src/coreclr/debug/daccess/dacimpl.h index 0e4c60ad053f34..5ef6cb61bda309 100644 --- a/src/coreclr/debug/daccess/dacimpl.h +++ b/src/coreclr/debug/daccess/dacimpl.h @@ -1232,6 +1232,7 @@ class ClrDataAccess HRESULT GetThreadDataImpl(CLRDATA_ADDRESS threadAddr, struct DacpThreadData *threadData); HRESULT GetThreadStoreDataImpl(struct DacpThreadStoreData *data); HRESULT GetMethodTableDataImpl(CLRDATA_ADDRESS mt, struct DacpMethodTableData *data); + HRESULT GetMethodTableForEEClassImpl (CLRDATA_ADDRESS eeClassReallyMT, CLRDATA_ADDRESS *value); BOOL IsExceptionFromManagedCode(EXCEPTION_RECORD * pExceptionRecord); #ifndef TARGET_UNIX diff --git a/src/coreclr/debug/daccess/request.cpp b/src/coreclr/debug/daccess/request.cpp index b377af3c7a3e89..15732bbf8bfc4e 100644 --- a/src/coreclr/debug/daccess/request.cpp +++ b/src/coreclr/debug/daccess/request.cpp @@ -1850,7 +1850,8 @@ ClrDataAccess::GetMethodTableDataImpl(CLRDATA_ADDRESS mt, struct DacpMethodTable if(!bIsFree) { MTData->Module = HOST_CDADDR(pMT->GetModule()); - MTData->Class = HOST_CDADDR(pMT->GetClass()); + // Note: DacpMethodTableData::Class is really a pointer to the canonical method table + MTData->Class = HOST_CDADDR(pMT->GetClass()->GetMethodTable()); MTData->ParentMethodTable = HOST_CDADDR(pMT->GetParentMethodTable());; MTData->wNumInterfaces = (WORD)pMT->GetNumInterfaces(); MTData->wNumMethods = pMT->GetNumMethods(); @@ -2109,27 +2110,56 @@ ClrDataAccess::GetMethodTableTransparencyData(CLRDATA_ADDRESS mt, struct DacpMet } HRESULT -ClrDataAccess::GetMethodTableForEEClass(CLRDATA_ADDRESS eeClass, CLRDATA_ADDRESS *value) +ClrDataAccess::GetMethodTableForEEClass(CLRDATA_ADDRESS eeClassReallyCanonMT, CLRDATA_ADDRESS *value) { - if (eeClass == 0 || value == NULL) + if (eeClassReallyCanonMT == 0 || value == NULL) return E_INVALIDARG; SOSDacEnter(); - - PTR_EEClass pClass = PTR_EEClass(TO_TADDR(eeClass)); - if (!DacValidateEEClass(pClass)) + if (m_cdacSos != NULL) { - hr = E_INVALIDARG; + // Try the cDAC first - it will return E_NOTIMPL if it doesn't support this method yet. Fall back to the DAC. + hr = m_cdacSos->GetMethodTableForEEClass(eeClassReallyCanonMT, value); + if (FAILED(hr)) + { + hr = GetMethodTableForEEClassImpl(eeClassReallyCanonMT, value); + } +#ifdef _DEBUG + else + { + // Assert that the data is the same as what we get from the DAC. + CLRDATA_ADDRESS valueLocal; + HRESULT hrLocal = GetMethodTableForEEClassImpl(eeClassReallyCanonMT, &valueLocal); + _ASSERTE(hr == hrLocal); + _ASSERTE(*value == valueLocal); + } +#endif } else { - *value = HOST_CDADDR(pClass->GetMethodTable()); + hr = GetMethodTableForEEClassImpl (eeClassReallyCanonMT, value); } - SOSDacLeave(); return hr; } +HRESULT +ClrDataAccess::GetMethodTableForEEClassImpl(CLRDATA_ADDRESS eeClassReallyCanonMT, CLRDATA_ADDRESS *value) +{ + PTR_MethodTable pCanonMT = PTR_MethodTable(TO_TADDR(eeClassReallyCanonMT)); + BOOL bIsFree; + if (!DacValidateMethodTable(pCanonMT, bIsFree)) + { + return E_INVALIDARG; + } + else + { + *value = HOST_CDADDR(pCanonMT); + } + + return S_OK; +} + HRESULT ClrDataAccess::GetFrameName(CLRDATA_ADDRESS vtable, unsigned int count, _Inout_updates_z_(count) WCHAR *frameName, unsigned int *pNeeded) { diff --git a/src/native/managed/cdacreader/src/Contracts/Metadata.cs b/src/native/managed/cdacreader/src/Contracts/Metadata.cs index 4a0a98b19f5b3e..db9e4267c68a6e 100644 --- a/src/native/managed/cdacreader/src/Contracts/Metadata.cs +++ b/src/native/managed/cdacreader/src/Contracts/Metadata.cs @@ -34,7 +34,7 @@ static IContract IContract.Create(Target target, int version) public virtual MethodTableHandle GetMethodTableHandle(TargetPointer targetPointer) => throw new NotImplementedException(); public virtual TargetPointer GetModule(MethodTableHandle methodTable) => throw new NotImplementedException(); - public virtual TargetPointer GetClass(MethodTableHandle methodTable) => throw new NotImplementedException(); + public virtual TargetPointer GetCanonicalMethodTable(MethodTableHandle methodTable) => throw new NotImplementedException(); public virtual TargetPointer GetParentMethodTable(MethodTableHandle methodTable) => throw new NotImplementedException(); public virtual uint GetBaseSize(MethodTableHandle methodTable) => throw new NotImplementedException(); diff --git a/src/native/managed/cdacreader/src/Contracts/Metadata_1.cs b/src/native/managed/cdacreader/src/Contracts/Metadata_1.cs index fc3bbda1216f7e..24e1fb37adae7c 100644 --- a/src/native/managed/cdacreader/src/Contracts/Metadata_1.cs +++ b/src/native/managed/cdacreader/src/Contracts/Metadata_1.cs @@ -301,7 +301,21 @@ private static uint GetComponentSize(MethodTable_1 methodTable) return methodTable.Flags.HasComponentSize ? methodTable.Flags.RawGetComponentSize() : 0u; } public uint GetComponentSize(MethodTableHandle methodTableHandle) => GetComponentSize(_methodTables[methodTableHandle.Address]); - public TargetPointer GetClass(MethodTableHandle methodTableHandle) + + // only called on trusted method tables, so we always trust the resulting EEClass + private EEClass_1 GetClassData(MethodTableHandle methodTableHandle) + { + TargetPointer clsPtr = GetClass(methodTableHandle); + // Check if we cached it already + if (_target.ProcessedData.TryGet(clsPtr, out Data.EEClass? eeClassData)) + { + return new EEClass_1(eeClassData); + } + eeClassData = _target.ProcessedData.GetOrAdd(clsPtr); + return new EEClass_1(eeClassData); + } + + private TargetPointer GetClass(MethodTableHandle methodTableHandle) { MethodTable_1 methodTable = _methodTables[methodTableHandle.Address]; switch (GetEEClassOrCanonMTBits(methodTable.EEClassOrCanonMT)) @@ -317,23 +331,11 @@ public TargetPointer GetClass(MethodTableHandle methodTableHandle) throw new InvalidOperationException(); } } + public TargetPointer GetCanonicalMethodTable(MethodTableHandle methodTableHandle) => GetClassData(methodTableHandle).MethodTable; public TargetPointer GetModule(MethodTableHandle methodTableHandle) => _methodTables[methodTableHandle.Address].Module; public TargetPointer GetParentMethodTable(MethodTableHandle methodTableHandle) => _methodTables[methodTableHandle.Address].ParentMethodTable; - // only called on trusted method tables, so we always trust the resulting EEClass - private EEClass_1 GetClassData(MethodTableHandle methodTableHandle) - { - TargetPointer clsPtr = GetClass(methodTableHandle); - // Check if we cached it already - if (_target.ProcessedData.TryGet(clsPtr, out Data.EEClass? eeClassData)) - { - return new EEClass_1(eeClassData); - } - eeClassData = _target.ProcessedData.GetOrAdd(clsPtr); - return new EEClass_1(eeClassData); - } - public bool IsFreeObjectMethodTable(MethodTableHandle methodTableHandle) => FreeObjectMethodTablePointer == methodTableHandle.Address; public bool IsString(MethodTableHandle methodTableHandle) => _methodTables[methodTableHandle.Address].Flags.IsString; diff --git a/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs b/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs index 7c3c78259b2763..0abfca259724f9 100644 --- a/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs +++ b/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; +using System.Diagnostics.Contracts; using System.Runtime.InteropServices; using System.Runtime.InteropServices.Marshalling; @@ -100,9 +101,8 @@ public unsafe int GetMethodTableData(ulong mt, DacpMethodTableData* data) if (!isFreeObjectMT) { result.module = contract.GetModule(methodTable); - // TODO[cdac]: it looks like this is only used in output. Can we just return the canonical MT pointer here - // instead and avoid exposing the EEClass concept from the contract? - result.@class = contract.GetClass(methodTable); + // Note: really the canonical method table, not the EEClass, which we don't expose + result.@class = contract.GetCanonicalMethodTable(methodTable); result.parentMethodTable = contract.GetParentMethodTable(methodTable); result.wNumInterfaces = contract.GetNumInterfaces(methodTable); result.wNumMethods = contract.GetNumMethods(methodTable); @@ -123,7 +123,22 @@ public unsafe int GetMethodTableData(ulong mt, DacpMethodTableData* data) } } public unsafe int GetMethodTableFieldData(ulong mt, void* data) => HResults.E_NOTIMPL; - public unsafe int GetMethodTableForEEClass(ulong eeClass, ulong* value) => HResults.E_NOTIMPL; + public unsafe int GetMethodTableForEEClass(ulong eeClassReallyCanonMT, ulong* value) + { + if (eeClassReallyCanonMT == 0 || value == null) + return HResults.E_INVALIDARG; + try + { + Contracts.IMetadata contract = _target.Contracts.Metadata; + Contracts.MethodTableHandle methodTableHandle = contract.GetMethodTableHandle(eeClassReallyCanonMT); + *value = methodTableHandle.Address; + return HResults.S_OK; + } + catch (Exception ex) + { + return ex.HResult; + } + } public unsafe int GetMethodTableName(ulong mt, uint count, char* mtName, uint* pNeeded) => HResults.E_NOTIMPL; public unsafe int GetMethodTableSlot(ulong mt, uint slot, ulong* value) => HResults.E_NOTIMPL; public unsafe int GetMethodTableTransparencyData(ulong mt, void* data) => HResults.E_NOTIMPL; From acf8436cbfdbd59efdb90189c889661a6895606b Mon Sep 17 00:00:00 2001 From: Aleksey Kliger Date: Tue, 18 Jun 2024 15:38:30 -0400 Subject: [PATCH 26/62] Delete unreferenced MethodTable flags --- .../Contracts/Metadata_1.MethodTableFlags.cs | 136 +----------------- 1 file changed, 4 insertions(+), 132 deletions(-) diff --git a/src/native/managed/cdacreader/src/Contracts/Metadata_1.MethodTableFlags.cs b/src/native/managed/cdacreader/src/Contracts/Metadata_1.MethodTableFlags.cs index 3c9c8c3928e7e5..c7e31785a4dd73 100644 --- a/src/native/managed/cdacreader/src/Contracts/Metadata_1.MethodTableFlags.cs +++ b/src/native/managed/cdacreader/src/Contracts/Metadata_1.MethodTableFlags.cs @@ -10,157 +10,29 @@ internal partial struct Metadata_1 [Flags] internal enum WFLAGS_LOW : uint { - // We are overloading the low 2 bytes of m_dwFlags to be a component size for Strings - // and Arrays and some set of flags which we can be assured are of a specified state - // for Strings / Arrays, currently these will be a bunch of generics flags which don't - // apply to Strings / Arrays. - - UNUSED_ComponentSize_1 = 0x00000001, - // GC depends on this bit - HasCriticalFinalizer = 0x00000002, // finalizer must be run on Appdomain Unload - - GenericsMask = 0x00000030, GenericsMask_NonGeneric = 0x00000000, // no instantiation - GenericsMask_GenericInst = 0x00000010, // regular instantiation, e.g. List - GenericsMask_SharedInst = 0x00000020, // shared instantiation, e.g. List<__Canon> or List> - GenericsMask_TypicalInst = 0x00000030, // the type instantiated at its formal parameters, e.g. List - - HasVariance = 0x00000100, // This is an instantiated type some of whose type parameters are co- or contra-variant - - HasDefaultCtor = 0x00000200, - HasPreciseInitCctors = 0x00000400, // Do we need to run class constructors at allocation time? (Not perf important, could be moved to EEClass - - // if defined(FEATURE_HFA) - IsHFA = 0x00000800, // This type is an HFA (Homogeneous Floating-point Aggregate) - - // if defined(UNIX_AMD64_ABI) - IsRegStructPassed = 0x00000800, // This type is a System V register passed struct. - - IsByRefLike = 0x00001000, - - HasBoxedRegularStatics = 0x00002000, - HasBoxedThreadStatics = 0x00004000, - - // In a perfect world we would fill these flags using other flags that we already have - // which have a constant value for something which has a component size. - UNUSED_ComponentSize_7 = 0x00008000, - - // IMPORTANT! IMPORTANT! IMPORTANT! - // - // As you change the flags in WFLAGS_LOW_ENUM you also need to change this - // to be up to date to reflect the default values of those flags for the - // case where this MethodTable is for a String or Array - StringArrayValues = // SET_FALSE(enum_flag_HasCriticalFinalizer) | - // SET_FALSE(enum_flag_HasBoxedRegularStatics) | - // SET_FALSE(enum_flag_HasBoxedThreadStatics) | - GenericsMask_NonGeneric | - // SET_FALSE(enum_flag_HasVariance) | - // SET_FALSE(enum_flag_HasDefaultCtor) | - // SET_FALSE(enum_flag_HasPreciseInitCctors), - 0, + + StringArrayValues = + GenericsMask_NonGeneric | + 0, } [Flags] internal enum WFLAGS_HIGH : uint { - // DO NOT use flags that have bits set in the low 2 bytes. - // These flags are DWORD sized so that our atomic masking - // operations can operate on the entire 4-byte aligned DWORD - // instead of the logical non-aligned WORD of flags. The - // low WORD of flags is reserved for the component size. - - // The following bits describe mutually exclusive locations of the type - // in the type hierarchy. Category_Mask = 0x000F0000, - - Category_Class = 0x00000000, - Category_Unused_1 = 0x00010000, - Category_Unused_2 = 0x00020000, - Category_Unused_3 = 0x00030000, - - Category_ValueType = 0x00040000, - Category_ValueType_Mask = 0x000C0000, - Category_Nullable = 0x00050000, // sub-category of ValueType - Category_PrimitiveValueType = 0x00060000, // sub-category of ValueType, Enum or primitive value type - Category_TruePrimitive = 0x00070000, // sub-category of ValueType, Primitive (ELEMENT_TYPE_I, etc.) - Category_Array = 0x00080000, Category_Array_Mask = 0x000C0000, - // enum_flag_Category_IfArrayThenUnused = 0x00010000, // sub-category of Array - Category_IfArrayThenSzArray = 0x00020000, // sub-category of Array - Category_Interface = 0x000C0000, - Category_Unused_4 = 0x000D0000, - Category_Unused_5 = 0x000E0000, - Category_Unused_6 = 0x000F0000, - - Category_ElementTypeMask = 0x000E0000, // bits that matter for element type mask - - HasFinalizer = 0x00100000, // instances require finalization. GC depends on this bit. - Collectible = 0x00200000, // GC depends on this bit. - ICastable = 0x00400000, // class implements ICastable interface - - // ifdef FEATURE_64BIT_ALIGNMENT - eRequiresAlign8 = 0x00800000, // Type requires 8-byte alignment (only set on platforms that require this and don't get it implicitly) - ContainsPointers = 0x01000000, // Contains object references - HasTypeEquivalence = 0x02000000, // can be equivalent to another type - IsTrackedReferenceWithFinalizer = 0x04000000, - // unused = 0x08000000, - - IDynamicInterfaceCastable = 0x10000000, // class implements IDynamicInterfaceCastable interface - ContainsGenericVariables = 0x20000000, // we cache this flag to help detect these efficiently and - // to detect this condition when restoring - ComObject = 0x40000000, // class is a com object HasComponentSize = 0x80000000, // This is set if component size is used for flags. - - // Types that require non-trivial interface cast have this bit set in the category - NonTrivialInterfaceCast = Category_Array - | ComObject - | ICastable - | IDynamicInterfaceCastable - | Category_ValueType } [Flags] internal enum WFLAGS2_ENUM : uint { - HasPerInstInfo = 0x0001, DynamicStatics = 0x0002, - HasDispatchMapSlot = 0x0004, - - // wflags2_unused_2 = 0x0008, - HasModuleDependencies = 0x0010, - IsIntrinsicType = 0x0020, - HasCctor = 0x0040, - HasVirtualStaticMethods = 0x0080, - - TokenMask = 0xFFFFFF00, - } - - [Flags] - internal enum MethodTableAuxiliaryDataFlags : uint - { - Initialized = 0x0001, - HasCheckedCanCompareBitsOrUseFastGetHashCode = 0x0002, // Whether we have checked the overridden Equals or GetHashCode - CanCompareBitsOrUseFastGetHashCode = 0x0004, // Is any field type or sub field type overridden Equals or GetHashCode - - HasApproxParent = 0x0010, - // enum_unused = 0x0020, - IsNotFullyLoaded = 0x0040, - DependenciesLoaded = 0x0080, // class and all dependencies loaded up to CLASS_LOADED_BUT_NOT_VERIFIED - - IsInitError = 0x0100, - IsStaticDataAllocated = 0x0200, - // unum_unused = 0x0400, - IsTlsIndexAllocated = 0x0800, - MayHaveOpenInterfaceInInterfaceMap = 0x1000, - // enum_unused = 0x2000, - - // ifdef _DEBUG - DEBUG_ParentMethodTablePointerValid = 0x4000, - DEBUG_HasInjectedInterfaceDuplicates = 0x8000, } internal struct MethodTableFlags From f246c863c04e044b7cc03368a55448abb5425a08 Mon Sep 17 00:00:00 2001 From: Aleksey Kliger Date: Wed, 19 Jun 2024 13:19:34 -0400 Subject: [PATCH 27/62] add Metadata contract doc; fixups --- docs/design/datacontracts/Metadata.md | 286 ++++++++++++++++++ .../cdacreader/src/Contracts/Metadata.cs | 12 +- .../cdacreader/src/Contracts/Metadata_1.cs | 2 +- 3 files changed, 298 insertions(+), 2 deletions(-) create mode 100644 docs/design/datacontracts/Metadata.md diff --git a/docs/design/datacontracts/Metadata.md b/docs/design/datacontracts/Metadata.md new file mode 100644 index 00000000000000..6349ca074e99ba --- /dev/null +++ b/docs/design/datacontracts/Metadata.md @@ -0,0 +1,286 @@ +# Contract Metadata + +This contract is for exploring the properties of the metadata of values on the heap or on the stack in a .NET process. + +## APIs of contract + +A `MethodTable` is the runtime representation of the type information about a value. Given a `TargetPointer` address, the `Metadata` contract provides a `MethodTableHandle` for querying the `MethodTable`. + +``` csharp +struct MethodTableHandle +{ + // no public properties or constructors + + internal TargetPointer Address { get; } +} + +``` csharp + #region MethodTable inspection APIs + public virtual MethodTableHandle GetMethodTableHandle(TargetPointer targetPointer) => throw new NotImplementedException(); + + public virtual TargetPointer GetModule(MethodTableHandle methodTable) => throw new NotImplementedException(); + // A canonical method table is either the MethodTable itself, or in the case of a generic instantiation, it is the + // MethodTable of the prototypical instance. + public virtual TargetPointer GetCanonicalMethodTable(MethodTableHandle methodTable) => throw new NotImplementedException(); + public virtual TargetPointer GetParentMethodTable(MethodTableHandle methodTable) => throw new NotImplementedException(); + + public virtual uint GetBaseSize(MethodTableHandle methodTable) => throw new NotImplementedException(); + // The component size is only available for strings and arrays. It is the size of the element type of the array, or the size of an ECMA 335 character (2 bytes) + public virtual uint GetComponentSize(MethodTableHandle methodTable) => throw new NotImplementedException(); + + // True if the MethodTable is the sentinel value associated with unallocated space in the managed heap + public virtual bool IsFreeObjectMethodTable(MethodTableHandle methodTable) => throw new NotImplementedException(); + public virtual bool IsString(MethodTableHandle methodTable) => throw new NotImplementedException(); + // True if the MethodTable represents a type that contains managed references + public virtual bool ContainsPointers(MethodTableHandle methodTable) => throw new NotImplementedException(); + public virtual bool IsDynamicStatics(MethodTableHandle methodTable) => throw new NotImplementedException(); + public virtual ushort GetNumMethods(MethodTableHandle methodTable) => throw new NotImplementedException(); + public virtual ushort GetNumInterfaces(MethodTableHandle methodTable) => throw new NotImplementedException(); + public virtual ushort GetNumVirtuals(MethodTableHandle methodTable) => throw new NotImplementedException(); + public virtual ushort GetNumVtableSlots(MethodTableHandle methodTable) => throw new NotImplementedException(); + + // Returns an ECMA-335 TypeDef table token for this type, or for its generic type definition if it is a generic instantiation + public virtual uint GetTypeDefToken(MethodTableHandle methodTable) => throw new NotImplementedException(); + // Returns the ECMA 335 TypeDef table Flags value (a bitmask of TypeAttributes) for this type, + // or for its generic type definition if it is a generic instantiation + public virtual uint GetTypeDefTypeAttributes(MethodTableHandle methodTable) => throw new NotImplementedException(); + #endregion MethodTable inspection APIs +``` + +## Version 1 + +The `MethodTable` inspection APIs are implemented in terms of the following flags on the runtime `MethodTable` structure: + +``` csharp +internal partial struct Metadata_1 +{ + [Flags] + internal enum WFLAGS_LOW : uint + { + GenericsMask = 0x00000030, + GenericsMask_NonGeneric = 0x00000000, // no instantiation + + StringArrayValues = + GenericsMask_NonGeneric | + 0, + } + + [Flags] + internal enum WFLAGS_HIGH : uint + { + Category_Mask = 0x000F0000, + Category_Array = 0x00080000, + Category_Array_Mask = 0x000C0000, + Category_Interface = 0x000C0000, + ContainsPointers = 0x01000000, // Contains object references + HasComponentSize = 0x80000000, // This is set if component size is used for flags. + } + + [Flags] + internal enum WFLAGS2_ENUM : uint + { + DynamicStatics = 0x0002, + } + + internal struct MethodTableFlags + { + public uint DwFlags { get; init; } + public uint DwFlags2 { get; init; } + public uint BaseSize { get; init; } + + private WFLAGS_HIGH FlagsHigh => (WFLAGS_HIGH)DwFlags; + private WFLAGS_LOW FlagsLow => (WFLAGS_LOW)DwFlags; + public int GetTypeDefRid() => (int)(DwFlags2 >> Constants.MethodTableDwFlags2TypeDefRidShift); + + public WFLAGS_LOW GetFlag(WFLAGS_LOW mask) => throw new NotImplementedException("TODO"); + public WFLAGS_HIGH GetFlag(WFLAGS_HIGH mask) => FlagsHigh & mask; + + public WFLAGS2_ENUM GetFlag(WFLAGS2_ENUM mask) => (WFLAGS2_ENUM)DwFlags2 & mask; + public bool IsInterface => GetFlag(WFLAGS_HIGH.Category_Mask) == WFLAGS_HIGH.Category_Interface; + public bool IsString => HasComponentSize && !IsArray && RawGetComponentSize() == 2; + + public bool HasComponentSize => GetFlag(WFLAGS_HIGH.HasComponentSize) != 0; + + public bool IsArray => GetFlag(WFLAGS_HIGH.Category_Array_Mask) == WFLAGS_HIGH.Category_Array; + + public bool IsStringOrArray => HasComponentSize; + public ushort RawGetComponentSize() => (ushort)(DwFlags >> 16); + + public bool TestFlagWithMask(WFLAGS_LOW mask, WFLAGS_LOW flag) + { + if (IsStringOrArray) + { + return (WFLAGS_LOW.StringArrayValues & mask) == flag; + } + else + { + return (FlagsLow & mask) == flag; + } + } + + public bool TestFlagWithMask(WFLAGS2_ENUM mask, WFLAGS2_ENUM flag) + { + return ((WFLAGS2_ENUM)DwFlags2 & mask) == flag; + } + + public bool HasInstantiation => !TestFlagWithMask(WFLAGS_LOW.GenericsMask, WFLAGS_LOW.GenericsMask_NonGeneric); + + public bool ContainsPointers => GetFlag(WFLAGS_HIGH.ContainsPointers) != 0; + } + + internal static class Constants + { + internal const int MethodTableDwFlags2TypeDefRidShift = 8; + } + + [Flags] + internal enum EEClassOrCanonMTBits + { + EEClass = 0, + CanonMT = 1, + Mask = 1, + } +``` + +Internally the contract has structs `MethodTable_1` and `EEClass_1` + +```csharp +internal struct MethodTable_1 +{ + internal Metadata_1.MethodTableFlags Flags { get; } + internal ushort NumInterfaces { get; } + internal ushort NumVirtuals { get; } + internal TargetPointer ParentMethodTable { get; } + internal TargetPointer Module { get; } + internal TargetPointer EEClassOrCanonMT { get; } + internal MethodTable_1(Data.MethodTable data) + { + Flags = new Metadata_1.MethodTableFlags + { + DwFlags = data.DwFlags, + DwFlags2 = data.DwFlags2, + BaseSize = data.BaseSize, + }; + NumInterfaces = data.NumInterfaces; + NumVirtuals = data.NumVirtuals; + EEClassOrCanonMT = data.EEClassOrCanonMT; + Module = data.Module; + ParentMethodTable = data.ParentMethodTable; + } +} + +internal struct EEClass_1 +{ + internal TargetPointer MethodTable { get; } + internal ushort NumMethods { get; } + internal ushort NumNonVirtualSlots { get; } + internal uint TypeDefTypeAttributes { get; } + internal EEClass_1(Data.EEClass eeClassData) + { + MethodTable = eeClassData.MethodTable; + NumMethods = eeClassData.NumMethods; + NumNonVirtualSlots = eeClassData.NumNonVirtualSlots; + TypeDefTypeAttributes = eeClassData.DwAttrClass; + } +} +``` + +```csharp + private readonly Dictionary _methodTables; + private readonly TargetPointer _freeObjectMethodTablePointer; + + internal TargetPointer FreeObjectMethodTablePointer => _freeObjectMethodTablePointer; + + public MethodTableHandle GetMethodTableHandle(TargetPointer methodTablePointer) + { + ... + } + + internal static EEClassOrCanonMTBits GetEEClassOrCanonMTBits(TargetPointer eeClassOrCanonMTPtr) + { + return (EEClassOrCanonMTBits)(eeClassOrCanonMTPtr & (ulong)EEClassOrCanonMTBits.Mask); + } + + public uint GetBaseSize(MethodTableHandle methodTableHandle) => _methodTables[methodTableHandle.Address].Flags.BaseSize; + + public uint GetComponentSize(MethodTableHandle methodTableHandle) => GetComponentSize(_methodTables[methodTableHandle.Address]); + + private EEClass_1 GetClassData(MethodTableHandle methodTableHandle) + { + TargetPointer clsPtr = GetClass(methodTableHandle); + // Check if we cached it already + if (_target.ProcessedData.TryGet(clsPtr, out Data.EEClass? eeClassData)) + { + return new EEClass_1(eeClassData); + } + eeClassData = _target.ProcessedData.GetOrAdd(clsPtr); + return new EEClass_1(eeClassData); + } + + private TargetPointer GetClass(MethodTableHandle methodTableHandle) + { + MethodTable_1 methodTable = _methodTables[methodTableHandle.Address]; + switch (GetEEClassOrCanonMTBits(methodTable.EEClassOrCanonMT)) + { + case EEClassOrCanonMTBits.EEClass: + return methodTable.EEClassOrCanonMT; + case EEClassOrCanonMTBits.CanonMT: + TargetPointer canonMTPtr = new TargetPointer((ulong)methodTable.EEClassOrCanonMT & ~(ulong)Metadata_1.EEClassOrCanonMTBits.Mask); + MethodTableHandle canonMTHandle = GetMethodTableHandle(canonMTPtr); + MethodTable_1 canonMT = _methodTables[canonMTHandle.Address]; + return canonMT.EEClassOrCanonMT; // canonical method table EEClassOrCanonMT is always EEClass + default: + throw new InvalidOperationException(); + } + } + public TargetPointer GetCanonicalMethodTable(MethodTableHandle methodTableHandle) => GetClassData(methodTableHandle).MethodTable; + + public TargetPointer GetModule(MethodTableHandle methodTableHandle) => _methodTables[methodTableHandle.Address].Module; + public TargetPointer GetParentMethodTable(MethodTableHandle methodTableHandle) => _methodTables[methodTableHandle.Address].ParentMethodTable; + + public bool IsFreeObjectMethodTable(MethodTableHandle methodTableHandle) => FreeObjectMethodTablePointer == methodTableHandle.Address; + + public bool IsString(MethodTableHandle methodTableHandle) => _methodTables[methodTableHandle.Address].Flags.IsString; + public bool ContainsPointers(MethodTableHandle methodTableHandle) => _methodTables[methodTableHandle.Address].Flags.ContainsPointers; + + public uint GetTypeDefToken(MethodTableHandle methodTableHandle) + { + MethodTable_1 methodTable = _methodTables[methodTableHandle.Address]; + return (uint)(methodTable.Flags.GetTypeDefRid() | ((int)TableIndex.TypeDef << 24)); + } + + public ushort GetNumMethods(MethodTableHandle methodTableHandle) + { + EEClass_1 cls = GetClassData(methodTableHandle); + return cls.NumMethods; + } + + public ushort GetNumInterfaces(MethodTableHandle methodTableHandle) => _methodTables[methodTableHandle.Address].NumInterfaces; + + public ushort GetNumVirtuals(MethodTableHandle methodTableHandle) => _methodTables[methodTableHandle.Address].NumVirtuals; + private ushort GetNumNonVirtualSlots(MethodTableHandle methodTableHandle) + { + MethodTable_1 methodTable = _methodTables[methodTableHandle.Address]; + TargetPointer eeClassOrCanonMT = methodTable.EEClassOrCanonMT; + if (GetEEClassOrCanonMTBits(eeClassOrCanonMT) == EEClassOrCanonMTBits.EEClass) + { + return GetClassData(methodTableHandle).NumNonVirtualSlots; + } + else + { + return 0; + } + } + + public ushort GetNumVtableSlots(MethodTableHandle methodTableHandle) + { + return checked((ushort)(GetNumVirtuals(methodTableHandle) + GetNumNonVirtualSlots(methodTableHandle))); + } + + public uint GetTypeDefTypeAttributes(MethodTableHandle methodTableHandle) + { + return GetClassData(methodTableHandle).TypeDefTypeAttributes; + } + + public bool IsDynamicStatics(MethodTableHandle methodTableHandle) => _methodTables[methodTableHandle.Address].Flags.GetFlag(WFLAGS2_ENUM.DynamicStatics) != 0; +``` diff --git a/src/native/managed/cdacreader/src/Contracts/Metadata.cs b/src/native/managed/cdacreader/src/Contracts/Metadata.cs index db9e4267c68a6e..0a64e4b191072f 100644 --- a/src/native/managed/cdacreader/src/Contracts/Metadata.cs +++ b/src/native/managed/cdacreader/src/Contracts/Metadata.cs @@ -31,26 +31,36 @@ static IContract IContract.Create(Target target, int version) }; } + #region MethodTable inspection APIs public virtual MethodTableHandle GetMethodTableHandle(TargetPointer targetPointer) => throw new NotImplementedException(); public virtual TargetPointer GetModule(MethodTableHandle methodTable) => throw new NotImplementedException(); + // A canonical method table is either the MethodTable itself, or in the case of a generic instantiation, it is the + // MethodTable of the prototypical instance. public virtual TargetPointer GetCanonicalMethodTable(MethodTableHandle methodTable) => throw new NotImplementedException(); public virtual TargetPointer GetParentMethodTable(MethodTableHandle methodTable) => throw new NotImplementedException(); public virtual uint GetBaseSize(MethodTableHandle methodTable) => throw new NotImplementedException(); + // The component size is only available for strings and arrays. It is the size of the element type of the array, or the size of an ECMA 335 character (2 bytes) public virtual uint GetComponentSize(MethodTableHandle methodTable) => throw new NotImplementedException(); + // True if the MethodTable is the sentinel value associated with unallocated space in the managed heap public virtual bool IsFreeObjectMethodTable(MethodTableHandle methodTable) => throw new NotImplementedException(); public virtual bool IsString(MethodTableHandle methodTable) => throw new NotImplementedException(); + // True if the MethodTable represents a type that contains managed references public virtual bool ContainsPointers(MethodTableHandle methodTable) => throw new NotImplementedException(); public virtual bool IsDynamicStatics(MethodTableHandle methodTable) => throw new NotImplementedException(); - public virtual uint GetTypeDefToken(MethodTableHandle methodTable) => throw new NotImplementedException(); public virtual ushort GetNumMethods(MethodTableHandle methodTable) => throw new NotImplementedException(); public virtual ushort GetNumInterfaces(MethodTableHandle methodTable) => throw new NotImplementedException(); public virtual ushort GetNumVirtuals(MethodTableHandle methodTable) => throw new NotImplementedException(); public virtual ushort GetNumVtableSlots(MethodTableHandle methodTable) => throw new NotImplementedException(); + // Returns an ECMA-335 TypeDef table token for this type, or for its generic type definition if it is a generic instantiation + public virtual uint GetTypeDefToken(MethodTableHandle methodTable) => throw new NotImplementedException(); + // Returns the ECMA 335 TypeDef table Flags value (a bitmask of TypeAttributes) for this type, + // or for its generic type definition if it is a generic instantiation public virtual uint GetTypeDefTypeAttributes(MethodTableHandle methodTable) => throw new NotImplementedException(); + #endregion MethodTable inspection APIs } internal struct Metadata : IMetadata diff --git a/src/native/managed/cdacreader/src/Contracts/Metadata_1.cs b/src/native/managed/cdacreader/src/Contracts/Metadata_1.cs index 24e1fb37adae7c..1a75d314ae2700 100644 --- a/src/native/managed/cdacreader/src/Contracts/Metadata_1.cs +++ b/src/native/managed/cdacreader/src/Contracts/Metadata_1.cs @@ -151,7 +151,7 @@ internal Metadata_1(Target target, TargetPointer freeObjectMethodTablePointer) _freeObjectMethodTablePointer = freeObjectMethodTablePointer; } - public TargetPointer FreeObjectMethodTablePointer => _freeObjectMethodTablePointer; + internal TargetPointer FreeObjectMethodTablePointer => _freeObjectMethodTablePointer; private UntrustedMethodTable_1 GetUntrustedMethodTableData(TargetPointer methodTablePointer) { From 08d069ac16973b8c719e0b743c71d136976020ba Mon Sep 17 00:00:00 2001 From: Aleksey Kliger Date: Thu, 20 Jun 2024 10:58:15 -0400 Subject: [PATCH 28/62] rename DacpMethodTableData:klass field --- src/native/managed/cdacreader/src/Legacy/ISOSDacInterface.cs | 2 +- src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/native/managed/cdacreader/src/Legacy/ISOSDacInterface.cs b/src/native/managed/cdacreader/src/Legacy/ISOSDacInterface.cs index ea3c73028a472d..cd3c5b1ee48f21 100644 --- a/src/native/managed/cdacreader/src/Legacy/ISOSDacInterface.cs +++ b/src/native/managed/cdacreader/src/Legacy/ISOSDacInterface.cs @@ -47,7 +47,7 @@ internal struct DacpMethodTableData { public int bIsFree; // everything else is NULL if this is true. public ulong module; - public ulong @class; + public ulong klass; public ulong parentMethodTable; public ushort wNumInterfaces; public ushort wNumMethods; diff --git a/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs b/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs index 0abfca259724f9..3a9327cb08f4b7 100644 --- a/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs +++ b/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs @@ -102,7 +102,7 @@ public unsafe int GetMethodTableData(ulong mt, DacpMethodTableData* data) { result.module = contract.GetModule(methodTable); // Note: really the canonical method table, not the EEClass, which we don't expose - result.@class = contract.GetCanonicalMethodTable(methodTable); + result.klass = contract.GetCanonicalMethodTable(methodTable); result.parentMethodTable = contract.GetParentMethodTable(methodTable); result.wNumInterfaces = contract.GetNumInterfaces(methodTable); result.wNumMethods = contract.GetNumMethods(methodTable); From 674655ff7464284411cd8dfe98555a05b760ac56 Mon Sep 17 00:00:00 2001 From: Aleksey Kliger Date: Thu, 20 Jun 2024 11:11:42 -0400 Subject: [PATCH 29/62] document GetMethodTableData string baseSize adjustment --- src/coreclr/debug/daccess/request.cpp | 5 +++++ src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs | 8 +++++++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/src/coreclr/debug/daccess/request.cpp b/src/coreclr/debug/daccess/request.cpp index 15732bbf8bfc4e..7908ce05ea0d6f 100644 --- a/src/coreclr/debug/daccess/request.cpp +++ b/src/coreclr/debug/daccess/request.cpp @@ -1843,6 +1843,11 @@ ClrDataAccess::GetMethodTableDataImpl(CLRDATA_ADDRESS mt, struct DacpMethodTable { ZeroMemory(MTData,sizeof(DacpMethodTableData)); MTData->BaseSize = pMT->GetBaseSize(); + // [compat] SOS DAC APIs added this base size adjustment for strings + // due to: "2008/09/25 Title: New implementation of StringBuilder and improvements in String class" + // which changed StringBuilder not to use a String as an internal buffer and in the process + // changed the String internals so that StringObject::GetBaseSize() now includes the nul terminator character, + // which is apparently not expected by SOS. if(pMT->IsString()) MTData->BaseSize -= sizeof(WCHAR); MTData->ComponentSize = (DWORD)pMT->GetComponentSize(); diff --git a/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs b/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs index 3a9327cb08f4b7..ed5c0ef2ad29fd 100644 --- a/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs +++ b/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs @@ -93,8 +93,14 @@ public unsafe int GetMethodTableData(ulong mt, DacpMethodTableData* data) DacpMethodTableData result = default; result.baseSize = contract.GetBaseSize(methodTable); + // [compat] SOS DAC APIs added this base size adjustment for strings + // due to: "2008/09/25 Title: New implementation of StringBuilder and improvements in String class" + // which changed StringBuilder not to use a String as an internal buffer and in the process + // changed the String internals so that StringObject::GetBaseSize() now includes the nul terminator character, + // which is apparently not expected by SOS. if (contract.IsString(methodTable)) - result.baseSize -= 2 /*sizeof(WCHAR) */; + result.baseSize -= sizeof(char); + result.componentSize = contract.GetComponentSize(methodTable); bool isFreeObjectMT = contract.IsFreeObjectMethodTable(methodTable); result.bIsFree = isFreeObjectMT ? 1 : 0; From 2ae4625bf77c371b0302efcac755e3b2b522f642 Mon Sep 17 00:00:00 2001 From: Aleksey Kliger Date: Thu, 20 Jun 2024 11:15:37 -0400 Subject: [PATCH 30/62] Apply suggestions from code review Co-Authored-By: Aaron Robinson --- docs/design/datacontracts/Metadata.md | 1 + src/coreclr/debug/runtimeinfo/contractpointerdata.cpp | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/design/datacontracts/Metadata.md b/docs/design/datacontracts/Metadata.md index 6349ca074e99ba..4e3e622a5a5a6c 100644 --- a/docs/design/datacontracts/Metadata.md +++ b/docs/design/datacontracts/Metadata.md @@ -13,6 +13,7 @@ struct MethodTableHandle internal TargetPointer Address { get; } } +``` ``` csharp #region MethodTable inspection APIs diff --git a/src/coreclr/debug/runtimeinfo/contractpointerdata.cpp b/src/coreclr/debug/runtimeinfo/contractpointerdata.cpp index 74653be024471d..cf7d914e728d78 100644 --- a/src/coreclr/debug/runtimeinfo/contractpointerdata.cpp +++ b/src/coreclr/debug/runtimeinfo/contractpointerdata.cpp @@ -11,7 +11,6 @@ extern "C" { - // without an extern declaration, clang does not emit this global into the object file extern const uintptr_t contractDescriptorPointerData[]; From e18a2beb600c069cab389f4126f0d8f39d9cf566 Mon Sep 17 00:00:00 2001 From: Aleksey Kliger Date: Fri, 21 Jun 2024 10:27:40 -0400 Subject: [PATCH 31/62] fix typo --- src/native/managed/cdacreader/src/Contracts/Metadata_1.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/native/managed/cdacreader/src/Contracts/Metadata_1.cs b/src/native/managed/cdacreader/src/Contracts/Metadata_1.cs index 1a75d314ae2700..ef296f79c38707 100644 --- a/src/native/managed/cdacreader/src/Contracts/Metadata_1.cs +++ b/src/native/managed/cdacreader/src/Contracts/Metadata_1.cs @@ -12,7 +12,7 @@ namespace Microsoft.Diagnostics.DataContractReader.Contracts; // may point to garbage. So this struct represents a MethodTable that we don't necessarily // trust to be valid. // see Metadata_1.ValidateMethodTablePointer -// This doesn't need as many properties as MethodTable because we dont' want to be operating on +// This doesn't need as many properties as MethodTable because we don't want to be operating on // an UntrustedMethodTable for too long internal struct UntrustedMethodTable_1 { @@ -339,7 +339,7 @@ private TargetPointer GetClass(MethodTableHandle methodTableHandle) public bool IsFreeObjectMethodTable(MethodTableHandle methodTableHandle) => FreeObjectMethodTablePointer == methodTableHandle.Address; public bool IsString(MethodTableHandle methodTableHandle) => _methodTables[methodTableHandle.Address].Flags.IsString; - public bool ContainsPointers(MethodTableHandle methodTableHandle) => _methodTables[methodTableHandle.Address].Flags.ContainsPointers; + public bool ContainsGCPointers(MethodTableHandle methodTableHandle) => _methodTables[methodTableHandle.Address].Flags.ContainsGCPointers; public uint GetTypeDefToken(MethodTableHandle methodTableHandle) { From 846e779fb65326faec71cf67d5cff9614256269e Mon Sep 17 00:00:00 2001 From: Aleksey Kliger Date: Fri, 21 Jun 2024 10:27:58 -0400 Subject: [PATCH 32/62] rename flag to ContainsGCPointers --- docs/design/datacontracts/Metadata.md | 2 +- src/native/managed/cdacreader/src/Contracts/Metadata.cs | 2 +- .../cdacreader/src/Contracts/Metadata_1.MethodTableFlags.cs | 4 ++-- src/native/managed/cdacreader/src/Legacy/ISOSDacInterface.cs | 2 +- src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/design/datacontracts/Metadata.md b/docs/design/datacontracts/Metadata.md index 4e3e622a5a5a6c..908ba3ed86d29e 100644 --- a/docs/design/datacontracts/Metadata.md +++ b/docs/design/datacontracts/Metadata.md @@ -33,7 +33,7 @@ struct MethodTableHandle public virtual bool IsFreeObjectMethodTable(MethodTableHandle methodTable) => throw new NotImplementedException(); public virtual bool IsString(MethodTableHandle methodTable) => throw new NotImplementedException(); // True if the MethodTable represents a type that contains managed references - public virtual bool ContainsPointers(MethodTableHandle methodTable) => throw new NotImplementedException(); + public virtual bool ContainsGCPointers(MethodTableHandle methodTable) => throw new NotImplementedException(); public virtual bool IsDynamicStatics(MethodTableHandle methodTable) => throw new NotImplementedException(); public virtual ushort GetNumMethods(MethodTableHandle methodTable) => throw new NotImplementedException(); public virtual ushort GetNumInterfaces(MethodTableHandle methodTable) => throw new NotImplementedException(); diff --git a/src/native/managed/cdacreader/src/Contracts/Metadata.cs b/src/native/managed/cdacreader/src/Contracts/Metadata.cs index 0a64e4b191072f..97fd5fe83b13bb 100644 --- a/src/native/managed/cdacreader/src/Contracts/Metadata.cs +++ b/src/native/managed/cdacreader/src/Contracts/Metadata.cs @@ -48,7 +48,7 @@ static IContract IContract.Create(Target target, int version) public virtual bool IsFreeObjectMethodTable(MethodTableHandle methodTable) => throw new NotImplementedException(); public virtual bool IsString(MethodTableHandle methodTable) => throw new NotImplementedException(); // True if the MethodTable represents a type that contains managed references - public virtual bool ContainsPointers(MethodTableHandle methodTable) => throw new NotImplementedException(); + public virtual bool ContainsGCPointers(MethodTableHandle methodTable) => throw new NotImplementedException(); public virtual bool IsDynamicStatics(MethodTableHandle methodTable) => throw new NotImplementedException(); public virtual ushort GetNumMethods(MethodTableHandle methodTable) => throw new NotImplementedException(); public virtual ushort GetNumInterfaces(MethodTableHandle methodTable) => throw new NotImplementedException(); diff --git a/src/native/managed/cdacreader/src/Contracts/Metadata_1.MethodTableFlags.cs b/src/native/managed/cdacreader/src/Contracts/Metadata_1.MethodTableFlags.cs index c7e31785a4dd73..51eede7725c47b 100644 --- a/src/native/managed/cdacreader/src/Contracts/Metadata_1.MethodTableFlags.cs +++ b/src/native/managed/cdacreader/src/Contracts/Metadata_1.MethodTableFlags.cs @@ -25,7 +25,7 @@ internal enum WFLAGS_HIGH : uint Category_Array = 0x00080000, Category_Array_Mask = 0x000C0000, Category_Interface = 0x000C0000, - ContainsPointers = 0x01000000, // Contains object references + ContainsGCPointers = 0x01000000, // Contains object references HasComponentSize = 0x80000000, // This is set if component size is used for flags. } @@ -78,6 +78,6 @@ public bool TestFlagWithMask(WFLAGS2_ENUM mask, WFLAGS2_ENUM flag) public bool HasInstantiation => !TestFlagWithMask(WFLAGS_LOW.GenericsMask, WFLAGS_LOW.GenericsMask_NonGeneric); - public bool ContainsPointers => GetFlag(WFLAGS_HIGH.ContainsPointers) != 0; + public bool ContainsGCPointers => GetFlag(WFLAGS_HIGH.ContainsGCPointers) != 0; } } diff --git a/src/native/managed/cdacreader/src/Legacy/ISOSDacInterface.cs b/src/native/managed/cdacreader/src/Legacy/ISOSDacInterface.cs index cd3c5b1ee48f21..6a806e631731cd 100644 --- a/src/native/managed/cdacreader/src/Legacy/ISOSDacInterface.cs +++ b/src/native/managed/cdacreader/src/Legacy/ISOSDacInterface.cs @@ -59,7 +59,7 @@ internal struct DacpMethodTableData public uint dwAttrClass; // cached metadata public int bIsShared; // Always false, preserved for backward compatibility public int bIsDynamic; - public int bContainsPointers; + public int bContainsGCPointers; } #pragma warning restore CS0649 // Field is never assigned to, and will always have its default value diff --git a/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs b/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs index ed5c0ef2ad29fd..c85a4a6f86fa03 100644 --- a/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs +++ b/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs @@ -116,7 +116,7 @@ public unsafe int GetMethodTableData(ulong mt, DacpMethodTableData* data) result.wNumVirtuals = contract.GetNumVirtuals(methodTable); result.cl = contract.GetTypeDefToken(methodTable); result.dwAttrClass = contract.GetTypeDefTypeAttributes(methodTable); - result.bContainsPointers = contract.ContainsPointers(methodTable) ? 1 : 0; + result.bContainsGCPointers = contract.ContainsGCPointers(methodTable) ? 1 : 0; result.bIsShared = 0; result.bIsDynamic = contract.IsDynamicStatics(methodTable) ? 1 : 0; } From 383af831ea814e2a7c87de93751b7e37bb026816 Mon Sep 17 00:00:00 2001 From: Aleksey Kliger Date: Fri, 21 Jun 2024 10:45:11 -0400 Subject: [PATCH 33/62] [vm] rename ContainsPointers flag to ContainsGCPointers also rename getter/setter methods in MethodTable --- .../RuntimeHelpers.CoreCLR.cs | 4 ++-- src/coreclr/debug/daccess/request.cpp | 2 +- src/coreclr/gc/env/gcenv.object.h | 10 ++++---- src/coreclr/gc/gc.cpp | 24 +++++++++---------- src/coreclr/gc/gcdesc.h | 4 ++-- src/coreclr/gc/sample/GCSample.cpp | 2 +- src/coreclr/nativeaot/Runtime/GCHelpers.cpp | 4 ++-- .../nativeaot/Runtime/inc/MethodTable.h | 4 ++-- .../nativeaot/Runtime/profheapwalkhelper.cpp | 2 +- src/coreclr/vm/amd64/JitHelpers_Slow.asm | 2 +- src/coreclr/vm/amd64/asmconstants.h | 6 ++--- src/coreclr/vm/arm/asmconstants.h | 4 ++-- src/coreclr/vm/array.cpp | 8 +++---- src/coreclr/vm/class.cpp | 2 +- src/coreclr/vm/classlayoutinfo.cpp | 4 ++-- src/coreclr/vm/comutilnative.cpp | 2 +- src/coreclr/vm/dllimport.cpp | 2 +- src/coreclr/vm/gchelpers.cpp | 10 ++++---- src/coreclr/vm/generics.cpp | 2 +- src/coreclr/vm/i386/jitinterfacex86.cpp | 4 ++-- src/coreclr/vm/ilmarshalers.cpp | 8 +++---- src/coreclr/vm/interpreter.cpp | 8 +++---- src/coreclr/vm/jitinterface.cpp | 24 +++++++++---------- src/coreclr/vm/method.cpp | 2 +- src/coreclr/vm/methodtable.cpp | 12 +++++----- src/coreclr/vm/methodtable.h | 14 +++++------ src/coreclr/vm/methodtablebuilder.cpp | 24 +++++++++---------- src/coreclr/vm/mlinfo.cpp | 2 +- src/coreclr/vm/object.cpp | 2 +- src/coreclr/vm/proftoeeinterfaceimpl.cpp | 4 ++-- src/coreclr/vm/reflectioninvocation.cpp | 4 ++-- src/coreclr/vm/siginfo.cpp | 4 ++-- src/coreclr/vm/stubgen.cpp | 2 +- src/coreclr/vm/tailcallhelp.cpp | 2 +- 34 files changed, 107 insertions(+), 107 deletions(-) diff --git a/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.CoreCLR.cs index 338d5c9e0357e9..f3f6437b834844 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.CoreCLR.cs @@ -556,7 +556,7 @@ internal unsafe struct MethodTable private const uint enum_flag_IsByRefLike = 0x00001000; // WFLAGS_HIGH_ENUM - private const uint enum_flag_ContainsPointers = 0x01000000; + private const uint enum_flag_ContainsGCPointers = 0x01000000; private const uint enum_flag_ContainsGenericVariables = 0x20000000; private const uint enum_flag_HasComponentSize = 0x80000000; private const uint enum_flag_HasTypeEquivalence = 0x02000000; @@ -609,7 +609,7 @@ internal unsafe struct MethodTable public bool HasComponentSize => (Flags & enum_flag_HasComponentSize) != 0; - public bool ContainsGCPointers => (Flags & enum_flag_ContainsPointers) != 0; + public bool ContainsGCPointers => (Flags & enum_flag_ContainsGCPointers) != 0; public bool NonTrivialInterfaceCast => (Flags & enum_flag_NonTrivialInterfaceCast) != 0; diff --git a/src/coreclr/debug/daccess/request.cpp b/src/coreclr/debug/daccess/request.cpp index 7908ce05ea0d6f..63f818affcd9c7 100644 --- a/src/coreclr/debug/daccess/request.cpp +++ b/src/coreclr/debug/daccess/request.cpp @@ -1864,7 +1864,7 @@ ClrDataAccess::GetMethodTableDataImpl(CLRDATA_ADDRESS mt, struct DacpMethodTable MTData->wNumVirtuals = pMT->GetNumVirtuals(); MTData->cl = pMT->GetCl(); MTData->dwAttrClass = pMT->GetAttrClass(); - MTData->bContainsPointers = pMT->ContainsPointers(); + MTData->bContainsPointers = pMT->ContainsGCPointers(); MTData->bIsShared = FALSE; MTData->bIsDynamic = pMT->IsDynamicStatics(); } diff --git a/src/coreclr/gc/env/gcenv.object.h b/src/coreclr/gc/env/gcenv.object.h index ff0dbb343ed1dd..e8e0a8185cb158 100644 --- a/src/coreclr/gc/env/gcenv.object.h +++ b/src/coreclr/gc/env/gcenv.object.h @@ -49,7 +49,7 @@ static_assert(sizeof(ObjHeader) == sizeof(uintptr_t), "this assumption is made b #define MTFlag_RequiresAlign8 0x00001000 // enum_flag_RequiresAlign8 #define MTFlag_Category_ValueType 0x00040000 // enum_flag_Category_ValueType #define MTFlag_Category_ValueType_Mask 0x000C0000 // enum_flag_Category_ValueType_Mask -#define MTFlag_ContainsPointers 0x01000000 // enum_flag_ContainsPointers +#define MTFlag_ContainsGCPointers 0x01000000 // enum_flag_ContainsGCPointers #define MTFlag_HasCriticalFinalizer 0x00000002 // enum_flag_HasCriticalFinalizer #define MTFlag_HasFinalizer 0x00100000 // enum_flag_HasFinalizer #define MTFlag_IsArray 0x00080000 // enum_flag_Category_Array @@ -100,14 +100,14 @@ class MethodTable return (m_flags & MTFlag_Collectible) != 0; } - bool ContainsPointers() + bool ContainsGCPointers() { - return (m_flags & MTFlag_ContainsPointers) != 0; + return (m_flags & MTFlag_ContainsGCPointers) != 0; } - bool ContainsPointersOrCollectible() + bool ContainsGCPointersOrCollectible() { - return ContainsPointers() || Collectible(); + return ContainsGCPointers() || Collectible(); } bool RequiresAlign8() diff --git a/src/coreclr/gc/gc.cpp b/src/coreclr/gc/gc.cpp index 4f215b3b763279..7dd1c252eddf49 100644 --- a/src/coreclr/gc/gc.cpp +++ b/src/coreclr/gc/gc.cpp @@ -4829,7 +4829,7 @@ class CObjectHeader : public Object CGCDesc *GetSlotMap () { - assert (GetMethodTable()->ContainsPointers()); + assert (GetMethodTable()->ContainsGCPointers()); return CGCDesc::GetCGCDescFromMT(GetMethodTable()); } @@ -4893,9 +4893,9 @@ class CObjectHeader : public Object } #endif // FEATURE_STRUCTALIGN - BOOL ContainsPointers() const + BOOL ContainsGCPointers() const { - return GetMethodTable()->ContainsPointers(); + return GetMethodTable()->ContainsGCPointers(); } #ifdef COLLECTIBLE_CLASS @@ -4904,10 +4904,10 @@ class CObjectHeader : public Object return GetMethodTable()->Collectible(); } - FORCEINLINE BOOL ContainsPointersOrCollectible() const + FORCEINLINE BOOL ContainsGCPointersOrCollectible() const { MethodTable *pMethodTable = GetMethodTable(); - return (pMethodTable->ContainsPointers() || pMethodTable->Collectible()); + return (pMethodTable->ContainsGCPointers() || pMethodTable->Collectible()); } #endif //COLLECTIBLE_CLASS @@ -6066,7 +6066,7 @@ void gc_heap::release_segment (heap_segment* sg) FIRE_EVENT(GCFreeSegment_V1, heap_segment_mem(sg)); size_t reserved_size = (uint8_t*)heap_segment_reserved (sg) - (uint8_t*)sg; reduce_committed_bytes ( - sg, + sg, ((uint8_t*)heap_segment_committed (sg) - (uint8_t*)sg), (int) heap_segment_oh (sg) #ifdef MULTIPLE_HEAPS @@ -9083,7 +9083,7 @@ void destroy_card_table (uint32_t* c_table) void gc_heap::destroy_card_table_helper (uint32_t* c_table) { uint8_t* lowest = card_table_lowest_address (c_table); - uint8_t* highest = card_table_highest_address (c_table); + uint8_t* highest = card_table_highest_address (c_table); get_card_table_element_layout(lowest, highest, card_table_element_layout); size_t result = card_table_element_layout[seg_mapping_table_element + 1]; gc_heap::reduce_committed_bytes (&card_table_refcount(c_table), result, recorded_committed_bookkeeping_bucket, -1, true); @@ -11555,14 +11555,14 @@ inline size_t my_get_size (Object* ob) #define size(i) my_get_size (header(i)) -#define contain_pointers(i) header(i)->ContainsPointers() +#define contain_pointers(i) header(i)->ContainsGCPointers() #ifdef COLLECTIBLE_CLASS -#define contain_pointers_or_collectible(i) header(i)->ContainsPointersOrCollectible() +#define contain_pointers_or_collectible(i) header(i)->ContainsGCPointersOrCollectible() #define get_class_object(i) GCToEEInterface::GetLoaderAllocatorObjectForGC((Object *)i) #define is_collectible(i) method_table(i)->Collectible() #else //COLLECTIBLE_CLASS -#define contain_pointers_or_collectible(i) header(i)->ContainsPointers() +#define contain_pointers_or_collectible(i) header(i)->ContainsGCPointers() #endif //COLLECTIBLE_CLASS #ifdef BACKGROUND_GC @@ -26683,7 +26683,7 @@ BOOL gc_heap::background_mark (uint8_t* o, uint8_t* low, uint8_t* high) #ifndef COLLECTIBLE_CLASS #define go_through_object_cl(mt,o,size,parm,exp) \ { \ - if (header(o)->ContainsPointers()) \ + if (header(o)->ContainsGCPointers()) \ { \ go_through_object_nostart(mt,o,size,parm,exp); \ } \ @@ -26697,7 +26697,7 @@ BOOL gc_heap::background_mark (uint8_t* o, uint8_t* low, uint8_t* high) uint8_t** parm = &class_obj; \ do {exp} while (false); \ } \ - if (header(o)->ContainsPointers()) \ + if (header(o)->ContainsGCPointers()) \ { \ go_through_object_nostart(mt,o,size,parm,exp); \ } \ diff --git a/src/coreclr/gc/gcdesc.h b/src/coreclr/gc/gcdesc.h index 54a13dfdb8cdff..8d91e776ac4284 100644 --- a/src/coreclr/gc/gcdesc.h +++ b/src/coreclr/gc/gcdesc.h @@ -161,7 +161,7 @@ class CGCDesc // If it doesn't contain pointers, there isn't a GCDesc PTR_MethodTable mt(pMT); - _ASSERTE(mt->ContainsPointers()); + _ASSERTE(mt->ContainsGCPointers()); return PTR_CGCDesc(mt); } @@ -195,7 +195,7 @@ class CGCDesc { size_t NumOfPointers = 0; - if (pMT->ContainsPointers()) + if (pMT->ContainsGCPointers()) { CGCDesc* map = GetCGCDescFromMT(pMT); CGCDescSeries* cur = map->GetHighestSeries(); diff --git a/src/coreclr/gc/sample/GCSample.cpp b/src/coreclr/gc/sample/GCSample.cpp index 0f2afc7c20a717..3b9bf63103dc4f 100644 --- a/src/coreclr/gc/sample/GCSample.cpp +++ b/src/coreclr/gc/sample/GCSample.cpp @@ -179,7 +179,7 @@ int __cdecl main(int argc, char* argv[]) My_MethodTable.m_MT.m_baseSize = max(baseSize, (uint32_t)MIN_OBJECT_SIZE); My_MethodTable.m_MT.m_componentSize = 0; // Array component size - My_MethodTable.m_MT.m_flags = MTFlag_ContainsPointers; + My_MethodTable.m_MT.m_flags = MTFlag_ContainsGCPointers; My_MethodTable.m_numSeries = 2; diff --git a/src/coreclr/nativeaot/Runtime/GCHelpers.cpp b/src/coreclr/nativeaot/Runtime/GCHelpers.cpp index 6fecd5ac04768e..b038d9d33541bd 100644 --- a/src/coreclr/nativeaot/Runtime/GCHelpers.cpp +++ b/src/coreclr/nativeaot/Runtime/GCHelpers.cpp @@ -476,7 +476,7 @@ static Object* GcAllocInternal(MethodTable* pEEType, uint32_t uFlags, uintptr_t ASSERT(!pThread->IsDoNotTriggerGcSet()); ASSERT(pThread->IsCurrentThreadInCooperativeMode()); - if (pEEType->ContainsPointers()) + if (pEEType->ContainsGCPointers()) { uFlags |= GC_ALLOC_CONTAINS_REF; uFlags &= ~GC_ALLOC_ZEROING_OPTIONAL; @@ -693,7 +693,7 @@ EXTERN_C void QCALLTYPE RhUnregisterFrozenSegment(void* pSegmentHandle) FCIMPL1(uint32_t, RhGetGCDescSize, MethodTable* pMT) { - if (!pMT->ContainsPointersOrCollectible()) + if (!pMT->ContainsGCPointersOrCollectible()) return 0; return (uint32_t)CGCDesc::GetCGCDescFromMT(pMT)->GetSize(); diff --git a/src/coreclr/nativeaot/Runtime/inc/MethodTable.h b/src/coreclr/nativeaot/Runtime/inc/MethodTable.h index b5c41b14d92bf5..f33a5d066dc3ec 100644 --- a/src/coreclr/nativeaot/Runtime/inc/MethodTable.h +++ b/src/coreclr/nativeaot/Runtime/inc/MethodTable.h @@ -289,8 +289,8 @@ class MethodTable public: // Methods expected by the GC - uint32_t ContainsPointers() { return HasReferenceFields(); } - uint32_t ContainsPointersOrCollectible() { return HasReferenceFields(); } + uint32_t ContainsGCPointers() { return HasReferenceFields(); } + uint32_t ContainsGCPointersOrCollectible() { return HasReferenceFields(); } UInt32_BOOL SanityCheck() { return Validate(); } }; diff --git a/src/coreclr/nativeaot/Runtime/profheapwalkhelper.cpp b/src/coreclr/nativeaot/Runtime/profheapwalkhelper.cpp index 3cecf152f3a855..6d9d7edc6fea46 100644 --- a/src/coreclr/nativeaot/Runtime/profheapwalkhelper.cpp +++ b/src/coreclr/nativeaot/Runtime/profheapwalkhelper.cpp @@ -138,7 +138,7 @@ bool HeapWalkHelper(Object * pBO, void * pvContext) ProfilerWalkHeapContext * pProfilerWalkHeapContext = (ProfilerWalkHeapContext *) pvContext; - //if (pMT->ContainsPointersOrCollectible()) + //if (pMT->ContainsGCPointersOrCollectible()) { // First round through calculates the number of object refs for this class GCHeapUtilities::GetGCHeap()->DiagWalkObject(pBO, &CountContainedObjectRef, (void *)&cNumRefs); diff --git a/src/coreclr/vm/amd64/JitHelpers_Slow.asm b/src/coreclr/vm/amd64/JitHelpers_Slow.asm index e2f58ac6618db4..6d322248cdeeec 100644 --- a/src/coreclr/vm/amd64/JitHelpers_Slow.asm +++ b/src/coreclr/vm/amd64/JitHelpers_Slow.asm @@ -224,7 +224,7 @@ NESTED_ENTRY JIT_BoxFastUP, _TEXT mov [g_global_alloc_lock], -1 ; Check whether the object contains pointers - test dword ptr [rcx + OFFSETOF__MethodTable__m_dwFlags], MethodTable__enum_flag_ContainsPointers + test dword ptr [rcx + OFFSETOF__MethodTable__m_dwFlags], MethodTable__enum_flag_ContainsGCPointers jnz ContainsPointers ; We have no pointers - emit a simple inline copy loop diff --git a/src/coreclr/vm/amd64/asmconstants.h b/src/coreclr/vm/amd64/asmconstants.h index c629192da5cb9b..524e1fd40b7ae8 100644 --- a/src/coreclr/vm/amd64/asmconstants.h +++ b/src/coreclr/vm/amd64/asmconstants.h @@ -176,9 +176,9 @@ ASMCONSTANTS_C_ASSERT(METHODTABLE_EQUIVALENCE_FLAGS #define METHODTABLE_EQUIVALENCE_FLAGS 0x0 #endif -#define MethodTable__enum_flag_ContainsPointers 0x01000000 -ASMCONSTANTS_C_ASSERT(MethodTable__enum_flag_ContainsPointers - == MethodTable::enum_flag_ContainsPointers); +#define MethodTable__enum_flag_ContainsGCPointers 0x01000000 +ASMCONSTANTS_C_ASSERT(MethodTable__enum_flag_ContainsGCPointers + == MethodTable::enum_flag_ContainsGCPointers); #define OFFSETOF__InterfaceInfo_t__m_pMethodTable 0 ASMCONSTANTS_C_ASSERT(OFFSETOF__InterfaceInfo_t__m_pMethodTable diff --git a/src/coreclr/vm/arm/asmconstants.h b/src/coreclr/vm/arm/asmconstants.h index 9995068d852792..1a65e1e45351da 100644 --- a/src/coreclr/vm/arm/asmconstants.h +++ b/src/coreclr/vm/arm/asmconstants.h @@ -76,8 +76,8 @@ ASMCONSTANTS_C_ASSERT(MethodTable__m_BaseSize == offsetof(MethodTable, m_BaseSiz #define MethodTable__m_dwFlags 0x0 ASMCONSTANTS_C_ASSERT(MethodTable__m_dwFlags == offsetof(MethodTable, m_dwFlags)); -#define MethodTable__enum_flag_ContainsPointers 0x01000000 -ASMCONSTANTS_C_ASSERT(MethodTable__enum_flag_ContainsPointers == MethodTable::enum_flag_ContainsPointers); +#define MethodTable__enum_flag_ContainsGCPointers 0x01000000 +ASMCONSTANTS_C_ASSERT(MethodTable__enum_flag_ContainsGCPointers == MethodTable::enum_flag_ContainsGCPointers); #define MethodTable__m_ElementType DBG_FRE(0x24, 0x20) ASMCONSTANTS_C_ASSERT(MethodTable__m_ElementType == offsetof(MethodTable, m_ElementTypeHnd)); diff --git a/src/coreclr/vm/array.cpp b/src/coreclr/vm/array.cpp index 3b2b778f4c50f0..546b2292b35270 100644 --- a/src/coreclr/vm/array.cpp +++ b/src/coreclr/vm/array.cpp @@ -238,7 +238,7 @@ MethodTable* Module::CreateArrayMethodTable(TypeHandle elemTypeHnd, CorElementTy } BOOL containsPointers = CorTypeInfo::IsObjRef(elemType); - if (elemType == ELEMENT_TYPE_VALUETYPE && pElemMT->ContainsPointers()) + if (elemType == ELEMENT_TYPE_VALUETYPE && pElemMT->ContainsGCPointers()) containsPointers = TRUE; // this is the base for every array type @@ -520,7 +520,7 @@ MethodTable* Module::CreateArrayMethodTable(TypeHandle elemTypeHnd, CorElementTy if (CorTypeInfo::IsObjRef(elemType) || ((elemType == ELEMENT_TYPE_VALUETYPE) && pElemMT->IsAllGCPointers())) { - pMT->SetContainsPointers(); + pMT->SetContainsGCPointers(); // This array is all GC Pointers CGCDesc::GetCGCDescFromMT(pMT)->Init( pMT, 1 ); @@ -536,9 +536,9 @@ MethodTable* Module::CreateArrayMethodTable(TypeHandle elemTypeHnd, CorElementTy else if (elemType == ELEMENT_TYPE_VALUETYPE) { // If it's an array of value classes, there is a different format for the GCDesc if it contains pointers - if (pElemMT->ContainsPointers()) + if (pElemMT->ContainsGCPointers()) { - pMT->SetContainsPointers(); + pMT->SetContainsGCPointers(); CGCDescSeries* pElemSeries = CGCDesc::GetCGCDescFromMT(pElemMT)->GetHighestSeries(); diff --git a/src/coreclr/vm/class.cpp b/src/coreclr/vm/class.cpp index 6c0052636f6ae1..9d85bc141e115a 100644 --- a/src/coreclr/vm/class.cpp +++ b/src/coreclr/vm/class.cpp @@ -2690,7 +2690,7 @@ MethodTable::DebugDumpGCDesc( LOG((LF_ALWAYS, LL_ALWAYS, "GC description for '%s':\n\n", pszClassName)); } - if (ContainsPointers()) + if (ContainsGCPointers()) { CGCDescSeries *pSeries; CGCDescSeries *pHighest; diff --git a/src/coreclr/vm/classlayoutinfo.cpp b/src/coreclr/vm/classlayoutinfo.cpp index 8336f890660329..b7290c5a5c3b5f 100644 --- a/src/coreclr/vm/classlayoutinfo.cpp +++ b/src/coreclr/vm/classlayoutinfo.cpp @@ -285,7 +285,7 @@ namespace } else #endif // FEATURE_64BIT_ALIGNMENT - if (pNestedType.GetMethodTable()->ContainsPointers()) + if (pNestedType.GetMethodTable()->ContainsGCPointers()) { // this field type has GC pointers in it, which need to be pointer-size aligned placementInfo.m_alignment = TARGET_POINTER_SIZE; @@ -310,7 +310,7 @@ namespace if (corElemType == ELEMENT_TYPE_VALUETYPE) { _ASSERTE(!pNestedType.IsNull()); - return pNestedType.GetMethodTable()->ContainsPointers() != FALSE; + return pNestedType.GetMethodTable()->ContainsGCPointers() != FALSE; } return TRUE; } diff --git a/src/coreclr/vm/comutilnative.cpp b/src/coreclr/vm/comutilnative.cpp index 143174cc24ce70..3575a54188d96e 100644 --- a/src/coreclr/vm/comutilnative.cpp +++ b/src/coreclr/vm/comutilnative.cpp @@ -1623,7 +1623,7 @@ BOOL CanCompareBitsOrUseFastGetHashCode(MethodTable* mt) return mt->CanCompareBitsOrUseFastGetHashCode(); } - if (mt->ContainsPointers() + if (mt->ContainsGCPointers() || mt->IsNotTightlyPacked()) { mt->SetHasCheckedCanCompareBitsOrUseFastGetHashCode(); diff --git a/src/coreclr/vm/dllimport.cpp b/src/coreclr/vm/dllimport.cpp index 652f4b69eb2091..89b897912f837a 100644 --- a/src/coreclr/vm/dllimport.cpp +++ b/src/coreclr/vm/dllimport.cpp @@ -3368,7 +3368,7 @@ BOOL NDirect::MarshalingRequired( // as long as they aren't auto-layout and don't have any auto-layout fields. if (!runtimeMarshallingEnabled && !hndArgType.IsEnum() && - (hndArgType.GetMethodTable()->ContainsPointers() + (hndArgType.GetMethodTable()->ContainsGCPointers() || hndArgType.GetMethodTable()->IsAutoLayoutOrHasAutoLayoutField())) { return TRUE; diff --git a/src/coreclr/vm/gchelpers.cpp b/src/coreclr/vm/gchelpers.cpp index 06db3076ef4ebf..335bd3cb25caba 100644 --- a/src/coreclr/vm/gchelpers.cpp +++ b/src/coreclr/vm/gchelpers.cpp @@ -413,7 +413,7 @@ OBJECTREF AllocateSzArray(MethodTable* pArrayMT, INT32 cElements, GC_ALLOC_FLAGS if (totalSize >= LARGE_OBJECT_SIZE && totalSize >= GCHeapUtilities::GetGCHeap()->GetLOHThreshold()) flags |= GC_ALLOC_LARGE_OBJECT_HEAP; - if (pArrayMT->ContainsPointers()) + if (pArrayMT->ContainsGCPointers()) flags |= GC_ALLOC_CONTAINS_REF; ArrayBase* orArray = NULL; @@ -513,7 +513,7 @@ OBJECTREF TryAllocateFrozenSzArray(MethodTable* pArrayMT, INT32 cElements) // The initial validation is copied from AllocateSzArray impl - if (pArrayMT->ContainsPointers() && cElements > 0) + if (pArrayMT->ContainsGCPointers() && cElements > 0) { // For arrays with GC pointers we can only work with empty arrays return NULL; @@ -720,7 +720,7 @@ OBJECTREF AllocateArrayEx(MethodTable *pArrayMT, INT32 *pArgs, DWORD dwNumArgs, if (totalSize >= LARGE_OBJECT_SIZE && totalSize >= GCHeapUtilities::GetGCHeap()->GetLOHThreshold()) flags |= GC_ALLOC_LARGE_OBJECT_HEAP; - if (pArrayMT->ContainsPointers()) + if (pArrayMT->ContainsGCPointers()) flags |= GC_ALLOC_CONTAINS_REF; ArrayBase* orArray = NULL; @@ -1066,7 +1066,7 @@ OBJECTREF AllocateObject(MethodTable *pMT #endif // FEATURE_COMINTEROP else { - if (pMT->ContainsPointers()) + if (pMT->ContainsGCPointers()) flags |= GC_ALLOC_CONTAINS_REF; if (pMT->HasFinalizer()) @@ -1122,7 +1122,7 @@ OBJECTREF TryAllocateFrozenObject(MethodTable* pObjMT) SetTypeHandleOnThreadForAlloc(TypeHandle(pObjMT)); - if (pObjMT->ContainsPointers() || pObjMT->IsComObjectType()) + if (pObjMT->ContainsGCPointers() || pObjMT->IsComObjectType()) { return NULL; } diff --git a/src/coreclr/vm/generics.cpp b/src/coreclr/vm/generics.cpp index 988229d8009a8f..384b1007cf8bda 100644 --- a/src/coreclr/vm/generics.cpp +++ b/src/coreclr/vm/generics.cpp @@ -217,7 +217,7 @@ ClassLoader::CreateTypeHandleForNonCanonicalGenericInstantiation( #endif // FEATURE_COMINTEROP // The number of bytes used for GC info - size_t cbGC = pOldMT->ContainsPointers() ? ((CGCDesc*) pOldMT)->GetSize() : 0; + size_t cbGC = pOldMT->ContainsGCPointers() ? ((CGCDesc*) pOldMT)->GetSize() : 0; // Bytes are required for the vtable itself S_SIZE_T safe_cbMT = S_SIZE_T( cbGC ) + S_SIZE_T( sizeof(MethodTable) ); diff --git a/src/coreclr/vm/i386/jitinterfacex86.cpp b/src/coreclr/vm/i386/jitinterfacex86.cpp index 73603f2752969d..3807b00a8ca6e1 100644 --- a/src/coreclr/vm/i386/jitinterfacex86.cpp +++ b/src/coreclr/vm/i386/jitinterfacex86.cpp @@ -421,9 +421,9 @@ void *JIT_TrialAlloc::GenBox(Flags flags) // Here we are at the end of the success case // Check whether the object contains pointers - // test [ecx]MethodTable.m_dwFlags,MethodTable::enum_flag_ContainsPointers + // test [ecx]MethodTable.m_dwFlags,MethodTable::enum_flag_ContainsGCPointers sl.X86EmitOffsetModRM(0xf7, (X86Reg)0x0, kECX, offsetof(MethodTable, m_dwFlags)); - sl.Emit32(MethodTable::enum_flag_ContainsPointers); + sl.Emit32(MethodTable::enum_flag_ContainsGCPointers); CodeLabel *pointerLabel = sl.NewCodeLabel(); diff --git a/src/coreclr/vm/ilmarshalers.cpp b/src/coreclr/vm/ilmarshalers.cpp index b0fcef12d6b8d7..75d979076bd3a9 100644 --- a/src/coreclr/vm/ilmarshalers.cpp +++ b/src/coreclr/vm/ilmarshalers.cpp @@ -4383,7 +4383,7 @@ extern "C" void QCALLTYPE MngdNativeArrayMarshaler_ConvertContentsToNative(MngdN if ( (!ClrSafeInt::multiply(cElements, OleVariant::GetElementSizeForVarType(pThis->m_vt, pThis->m_pElementMT), cElements)) || cElements > MAX_SIZE_FOR_INTEROP) COMPlusThrow(kArgumentException, IDS_EE_STRUCTARRAYTOOLARGE); - _ASSERTE(!GetTypeHandleForCVType(OleVariant::GetCVTypeForVarType(pThis->m_vt)).GetMethodTable()->ContainsPointers()); + _ASSERTE(!GetTypeHandleForCVType(OleVariant::GetCVTypeForVarType(pThis->m_vt)).GetMethodTable()->ContainsGCPointers()); memcpyNoGCRefs(*pNativeHome, arrayRef->GetDataPtr(), cElements); } else @@ -4452,7 +4452,7 @@ extern "C" void QCALLTYPE MngdNativeArrayMarshaler_ConvertContentsToManaged(Mngd COMPlusThrow(kArgumentException, IDS_EE_STRUCTARRAYTOOLARGE); // If we are copying variants, strings, etc, we need to use write barrier - _ASSERTE(!GetTypeHandleForCVType(OleVariant::GetCVTypeForVarType(pThis->m_vt)).GetMethodTable()->ContainsPointers()); + _ASSERTE(!GetTypeHandleForCVType(OleVariant::GetCVTypeForVarType(pThis->m_vt)).GetMethodTable()->ContainsGCPointers()); memcpyNoGCRefs(arrayRef->GetDataPtr(), *pNativeHome, cElements); } else @@ -4567,7 +4567,7 @@ extern "C" void QCALLTYPE MngdFixedArrayMarshaler_ConvertContentsToNative(MngdFi SIZE_T cElements = arrayRef->GetNumComponents(); if (pMarshaler == NULL || pMarshaler->ComToOleArray == NULL) { - _ASSERTE(!GetTypeHandleForCVType(OleVariant::GetCVTypeForVarType(pThis->m_vt)).GetMethodTable()->ContainsPointers()); + _ASSERTE(!GetTypeHandleForCVType(OleVariant::GetCVTypeForVarType(pThis->m_vt)).GetMethodTable()->ContainsGCPointers()); memcpyNoGCRefs(pNativeHome, arrayRef->GetDataPtr(), nativeSize); } else @@ -4641,7 +4641,7 @@ extern "C" void QCALLTYPE MngdFixedArrayMarshaler_ConvertContentsToManaged(MngdF if (pMarshaler == NULL || pMarshaler->OleToComArray == NULL) { // If we are copying variants, strings, etc, we need to use write barrier - _ASSERTE(!GetTypeHandleForCVType(OleVariant::GetCVTypeForVarType(pThis->m_vt)).GetMethodTable()->ContainsPointers()); + _ASSERTE(!GetTypeHandleForCVType(OleVariant::GetCVTypeForVarType(pThis->m_vt)).GetMethodTable()->ContainsGCPointers()); memcpyNoGCRefs(arrayRef->GetDataPtr(), pNativeHome, nativeSize); } else diff --git a/src/coreclr/vm/interpreter.cpp b/src/coreclr/vm/interpreter.cpp index 908077f7ba3899..181dc83cbea833 100644 --- a/src/coreclr/vm/interpreter.cpp +++ b/src/coreclr/vm/interpreter.cpp @@ -10959,7 +10959,7 @@ void Interpreter::DoIsReferenceOrContainsReferences(CORINFO_METHOD_HANDLE method MethodTable* typeArg = GetMethodTableFromClsHnd(sigInfoFull.sigInst.methInst[0]); - bool containsGcPtrs = typeArg->ContainsPointers(); + bool containsGcPtrs = typeArg->ContainsGCPointers(); // Return true for byref-like structs with ref fields (they might not have them) if (!containsGcPtrs && typeArg->IsByRefLike()) @@ -10981,7 +10981,7 @@ bool Interpreter::DoInterlockedCompareExchange(CorInfoType retType) } CONTRACTL_END; // These CompareExchange are must-expand: - // + // // long CompareExchange(ref long location1, long value, long comparand) // int CompareExchange(ref int location1, int value, int comparand) // ushort CompareExchange(ref ushort location1, ushort value, ushort comparand) @@ -11033,7 +11033,7 @@ bool Interpreter::DoInterlockedExchange(CorInfoType retType) } CONTRACTL_END; // These Exchange are must-expand: - // + // // long Exchange(ref long location1, long value) // int Exchange(ref int location1, int value) // ushort Exchange(ref ushort location1, ushort value) @@ -11082,7 +11082,7 @@ bool Interpreter::DoInterlockedExchangeAdd(CorInfoType retType) } CONTRACTL_END; // These ExchangeAdd are must-expand: - // + // // long ExchangeAdd(ref long location1, long value) // int ExchangeAdd(ref int location1, int value) // diff --git a/src/coreclr/vm/jitinterface.cpp b/src/coreclr/vm/jitinterface.cpp index 54ce75255eeaf6..bb2963cf06de85 100644 --- a/src/coreclr/vm/jitinterface.cpp +++ b/src/coreclr/vm/jitinterface.cpp @@ -1182,7 +1182,7 @@ size_t CEEInfo::getClassThreadStaticDynamicInfo(CORINFO_CLASS_HANDLE cls) EE_TO_JIT_TRANSITION_LEAF(); - return result; + return result; } size_t CEEInfo::getClassStaticDynamicInfo(CORINFO_CLASS_HANDLE cls) @@ -1203,7 +1203,7 @@ size_t CEEInfo::getClassStaticDynamicInfo(CORINFO_CLASS_HANDLE cls) EE_TO_JIT_TRANSITION_LEAF(); - return result; + return result; } CorInfoHelpFunc CEEInfo::getSharedStaticsHelper(FieldDesc * pField, MethodTable * pFieldMT) @@ -1219,7 +1219,7 @@ CorInfoHelpFunc CEEInfo::getSharedStaticsHelper(FieldDesc * pField, MethodTable bool isCollectible = pFieldMT->Collectible(); _ASSERTE(!isInexactMT); CorInfoHelpFunc helper; - + if (threadStatic) { if (GCStatic) @@ -1553,9 +1553,9 @@ void CEEInfo::getFieldInfo (CORINFO_RESOLVED_TOKEN * pResolvedToken, Object* frozenObj = VolatileLoad((Object**)pResult->fieldLookup.addr); _ASSERT(frozenObj != nullptr); - // ContainsPointers here is unnecessary but it's cheaper than IsInFrozenSegment + // ContainsGCPointers here is unnecessary but it's cheaper than IsInFrozenSegment // for structs containing gc handles - if (!frozenObj->GetMethodTable()->ContainsPointers() && + if (!frozenObj->GetMethodTable()->ContainsGCPointers() && GCHeapUtilities::GetGCHeap()->IsInFrozenSegment(frozenObj)) { pResult->fieldLookup.addr = frozenObj->GetData(); @@ -2002,7 +2002,7 @@ unsigned CEEInfo::getClassAlignmentRequirementStatic(TypeHandle clsHnd) } else if (pInfo->IsManagedSequential() || pInfo->IsBlittable()) { - _ASSERTE(!pMT->ContainsPointers()); + _ASSERTE(!pMT->ContainsGCPointers()); // if it's managed sequential, we use the managed alignment requirement result = pInfo->m_ManagedLargestAlignmentRequirementOfAllMembers; @@ -2413,7 +2413,7 @@ unsigned CEEInfo::getClassGClayoutStatic(TypeHandle VMClsHnd, BYTE* gcPtrs) (size + TARGET_POINTER_SIZE - 1) / TARGET_POINTER_SIZE); // walk the GC descriptors, turning on the correct bits - if (pMT->ContainsPointers()) + if (pMT->ContainsGCPointers()) { CGCDesc* map = CGCDesc::GetCGCDescFromMT(pMT); CGCDescSeries * pByValueSeries = map->GetLowestSeries(); @@ -3817,7 +3817,7 @@ uint32_t CEEInfo::getClassAttribsInternal (CORINFO_CLASS_HANDLE clsHnd) if (VMClsHnd.IsCanonicalSubtype()) ret |= CORINFO_FLG_SHAREDINST; - if (pMT->ContainsPointers() || pMT == g_TypedReferenceMT) + if (pMT->ContainsGCPointers() || pMT == g_TypedReferenceMT) ret |= CORINFO_FLG_CONTAINS_GC_PTR; if (pMT->IsDelegate()) @@ -11675,7 +11675,7 @@ bool CEEInfo::getStaticFieldContent(CORINFO_FIELD_HANDLE fieldHnd, uint8_t* buff { TypeHandle structType = field->GetFieldTypeHandleThrowing(); PTR_MethodTable structTypeMT = structType.AsMethodTable(); - if (!structTypeMT->ContainsPointers()) + if (!structTypeMT->ContainsGCPointers()) { // Fast-path: no GC pointers in the struct, we can use memcpy useMemcpy = true; @@ -11774,7 +11774,7 @@ bool CEEInfo::getObjectContent(CORINFO_OBJECT_HANDLE handle, uint8_t* buffer, in { Object* obj = OBJECTREFToObject(objRef); PTR_MethodTable type = obj->GetMethodTable(); - if (type->ContainsPointers()) + if (type->ContainsGCPointers()) { // RuntimeType has a gc field (object m_keepAlive), but if the object is in a frozen segment // it means that field is always nullptr so we can read any part of the object: @@ -13080,7 +13080,7 @@ void ComputeGCRefMap(MethodTable * pMT, BYTE * pGCRefMap, size_t cbGCRefMap) ZeroMemory(pGCRefMap, cbGCRefMap); - if (!pMT->ContainsPointers()) + if (!pMT->ContainsGCPointers()) return; CGCDesc* map = CGCDesc::GetCGCDescFromMT(pMT); @@ -13229,7 +13229,7 @@ BOOL TypeLayoutCheck(MethodTable * pMT, PCCOR_SIGNATURE pBlob, BOOL printDiff) { if (dwFlags & READYTORUN_LAYOUT_GCLayout_Empty) { - if (pMT->ContainsPointers()) + if (pMT->ContainsGCPointers()) { if (printDiff) { diff --git a/src/coreclr/vm/method.cpp b/src/coreclr/vm/method.cpp index 628707417c459e..0a6d6521c683a7 100644 --- a/src/coreclr/vm/method.cpp +++ b/src/coreclr/vm/method.cpp @@ -1192,7 +1192,7 @@ ReturnKind MethodDesc::ParseReturnKindFromSig(INDEBUG(bool supportStringConstruc } #endif // UNIX_AMD64_ABI - if (pReturnTypeMT->ContainsPointers() || pReturnTypeMT->IsByRefLike()) + if (pReturnTypeMT->ContainsGCPointers() || pReturnTypeMT->IsByRefLike()) { if (pReturnTypeMT->GetNumInstanceFields() == 1) { diff --git a/src/coreclr/vm/methodtable.cpp b/src/coreclr/vm/methodtable.cpp index a3e9641bc73e1a..a3b3201af68168 100644 --- a/src/coreclr/vm/methodtable.cpp +++ b/src/coreclr/vm/methodtable.cpp @@ -658,7 +658,7 @@ void MethodTable::AllocateAuxiliaryData(LoaderAllocator *pAllocator, Module *pLo } prependedAllocationSpace = prependedAllocationSpace + sizeofStaticsStructure; - + cbAuxiliaryData = cbAuxiliaryData + S_SIZE_T(prependedAllocationSpace) + extraAllocation; if (cbAuxiliaryData.IsOverflow()) ThrowHR(COR_E_OVERFLOW); @@ -1569,7 +1569,7 @@ MethodTable::IsExternallyVisible() BOOL MethodTable::IsAllGCPointers() { - if (this->ContainsPointers()) + if (this->ContainsGCPointers()) { // check for canonical GC encoding for all-pointer types CGCDesc* pDesc = CGCDesc::GetCGCDescFromMT(this); @@ -3351,7 +3351,7 @@ void MethodTable::AllocateRegularStaticBox(FieldDesc* pField, Object** boxedStat bool hasFixedAddr = HasFixedAddressVTStatics(); LOG((LF_CLASSLOADER, LL_INFO10000, "\tInstantiating static of type %s\n", pFieldMT->GetDebugClassName())); - const bool canBeFrozen = !pFieldMT->ContainsPointers() && !Collectible(); + const bool canBeFrozen = !pFieldMT->ContainsGCPointers() && !Collectible(); OBJECTREF obj = AllocateStaticBox(pFieldMT, hasFixedAddr, canBeFrozen); SetObjectReference((OBJECTREF*)(boxedStaticHandle), obj); GCPROTECT_END(); @@ -3377,7 +3377,7 @@ OBJECTREF MethodTable::AllocateStaticBox(MethodTable* pFieldMT, BOOL fPinned, bo if (canBeFrozen) { // In case if we don't plan to collect this handle we may try to allocate it on FOH - _ASSERT(!pFieldMT->ContainsPointers()); + _ASSERT(!pFieldMT->ContainsGCPointers()); FrozenObjectHeapManager* foh = SystemDomain::GetFrozenObjectHeapManager(); obj = ObjectToOBJECTREF(foh->TryAllocateObject(pFieldMT, pFieldMT->GetBaseSize())); // obj can be null in case if struct is huge (>64kb) @@ -3796,7 +3796,7 @@ void MethodTable::AttemptToPreinit() // If there is a class constructor, then the class cannot be preinitted. return; } - + if (GetClass()->GetNonGCRegularStaticFieldBytes() == 0 && GetClass()->GetNumHandleRegularStatics() == 0) { // If there are static fields that are not thread statics, then the class is preinitted. @@ -7417,7 +7417,7 @@ MethodTable::EnumMemoryRegions(CLRDataEnumMemoryFlags flags) DacEnumMemoryRegion(dac_cast(this), size); // Make sure the GCDescs are added to the dump - if (ContainsPointers()) + if (ContainsGCPointers()) { PTR_CGCDesc gcdesc = CGCDesc::GetCGCDescFromMT(this); size_t size = gcdesc->GetSize(); diff --git a/src/coreclr/vm/methodtable.h b/src/coreclr/vm/methodtable.h index 865284d43e536b..663803243164ae 100644 --- a/src/coreclr/vm/methodtable.h +++ b/src/coreclr/vm/methodtable.h @@ -1754,10 +1754,10 @@ class MethodTable inline WORD GetNumIntroducedInstanceFields(); - BOOL ContainsPointers() + BOOL ContainsGCPointers() { LIMITED_METHOD_CONTRACT; - return !!GetFlag(enum_flag_ContainsPointers); + return !!GetFlag(enum_flag_ContainsGCPointers); } BOOL Collectible() @@ -1770,10 +1770,10 @@ class MethodTable #endif } - BOOL ContainsPointersOrCollectible() + BOOL ContainsGCPointersOrCollectible() { LIMITED_METHOD_CONTRACT; - return GetFlag(enum_flag_ContainsPointers) || GetFlag(enum_flag_Collectible); + return GetFlag(enum_flag_ContainsGCPointers) || GetFlag(enum_flag_Collectible); } OBJECTHANDLE GetLoaderAllocatorObjectHandle(); @@ -1783,10 +1783,10 @@ class MethodTable BOOL IsAllGCPointers(); - void SetContainsPointers() + void SetContainsGCPointers() { LIMITED_METHOD_CONTRACT; - SetFlag(enum_flag_ContainsPointers); + SetFlag(enum_flag_ContainsGCPointers); } #ifdef FEATURE_64BIT_ALIGNMENT @@ -3573,7 +3573,7 @@ public : enum_flag_RequiresAlign8 = 0x00800000, // Type requires 8-byte alignment (only set on platforms that require this and don't get it implicitly) #endif - enum_flag_ContainsPointers = 0x01000000, // Contains object references + enum_flag_ContainsGCPointers = 0x01000000, // Contains object references enum_flag_HasTypeEquivalence = 0x02000000, // can be equivalent to another type enum_flag_IsTrackedReferenceWithFinalizer = 0x04000000, // unused = 0x08000000, diff --git a/src/coreclr/vm/methodtablebuilder.cpp b/src/coreclr/vm/methodtablebuilder.cpp index 7305719e21a516..124dd62fd0da4b 100644 --- a/src/coreclr/vm/methodtablebuilder.cpp +++ b/src/coreclr/vm/methodtablebuilder.cpp @@ -1878,7 +1878,7 @@ MethodTableBuilder::BuildMethodTableThrowing( // GC reqires the series to be sorted. // TODO: fix it so that we emit them in the correct order in the first place. - if (pMT->ContainsPointers()) + if (pMT->ContainsGCPointers()) { CGCDesc* gcDesc = CGCDesc::GetCGCDescFromMT(pMT); qsort(gcDesc->GetLowestSeries(), (int)gcDesc->GetNumSeries(), sizeof(CGCDescSeries), compareCGCDescSeries); @@ -1907,7 +1907,7 @@ MethodTableBuilder::BuildMethodTableThrowing( // // structs with GC pointers MUST be pointer sized aligned because the GC assumes it - if (IsValueClass() && pMT->ContainsPointers() && (bmtFP->NumInstanceFieldBytes % TARGET_POINTER_SIZE != 0)) + if (IsValueClass() && pMT->ContainsGCPointers() && (bmtFP->NumInstanceFieldBytes % TARGET_POINTER_SIZE != 0)) { BuildMethodTableThrowException(IDS_CLASSLOAD_BADFORMAT); } @@ -2098,7 +2098,7 @@ MethodTableBuilder::ResolveInterfaces( MethodTable * pParentClass = GetParentMethodTable(); PREFIX_ASSUME(pParentClass != NULL); - bmtParent->NumParentPointerSeries = pParentClass->ContainsPointers() ? + bmtParent->NumParentPointerSeries = pParentClass->ContainsGCPointers() ? (DWORD)CGCDesc::GetCGCDescFromMT(pParentClass)->GetNumSeries() : 0; if (pParentClass->HasFieldsWhichMustBeInited()) @@ -8317,7 +8317,7 @@ VOID MethodTableBuilder::PlaceInstanceFields(MethodTable ** pByValueClassCach } else #endif // FEATURE_64BIT_ALIGNMENT - if (pByValueMT->ContainsPointers()) + if (pByValueMT->ContainsGCPointers()) { // this field type has GC pointers in it, which need to be pointer-size aligned // so do this if it has not been done already @@ -8335,13 +8335,13 @@ VOID MethodTableBuilder::PlaceInstanceFields(MethodTable ** pByValueClassCach pFieldDescList[i].SetOffset(dwCumulativeInstanceFieldPos - dwOffsetBias); dwCumulativeInstanceFieldPos += pByValueMT->GetNumInstanceFieldBytes(); - if (pByValueMT->ContainsPointers()) + if (pByValueMT->ContainsGCPointers()) { // Add pointer series for by-value classes dwNumGCPointerSeries += (DWORD)CGCDesc::GetCGCDescFromMT(pByValueMT)->GetNumSeries(); } - if (!pByValueMT->ContainsPointers() || !pByValueMT->IsAllGCPointers()) + if (!pByValueMT->ContainsGCPointers() || !pByValueMT->IsAllGCPointers()) { isAllGCPointers = false; } @@ -8684,7 +8684,7 @@ MethodTableBuilder::HandleExplicitLayout( else { MethodTable *pByValueMT = pByValueClassCache[valueClassCacheIndex]; - if (pByValueMT->IsByRefLike() || pByValueMT->ContainsPointers()) + if (pByValueMT->IsByRefLike() || pByValueMT->ContainsGCPointers()) { if ((pFD->GetOffset() & ((ULONG)TARGET_POINTER_SIZE - 1)) != 0) { @@ -8881,7 +8881,7 @@ MethodTableBuilder::HandleExplicitLayout( memset((void*)vcLayout, nonoref, fieldSize); // If the type contains pointers fill it out from the GC data - if (pMT->ContainsPointers()) + if (pMT->ContainsGCPointers()) { // use pointer series to locate the orefs CGCDesc* map = CGCDesc::GetCGCDescFromMT(pMT); @@ -9097,7 +9097,7 @@ MethodTableBuilder::HandleGCForExplicitLayout() if (bmtFP->NumGCPointerSeries != 0) { - pMT->SetContainsPointers(); + pMT->SetContainsGCPointers(); // Copy the pointer series map from the parent CGCDesc::Init( (PVOID) pMT, bmtFP->NumGCPointerSeries ); @@ -10643,7 +10643,7 @@ MethodTableBuilder::SetupMethodTable2( pMT->SetHasClassConstructor(); CONSISTENCY_CHECK(pMT->GetClassConstructorSlot() == bmtVT->pCCtor->GetSlotIndex()); } - + if (bmtVT->pDefaultCtor != NULL) { pMT->SetHasDefaultConstructor(); @@ -11569,7 +11569,7 @@ VOID MethodTableBuilder::HandleGCForValueClasses(MethodTable ** pByValueClassCac CGCDescSeries *pSeries; CGCDescSeries *pHighest; - pMT->SetContainsPointers(); + pMT->SetContainsGCPointers(); CGCDesc::Init( (PVOID) pMT, bmtFP->NumGCPointerSeries ); @@ -11625,7 +11625,7 @@ VOID MethodTableBuilder::HandleGCForValueClasses(MethodTable ** pByValueClassCac { MethodTable* pByValueMT = pByValueClassCache[i]; - if (pByValueMT->ContainsPointers()) + if (pByValueMT->ContainsGCPointers()) { // Offset of the by value class in the class we are building, does NOT include Object DWORD dwCurrentOffset = pFieldDescList[i].GetOffset(); diff --git a/src/coreclr/vm/mlinfo.cpp b/src/coreclr/vm/mlinfo.cpp index f3401ae2c8fd7b..8131e4fd3053a4 100644 --- a/src/coreclr/vm/mlinfo.cpp +++ b/src/coreclr/vm/mlinfo.cpp @@ -1174,7 +1174,7 @@ namespace TypeHandle sigTH = sig.GetTypeHandleThrowing(pModule, pTypeContext); MethodTable* pMT = sigTH.GetMethodTable(); - if (!pMT->IsValueType() || pMT->ContainsPointers()) + if (!pMT->IsValueType() || pMT->ContainsGCPointers()) { *errorResIDOut = IDS_EE_BADMARSHAL_MARSHAL_DISABLED; return MarshalInfo::MARSHAL_TYPE_UNKNOWN; diff --git a/src/coreclr/vm/object.cpp b/src/coreclr/vm/object.cpp index a41c3acfcd4bbe..88a57909297876 100644 --- a/src/coreclr/vm/object.cpp +++ b/src/coreclr/vm/object.cpp @@ -364,7 +364,7 @@ void STDCALL CopyValueClassUnchecked(void* dest, void* src, MethodTable *pMT) _ASSERTE(!pMT->IsArray()); // bunch of assumptions about arrays wrong. - if (pMT->ContainsPointers()) + if (pMT->ContainsGCPointers()) { memmoveGCRefs(dest, src, pMT->GetNumInstanceFieldBytes()); } diff --git a/src/coreclr/vm/proftoeeinterfaceimpl.cpp b/src/coreclr/vm/proftoeeinterfaceimpl.cpp index 7bbef439813a22..dc5d4694520675 100644 --- a/src/coreclr/vm/proftoeeinterfaceimpl.cpp +++ b/src/coreclr/vm/proftoeeinterfaceimpl.cpp @@ -1166,7 +1166,7 @@ bool HeapWalkHelper(Object * pBO, void * pvContext) ProfilerWalkHeapContext * pProfilerWalkHeapContext = (ProfilerWalkHeapContext *) pvContext; - if (pMT->ContainsPointersOrCollectible()) + if (pMT->ContainsGCPointersOrCollectible()) { // First round through calculates the number of object refs for this class GCHeapUtilities::GetGCHeap()->DiagWalkObject(pBO, &CountContainedObjectRef, (void *)&cNumRefs); @@ -6747,7 +6747,7 @@ HRESULT ProfToEEInterfaceImpl::EnumerateObjectReferences(ObjectID objectId, Obje Object* pBO = (Object*)objectId; MethodTable *pMT = pBO->GetMethodTable(); - if (pMT->ContainsPointersOrCollectible()) + if (pMT->ContainsGCPointersOrCollectible()) { GCHeapUtilities::GetGCHeap()->DiagWalkObject2(pBO, (walk_fn2)callback, clientData); return S_OK; diff --git a/src/coreclr/vm/reflectioninvocation.cpp b/src/coreclr/vm/reflectioninvocation.cpp index a98dafd62a558b..b1daf1cc4cd550 100644 --- a/src/coreclr/vm/reflectioninvocation.cpp +++ b/src/coreclr/vm/reflectioninvocation.cpp @@ -647,7 +647,7 @@ FCIMPL4(Object*, RuntimeMethodHandle::InvokeMethod, *(PVOID *)pArgDst = pStackCopy; // save the info into ValueClassInfo - if (pMT->ContainsPointers()) + if (pMT->ContainsGCPointers()) { pValueClasses = new (_alloca(sizeof(ValueClassInfo))) ValueClassInfo(pStackCopy, pMT, pValueClasses); } @@ -2114,4 +2114,4 @@ extern "C" void QCALLTYPE ReflectionInvocation_GetBoxInfo( pMT->EnsureInstanceActive(); END_QCALL; -} \ No newline at end of file +} diff --git a/src/coreclr/vm/siginfo.cpp b/src/coreclr/vm/siginfo.cpp index 108e95cc39e53c..facb809cd4841a 100644 --- a/src/coreclr/vm/siginfo.cpp +++ b/src/coreclr/vm/siginfo.cpp @@ -5062,7 +5062,7 @@ void ReportPointersFromValueType(promote_func *fn, ScanContext *sc, PTR_MethodTa reporter.Find(pMT, 0 /* baseOffset */); } - if (!pMT->ContainsPointers()) + if (!pMT->ContainsGCPointers()) return; CGCDesc* map = CGCDesc::GetCGCDescFromMT(pMT); @@ -5091,7 +5091,7 @@ void ReportPointersFromValueTypeArg(promote_func *fn, ScanContext *sc, PTR_Metho { WRAPPER_NO_CONTRACT; - if (!pMT->ContainsPointers() && !pMT->IsByRefLike()) + if (!pMT->ContainsGCPointers() && !pMT->IsByRefLike()) { return; } diff --git a/src/coreclr/vm/stubgen.cpp b/src/coreclr/vm/stubgen.cpp index 29595b9414d514..283b6d3fa2d883 100644 --- a/src/coreclr/vm/stubgen.cpp +++ b/src/coreclr/vm/stubgen.cpp @@ -2657,7 +2657,7 @@ void ILStubLinker::TransformArgForJIT(LocalDesc *pLoc) // JIT will handle structures if (pLoc->InternalToken.IsValueType()) { - _ASSERTE(pLoc->InternalToken.IsNativeValueType() || !pLoc->InternalToken.GetMethodTable()->ContainsPointers()); + _ASSERTE(pLoc->InternalToken.IsNativeValueType() || !pLoc->InternalToken.GetMethodTable()->ContainsGCPointers()); break; } FALLTHROUGH; diff --git a/src/coreclr/vm/tailcallhelp.cpp b/src/coreclr/vm/tailcallhelp.cpp index e9fb3a75852ea1..4d9c60838b54f5 100644 --- a/src/coreclr/vm/tailcallhelp.cpp +++ b/src/coreclr/vm/tailcallhelp.cpp @@ -281,7 +281,7 @@ bool TailCallHelp::GenerateGCDescriptor( TypeHandle tyHnd = val.TyHnd; if (tyHnd.IsValueType()) { - if (!tyHnd.GetMethodTable()->ContainsPointers()) + if (!tyHnd.GetMethodTable()->ContainsGCPointers()) { #ifndef TARGET_X86 // The generic instantiation arg is right after this pointer From 0f8c7f1f0bcbbfa0c4b7d941b5847c3da52c5737 Mon Sep 17 00:00:00 2001 From: Aleksey Kliger Date: Fri, 21 Jun 2024 12:12:59 -0400 Subject: [PATCH 34/62] code style suggestions from code review --- src/coreclr/debug/daccess/request.cpp | 62 +++++++++---------- src/coreclr/inc/dacprivate.h | 1 + .../cdacreader/src/Contracts/Metadata_1.cs | 4 +- .../cdacreader/src/Legacy/SOSDacImpl.cs | 2 + 4 files changed, 33 insertions(+), 36 deletions(-) diff --git a/src/coreclr/debug/daccess/request.cpp b/src/coreclr/debug/daccess/request.cpp index 63f818affcd9c7..8f325dcfa00374 100644 --- a/src/coreclr/debug/daccess/request.cpp +++ b/src/coreclr/debug/daccess/request.cpp @@ -1839,37 +1839,34 @@ ClrDataAccess::GetMethodTableDataImpl(CLRDATA_ADDRESS mt, struct DacpMethodTable { return E_INVALIDARG; } - else - { - ZeroMemory(MTData,sizeof(DacpMethodTableData)); - MTData->BaseSize = pMT->GetBaseSize(); - // [compat] SOS DAC APIs added this base size adjustment for strings - // due to: "2008/09/25 Title: New implementation of StringBuilder and improvements in String class" - // which changed StringBuilder not to use a String as an internal buffer and in the process - // changed the String internals so that StringObject::GetBaseSize() now includes the nul terminator character, - // which is apparently not expected by SOS. - if(pMT->IsString()) - MTData->BaseSize -= sizeof(WCHAR); - MTData->ComponentSize = (DWORD)pMT->GetComponentSize(); - MTData->bIsFree = bIsFree; - if(!bIsFree) - { - MTData->Module = HOST_CDADDR(pMT->GetModule()); - // Note: DacpMethodTableData::Class is really a pointer to the canonical method table - MTData->Class = HOST_CDADDR(pMT->GetClass()->GetMethodTable()); - MTData->ParentMethodTable = HOST_CDADDR(pMT->GetParentMethodTable());; - MTData->wNumInterfaces = (WORD)pMT->GetNumInterfaces(); - MTData->wNumMethods = pMT->GetNumMethods(); - MTData->wNumVtableSlots = pMT->GetNumVtableSlots(); - MTData->wNumVirtuals = pMT->GetNumVirtuals(); - MTData->cl = pMT->GetCl(); - MTData->dwAttrClass = pMT->GetAttrClass(); - MTData->bContainsPointers = pMT->ContainsGCPointers(); - MTData->bIsShared = FALSE; - MTData->bIsDynamic = pMT->IsDynamicStatics(); - } - } + ZeroMemory(MTData,sizeof(DacpMethodTableData)); + MTData->BaseSize = pMT->GetBaseSize(); + // [compat] SOS DAC APIs added this base size adjustment for strings + // due to: "2008/09/25 Title: New implementation of StringBuilder and improvements in String class" + // which changed StringBuilder not to use a String as an internal buffer and in the process + // changed the String internals so that StringObject::GetBaseSize() now includes the nul terminator character, + // which is apparently not expected by SOS. + if(pMT->IsString()) + MTData->BaseSize -= sizeof(WCHAR); + MTData->ComponentSize = (DWORD)pMT->GetComponentSize(); + MTData->bIsFree = bIsFree; + if(!bIsFree) + { + MTData->Module = HOST_CDADDR(pMT->GetModule()); + // Note: DacpMethodTableData::Class is really a pointer to the canonical method table + MTData->Class = HOST_CDADDR(pMT->GetClass()->GetMethodTable()); + MTData->ParentMethodTable = HOST_CDADDR(pMT->GetParentMethodTable());; + MTData->wNumInterfaces = (WORD)pMT->GetNumInterfaces(); + MTData->wNumMethods = pMT->GetNumMethods(); + MTData->wNumVtableSlots = pMT->GetNumVtableSlots(); + MTData->wNumVirtuals = pMT->GetNumVirtuals(); + MTData->cl = pMT->GetCl(); + MTData->dwAttrClass = pMT->GetAttrClass(); + MTData->bContainsPointers = pMT->ContainsGCPointers(); + MTData->bIsShared = FALSE; + MTData->bIsDynamic = pMT->IsDynamicStatics(); + } return S_OK; } @@ -2157,11 +2154,8 @@ ClrDataAccess::GetMethodTableForEEClassImpl(CLRDATA_ADDRESS eeClassReallyCanonMT { return E_INVALIDARG; } - else - { - *value = HOST_CDADDR(pCanonMT); - } + *value = HOST_CDADDR(pCanonMT); return S_OK; } diff --git a/src/coreclr/inc/dacprivate.h b/src/coreclr/inc/dacprivate.h index ae91e940ce22fd..05e07b42c0b784 100644 --- a/src/coreclr/inc/dacprivate.h +++ b/src/coreclr/inc/dacprivate.h @@ -274,6 +274,7 @@ struct MSLAYOUT DacpMethodTableData { BOOL bIsFree = FALSE; // everything else is NULL if this is true. CLRDATA_ADDRESS Module = 0; + // Note: DacpMethodTableData::Class is really a pointer to the canonical method table CLRDATA_ADDRESS Class = 0; CLRDATA_ADDRESS ParentMethodTable = 0; WORD wNumInterfaces = 0; diff --git a/src/native/managed/cdacreader/src/Contracts/Metadata_1.cs b/src/native/managed/cdacreader/src/Contracts/Metadata_1.cs index ef296f79c38707..41fc20eabac28f 100644 --- a/src/native/managed/cdacreader/src/Contracts/Metadata_1.cs +++ b/src/native/managed/cdacreader/src/Contracts/Metadata_1.cs @@ -30,7 +30,7 @@ internal UntrustedMethodTable_1(Target target, TargetPointer methodTablePointer) _methodTableFlags = null; } - private Metadata_1.MethodTableFlags EnsureFlags() + private Metadata_1.MethodTableFlags GetOrCreateFlags() { if (_methodTableFlags == null) { @@ -46,7 +46,7 @@ private Metadata_1.MethodTableFlags EnsureFlags() return _methodTableFlags.Value; } - internal Metadata_1.MethodTableFlags Flags => EnsureFlags(); + internal Metadata_1.MethodTableFlags Flags => GetOrCreateFlags(); internal TargetPointer EEClassOrCanonMT => _target.ReadPointer(Address + (ulong)_type.Fields[nameof(EEClassOrCanonMT)].Offset); internal TargetPointer EEClass => Metadata_1.GetEEClassOrCanonMTBits(EEClassOrCanonMT) == Metadata_1.EEClassOrCanonMTBits.EEClass ? EEClassOrCanonMT : throw new InvalidOperationException("not an EEClass"); diff --git a/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs b/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs index c85a4a6f86fa03..499df64bfac936 100644 --- a/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs +++ b/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs @@ -86,6 +86,7 @@ public unsafe int GetMethodTableData(ulong mt, DacpMethodTableData* data) { if (mt == 0 || data == null) return HResults.E_INVALIDARG; + try { Contracts.IMetadata contract = _target.Contracts.Metadata; @@ -133,6 +134,7 @@ public unsafe int GetMethodTableForEEClass(ulong eeClassReallyCanonMT, ulong* va { if (eeClassReallyCanonMT == 0 || value == null) return HResults.E_INVALIDARG; + try { Contracts.IMetadata contract = _target.Contracts.Metadata; From 7a337c16e1fcd788832296af413200022a89f9fb Mon Sep 17 00:00:00 2001 From: Aleksey Kliger Date: Fri, 21 Jun 2024 12:14:13 -0400 Subject: [PATCH 35/62] BUGFIX: read DwFlags2 from the correct offset --- src/native/managed/cdacreader/src/Contracts/Metadata_1.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/native/managed/cdacreader/src/Contracts/Metadata_1.cs b/src/native/managed/cdacreader/src/Contracts/Metadata_1.cs index 41fc20eabac28f..5aef200bd1cd1a 100644 --- a/src/native/managed/cdacreader/src/Contracts/Metadata_1.cs +++ b/src/native/managed/cdacreader/src/Contracts/Metadata_1.cs @@ -38,7 +38,7 @@ private Metadata_1.MethodTableFlags GetOrCreateFlags() Metadata_1.MethodTableFlags flags = new Metadata_1.MethodTableFlags { DwFlags = _target.Read(Address + (ulong)_type.Fields[nameof(Metadata_1.MethodTableFlags.DwFlags)].Offset), - DwFlags2 = _target.Read(Address + (ulong)_type.Fields[nameof(Metadata_1.MethodTableFlags.DwFlags)].Offset), + DwFlags2 = _target.Read(Address + (ulong)_type.Fields[nameof(Metadata_1.MethodTableFlags.DwFlags2)].Offset), BaseSize = _target.Read(Address + (ulong)_type.Fields[nameof(Metadata_1.MethodTableFlags.BaseSize)].Offset), }; _methodTableFlags = flags; From a7c8158bdfa1901dc80439fe30ff5546be9dc70c Mon Sep 17 00:00:00 2001 From: Aleksey Kliger Date: Fri, 21 Jun 2024 12:16:56 -0400 Subject: [PATCH 36/62] hide utility methods --- .../cdacreader/src/Contracts/Metadata_1.MethodTableFlags.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/native/managed/cdacreader/src/Contracts/Metadata_1.MethodTableFlags.cs b/src/native/managed/cdacreader/src/Contracts/Metadata_1.MethodTableFlags.cs index 51eede7725c47b..17d9d2f01cc587 100644 --- a/src/native/managed/cdacreader/src/Contracts/Metadata_1.MethodTableFlags.cs +++ b/src/native/managed/cdacreader/src/Contracts/Metadata_1.MethodTableFlags.cs @@ -59,7 +59,7 @@ internal struct MethodTableFlags public bool IsStringOrArray => HasComponentSize; public ushort RawGetComponentSize() => (ushort)(DwFlags >> 16); - public bool TestFlagWithMask(WFLAGS_LOW mask, WFLAGS_LOW flag) + private bool TestFlagWithMask(WFLAGS_LOW mask, WFLAGS_LOW flag) { if (IsStringOrArray) { @@ -71,7 +71,7 @@ public bool TestFlagWithMask(WFLAGS_LOW mask, WFLAGS_LOW flag) } } - public bool TestFlagWithMask(WFLAGS2_ENUM mask, WFLAGS2_ENUM flag) + private bool TestFlagWithMask(WFLAGS2_ENUM mask, WFLAGS2_ENUM flag) { return ((WFLAGS2_ENUM)DwFlags2 & mask) == flag; } From f4a3493e8534090c67e4d34f3bb87af07c9c70f5 Mon Sep 17 00:00:00 2001 From: Aleksey Kliger Date: Fri, 21 Jun 2024 12:23:57 -0400 Subject: [PATCH 37/62] remove EEClass_1 struct use Data.EEClass directly in places where we trust an EEClass pointer --- .../cdacreader/src/Contracts/Metadata_1.cs | 33 +++---------------- 1 file changed, 5 insertions(+), 28 deletions(-) diff --git a/src/native/managed/cdacreader/src/Contracts/Metadata_1.cs b/src/native/managed/cdacreader/src/Contracts/Metadata_1.cs index 5aef200bd1cd1a..70e0c787099ab9 100644 --- a/src/native/managed/cdacreader/src/Contracts/Metadata_1.cs +++ b/src/native/managed/cdacreader/src/Contracts/Metadata_1.cs @@ -108,22 +108,6 @@ internal MethodTable_1(Data.MethodTable data) } } -internal struct EEClass_1 -{ - internal TargetPointer MethodTable { get; } - internal ushort NumMethods { get; } - internal ushort NumNonVirtualSlots { get; } - internal uint TypeDefTypeAttributes { get; } - internal EEClass_1(Data.EEClass eeClassData) - { - MethodTable = eeClassData.MethodTable; - NumMethods = eeClassData.NumMethods; - NumNonVirtualSlots = eeClassData.NumNonVirtualSlots; - TypeDefTypeAttributes = eeClassData.DwAttrClass; - } -} - - internal partial struct Metadata_1 : IMetadata { private readonly Target _target; @@ -303,16 +287,16 @@ private static uint GetComponentSize(MethodTable_1 methodTable) public uint GetComponentSize(MethodTableHandle methodTableHandle) => GetComponentSize(_methodTables[methodTableHandle.Address]); // only called on trusted method tables, so we always trust the resulting EEClass - private EEClass_1 GetClassData(MethodTableHandle methodTableHandle) + private Data.EEClass GetClassData(MethodTableHandle methodTableHandle) { TargetPointer clsPtr = GetClass(methodTableHandle); // Check if we cached it already if (_target.ProcessedData.TryGet(clsPtr, out Data.EEClass? eeClassData)) { - return new EEClass_1(eeClassData); + return eeClassData; } eeClassData = _target.ProcessedData.GetOrAdd(clsPtr); - return new EEClass_1(eeClassData); + return eeClassData; } private TargetPointer GetClass(MethodTableHandle methodTableHandle) @@ -347,11 +331,7 @@ public uint GetTypeDefToken(MethodTableHandle methodTableHandle) return (uint)(methodTable.Flags.GetTypeDefRid() | ((int)TableIndex.TypeDef << 24)); } - public ushort GetNumMethods(MethodTableHandle methodTableHandle) - { - EEClass_1 cls = GetClassData(methodTableHandle); - return cls.NumMethods; - } + public ushort GetNumMethods(MethodTableHandle methodTableHandle) => GetClassData(methodTableHandle).NumMethods; public ushort GetNumInterfaces(MethodTableHandle methodTableHandle) => _methodTables[methodTableHandle.Address].NumInterfaces; @@ -375,10 +355,7 @@ public ushort GetNumVtableSlots(MethodTableHandle methodTableHandle) return checked((ushort)(GetNumVirtuals(methodTableHandle) + GetNumNonVirtualSlots(methodTableHandle))); } - public uint GetTypeDefTypeAttributes(MethodTableHandle methodTableHandle) - { - return GetClassData(methodTableHandle).TypeDefTypeAttributes; - } + public uint GetTypeDefTypeAttributes(MethodTableHandle methodTableHandle) => GetClassData(methodTableHandle).DwAttrClass; public bool IsDynamicStatics(MethodTableHandle methodTableHandle) => _methodTables[methodTableHandle.Address].Flags.GetFlag(WFLAGS2_ENUM.DynamicStatics) != 0; From 65cc531753514cd6f58f65bef08d9ae09b59d340 Mon Sep 17 00:00:00 2001 From: Aleksey Kliger Date: Fri, 21 Jun 2024 12:38:45 -0400 Subject: [PATCH 38/62] rename data descriptor members to remove prefixes --- .../debug/runtimeinfo/datadescriptor.h | 26 +++++++++---------- src/coreclr/vm/class.h | 8 +++--- src/coreclr/vm/methodtable.h | 18 ++++++------- .../cdacreader/src/Contracts/Metadata_1.cs | 2 +- .../managed/cdacreader/src/Data/EEClass.cs | 4 +-- 5 files changed, 29 insertions(+), 29 deletions(-) diff --git a/src/coreclr/debug/runtimeinfo/datadescriptor.h b/src/coreclr/debug/runtimeinfo/datadescriptor.h index 068c8f89bce8d2..8116313bf60170 100644 --- a/src/coreclr/debug/runtimeinfo/datadescriptor.h +++ b/src/coreclr/debug/runtimeinfo/datadescriptor.h @@ -158,23 +158,23 @@ CDAC_TYPE_END(GCHandle) CDAC_TYPE_BEGIN(MethodTable) CDAC_TYPE_INDETERMINATE(MethodTable) -CDAC_TYPE_FIELD(MethodTable, /*uint32*/, DwFlags, cdac_offsets::m_dwFlags) -CDAC_TYPE_FIELD(MethodTable, /*uint32*/, BaseSize, cdac_offsets::m_BaseSize) -CDAC_TYPE_FIELD(MethodTable, /*uint32*/, DwFlags2, cdac_offsets::m_dwFlags2) -CDAC_TYPE_FIELD(MethodTable, /*nuint*/, EEClassOrCanonMT, cdac_offsets::m_pEEClassOrCanonMT) -CDAC_TYPE_FIELD(MethodTable, /*pointer*/, Module, cdac_offsets::m_pModule) -CDAC_TYPE_FIELD(MethodTable, /*pointer*/, AuxiliaryData, cdac_offsets::m_pAuxiliaryData) -CDAC_TYPE_FIELD(MethodTable, /*pointer*/, ParentMethodTable, cdac_offsets::m_pParentMethodTable) -CDAC_TYPE_FIELD(MethodTable, /*uint16*/, NumInterfaces, cdac_offsets::m_wNumInterfaces) -CDAC_TYPE_FIELD(MethodTable, /*uint16*/, NumVirtuals, cdac_offsets::m_wNumVirtuals) +CDAC_TYPE_FIELD(MethodTable, /*uint32*/, DwFlags, cdac_offsets::DwFlags) +CDAC_TYPE_FIELD(MethodTable, /*uint32*/, BaseSize, cdac_offsets::BaseSize) +CDAC_TYPE_FIELD(MethodTable, /*uint32*/, DwFlags2, cdac_offsets::DwFlags2) +CDAC_TYPE_FIELD(MethodTable, /*nuint*/, EEClassOrCanonMT, cdac_offsets::EEClassOrCanonMT) +CDAC_TYPE_FIELD(MethodTable, /*pointer*/, Module, cdac_offsets::Module) +CDAC_TYPE_FIELD(MethodTable, /*pointer*/, AuxiliaryData, cdac_offsets::AuxiliaryData) +CDAC_TYPE_FIELD(MethodTable, /*pointer*/, ParentMethodTable, cdac_offsets::ParentMethodTable) +CDAC_TYPE_FIELD(MethodTable, /*uint16*/, NumInterfaces, cdac_offsets::NumInterfaces) +CDAC_TYPE_FIELD(MethodTable, /*uint16*/, NumVirtuals, cdac_offsets::NumVirtuals) CDAC_TYPE_END(MethodTable) CDAC_TYPE_BEGIN(EEClass) CDAC_TYPE_INDETERMINATE(EEClass) -CDAC_TYPE_FIELD(EEClass, /*pointer*/, MethodTable, cdac_offsets::m_pMethodTable) -CDAC_TYPE_FIELD(EEClass, /*uint16*/, NumMethods, cdac_offsets::m_NumMethods) -CDAC_TYPE_FIELD(EEClass, /*uint16*/, NumNonVirtualSlots, cdac_offsets::m_NumNonVirtualSlots) -CDAC_TYPE_FIELD(EEClass, /*uint32*/, DwAttrClass, cdac_offsets::m_dwAttrClass) +CDAC_TYPE_FIELD(EEClass, /*pointer*/, MethodTable, cdac_offsets::MethodTable) +CDAC_TYPE_FIELD(EEClass, /*uint16*/, NumMethods, cdac_offsets::NumMethods) +CDAC_TYPE_FIELD(EEClass, /*uint16*/, NumNonVirtualSlots, cdac_offsets::NumNonVirtualSlots) +CDAC_TYPE_FIELD(EEClass, /*uint32*/, AttrClass, cdac_offsets::AttrClass) CDAC_TYPE_END(EEClass) CDAC_TYPE_BEGIN(MethodTableAuxiliaryData) diff --git a/src/coreclr/vm/class.h b/src/coreclr/vm/class.h index 4ec100f5306bd0..a247342360515a 100644 --- a/src/coreclr/vm/class.h +++ b/src/coreclr/vm/class.h @@ -1803,10 +1803,10 @@ class EEClass // DO NOT CREATE A NEW EEClass USING NEW! template<> struct cdac_offsets { - static constexpr size_t m_pMethodTable = offsetof(EEClass, m_pMethodTable); - static constexpr size_t m_NumMethods = offsetof(EEClass, m_NumMethods); - static constexpr size_t m_NumNonVirtualSlots = offsetof(EEClass, m_NumNonVirtualSlots); - static constexpr size_t m_dwAttrClass = offsetof(EEClass, m_dwAttrClass); + static constexpr size_t MethodTable = offsetof(EEClass, m_pMethodTable); + static constexpr size_t NumMethods = offsetof(EEClass, m_NumMethods); + static constexpr size_t NumNonVirtualSlots = offsetof(EEClass, m_NumNonVirtualSlots); + static constexpr size_t AttrClass = offsetof(EEClass, m_dwAttrClass); }; // -------------------------------------------------------------------------------------------- diff --git a/src/coreclr/vm/methodtable.h b/src/coreclr/vm/methodtable.h index 663803243164ae..db6e8d0e69bc2a 100644 --- a/src/coreclr/vm/methodtable.h +++ b/src/coreclr/vm/methodtable.h @@ -3864,15 +3864,15 @@ public : template<> struct cdac_offsets { - static constexpr size_t m_dwFlags = offsetof(MethodTable, m_dwFlags); - static constexpr size_t m_BaseSize = offsetof(MethodTable, m_BaseSize); - static constexpr size_t m_dwFlags2 = offsetof(MethodTable, m_dwFlags2); - static constexpr size_t m_pEEClassOrCanonMT = offsetof(MethodTable, m_pEEClass); - static constexpr size_t m_pModule = offsetof(MethodTable, m_pModule); - static constexpr size_t m_pAuxiliaryData = offsetof(MethodTable, m_pAuxiliaryData); - static constexpr size_t m_pParentMethodTable = offsetof(MethodTable, m_pParentMethodTable); - static constexpr size_t m_wNumInterfaces = offsetof(MethodTable, m_wNumInterfaces); - static constexpr size_t m_wNumVirtuals = offsetof(MethodTable, m_wNumVirtuals); + static constexpr size_t DwFlags = offsetof(MethodTable, m_dwFlags); + static constexpr size_t BaseSize = offsetof(MethodTable, m_BaseSize); + static constexpr size_t DwFlags2 = offsetof(MethodTable, m_dwFlags2); + static constexpr size_t EEClassOrCanonMT = offsetof(MethodTable, m_pEEClass); + static constexpr size_t Module = offsetof(MethodTable, m_pModule); + static constexpr size_t AuxiliaryData = offsetof(MethodTable, m_pAuxiliaryData); + static constexpr size_t ParentMethodTable = offsetof(MethodTable, m_pParentMethodTable); + static constexpr size_t NumInterfaces = offsetof(MethodTable, m_wNumInterfaces); + static constexpr size_t NumVirtuals = offsetof(MethodTable, m_wNumVirtuals); }; #ifndef CROSSBITNESS_COMPILE diff --git a/src/native/managed/cdacreader/src/Contracts/Metadata_1.cs b/src/native/managed/cdacreader/src/Contracts/Metadata_1.cs index 70e0c787099ab9..77f2e80f4b57e8 100644 --- a/src/native/managed/cdacreader/src/Contracts/Metadata_1.cs +++ b/src/native/managed/cdacreader/src/Contracts/Metadata_1.cs @@ -355,7 +355,7 @@ public ushort GetNumVtableSlots(MethodTableHandle methodTableHandle) return checked((ushort)(GetNumVirtuals(methodTableHandle) + GetNumNonVirtualSlots(methodTableHandle))); } - public uint GetTypeDefTypeAttributes(MethodTableHandle methodTableHandle) => GetClassData(methodTableHandle).DwAttrClass; + public uint GetTypeDefTypeAttributes(MethodTableHandle methodTableHandle) => GetClassData(methodTableHandle).AttrClass; public bool IsDynamicStatics(MethodTableHandle methodTableHandle) => _methodTables[methodTableHandle.Address].Flags.GetFlag(WFLAGS2_ENUM.DynamicStatics) != 0; diff --git a/src/native/managed/cdacreader/src/Data/EEClass.cs b/src/native/managed/cdacreader/src/Data/EEClass.cs index 122cf2c477f9ab..ec056e9695e11a 100644 --- a/src/native/managed/cdacreader/src/Data/EEClass.cs +++ b/src/native/managed/cdacreader/src/Data/EEClass.cs @@ -15,11 +15,11 @@ public EEClass(Target target, TargetPointer address) MethodTable = target.ReadPointer(address + (ulong)type.Fields[nameof(MethodTable)].Offset); NumMethods = target.Read(address + (ulong)type.Fields[nameof(NumMethods)].Offset); NumNonVirtualSlots = target.Read(address + (ulong)type.Fields[nameof(NumNonVirtualSlots)].Offset); - DwAttrClass = target.Read(address + (ulong)type.Fields[nameof(DwAttrClass)].Offset); + AttrClass = target.Read(address + (ulong)type.Fields[nameof(AttrClass)].Offset); } public TargetPointer MethodTable { get; init; } public ushort NumMethods { get; init; } public ushort NumNonVirtualSlots { get; init; } - public uint DwAttrClass { get; init; } + public uint AttrClass { get; init; } } From d526087845afb71583ecac5012646f26871a1f9a Mon Sep 17 00:00:00 2001 From: Aleksey Kliger Date: Fri, 21 Jun 2024 14:01:13 -0400 Subject: [PATCH 39/62] cleanup the contract docs --- docs/design/datacontracts/Metadata.md | 87 ++++++--------------------- 1 file changed, 20 insertions(+), 67 deletions(-) diff --git a/docs/design/datacontracts/Metadata.md b/docs/design/datacontracts/Metadata.md index 908ba3ed86d29e..89adbc65800671 100644 --- a/docs/design/datacontracts/Metadata.md +++ b/docs/design/datacontracts/Metadata.md @@ -61,9 +61,7 @@ internal partial struct Metadata_1 GenericsMask = 0x00000030, GenericsMask_NonGeneric = 0x00000000, // no instantiation - StringArrayValues = - GenericsMask_NonGeneric | - 0, + StringArrayValues = GenericsMask_NonGeneric, } [Flags] @@ -83,20 +81,13 @@ internal partial struct Metadata_1 DynamicStatics = 0x0002, } + // Encapsulates the MethodTable flags v1 uses internal struct MethodTableFlags { - public uint DwFlags { get; init; } - public uint DwFlags2 { get; init; } - public uint BaseSize { get; init; } + public WFLAGS_LOW GetFlag(WFLAGS_LOW mask) { ... } + public WFLAGS_HIGH GetFlag(WFLAGS_HIGH mask) { ... } - private WFLAGS_HIGH FlagsHigh => (WFLAGS_HIGH)DwFlags; - private WFLAGS_LOW FlagsLow => (WFLAGS_LOW)DwFlags; - public int GetTypeDefRid() => (int)(DwFlags2 >> Constants.MethodTableDwFlags2TypeDefRidShift); - - public WFLAGS_LOW GetFlag(WFLAGS_LOW mask) => throw new NotImplementedException("TODO"); - public WFLAGS_HIGH GetFlag(WFLAGS_HIGH mask) => FlagsHigh & mask; - - public WFLAGS2_ENUM GetFlag(WFLAGS2_ENUM mask) => (WFLAGS2_ENUM)DwFlags2 & mask; + public WFLAGS2_ENUM GetFlag(WFLAGS2_ENUM mask) { ... } public bool IsInterface => GetFlag(WFLAGS_HIGH.Category_Mask) == WFLAGS_HIGH.Category_Interface; public bool IsString => HasComponentSize && !IsArray && RawGetComponentSize() == 2; @@ -107,7 +98,7 @@ internal partial struct Metadata_1 public bool IsStringOrArray => HasComponentSize; public ushort RawGetComponentSize() => (ushort)(DwFlags >> 16); - public bool TestFlagWithMask(WFLAGS_LOW mask, WFLAGS_LOW flag) + private bool TestFlagWithMask(WFLAGS_LOW mask, WFLAGS_LOW flag) { if (IsStringOrArray) { @@ -119,19 +110,9 @@ internal partial struct Metadata_1 } } - public bool TestFlagWithMask(WFLAGS2_ENUM mask, WFLAGS2_ENUM flag) - { - return ((WFLAGS2_ENUM)DwFlags2 & mask) == flag; - } - public bool HasInstantiation => !TestFlagWithMask(WFLAGS_LOW.GenericsMask, WFLAGS_LOW.GenericsMask_NonGeneric); - public bool ContainsPointers => GetFlag(WFLAGS_HIGH.ContainsPointers) != 0; - } - - internal static class Constants - { - internal const int MethodTableDwFlags2TypeDefRidShift = 8; + public bool ContainsGCPointers => GetFlag(WFLAGS_HIGH.ContainsGCPointers) != 0; } [Flags] @@ -143,7 +124,7 @@ internal partial struct Metadata_1 } ``` -Internally the contract has structs `MethodTable_1` and `EEClass_1` +Internally the contract has a `MethodTable_1` struct that depends on the `MethodTable` data descriptor ```csharp internal struct MethodTable_1 @@ -169,28 +150,15 @@ internal struct MethodTable_1 ParentMethodTable = data.ParentMethodTable; } } - -internal struct EEClass_1 -{ - internal TargetPointer MethodTable { get; } - internal ushort NumMethods { get; } - internal ushort NumNonVirtualSlots { get; } - internal uint TypeDefTypeAttributes { get; } - internal EEClass_1(Data.EEClass eeClassData) - { - MethodTable = eeClassData.MethodTable; - NumMethods = eeClassData.NumMethods; - NumNonVirtualSlots = eeClassData.NumNonVirtualSlots; - TypeDefTypeAttributes = eeClassData.DwAttrClass; - } -} ``` +The contract depends on the global pointer value `FreeObjectMethodTablePointer`. +The contract additionally depends on the `EEClass` data descriptor. + ```csharp private readonly Dictionary _methodTables; - private readonly TargetPointer _freeObjectMethodTablePointer; - internal TargetPointer FreeObjectMethodTablePointer => _freeObjectMethodTablePointer; + internal TargetPointer FreeObjectMethodTablePointer {get; } public MethodTableHandle GetMethodTableHandle(TargetPointer methodTablePointer) { @@ -206,34 +174,19 @@ internal struct EEClass_1 public uint GetComponentSize(MethodTableHandle methodTableHandle) => GetComponentSize(_methodTables[methodTableHandle.Address]); - private EEClass_1 GetClassData(MethodTableHandle methodTableHandle) + private Data.EEClass GetClassData(MethodTableHandle methodTableHandle) { - TargetPointer clsPtr = GetClass(methodTableHandle); - // Check if we cached it already - if (_target.ProcessedData.TryGet(clsPtr, out Data.EEClass? eeClassData)) - { - return new EEClass_1(eeClassData); - } - eeClassData = _target.ProcessedData.GetOrAdd(clsPtr); - return new EEClass_1(eeClassData); + ... } private TargetPointer GetClass(MethodTableHandle methodTableHandle) { - MethodTable_1 methodTable = _methodTables[methodTableHandle.Address]; - switch (GetEEClassOrCanonMTBits(methodTable.EEClassOrCanonMT)) - { - case EEClassOrCanonMTBits.EEClass: - return methodTable.EEClassOrCanonMT; - case EEClassOrCanonMTBits.CanonMT: - TargetPointer canonMTPtr = new TargetPointer((ulong)methodTable.EEClassOrCanonMT & ~(ulong)Metadata_1.EEClassOrCanonMTBits.Mask); - MethodTableHandle canonMTHandle = GetMethodTableHandle(canonMTPtr); - MethodTable_1 canonMT = _methodTables[canonMTHandle.Address]; - return canonMT.EEClassOrCanonMT; // canonical method table EEClassOrCanonMT is always EEClass - default: - throw new InvalidOperationException(); - } + ... // if the MethodTable stores a pointer to the EEClass, return it + // otherwise the MethodTable stores a pointer to the canonical MethodTable + // in that case, return the canonical MethodTable's EEClass. + // Canonical MethodTables always store an EEClass pointer. } + public TargetPointer GetCanonicalMethodTable(MethodTableHandle methodTableHandle) => GetClassData(methodTableHandle).MethodTable; public TargetPointer GetModule(MethodTableHandle methodTableHandle) => _methodTables[methodTableHandle.Address].Module; @@ -280,7 +233,7 @@ internal struct EEClass_1 public uint GetTypeDefTypeAttributes(MethodTableHandle methodTableHandle) { - return GetClassData(methodTableHandle).TypeDefTypeAttributes; + return GetClassData(methodTableHandle).AttrClass; } public bool IsDynamicStatics(MethodTableHandle methodTableHandle) => _methodTables[methodTableHandle.Address].Flags.GetFlag(WFLAGS2_ENUM.DynamicStatics) != 0; From 0a4112ee41a14b3c1db6fabd6226dbba4f9c847f Mon Sep 17 00:00:00 2001 From: Aleksey Kliger Date: Mon, 24 Jun 2024 12:24:53 -0400 Subject: [PATCH 40/62] remove hungariant notation prefixes from data descriptors --- docs/design/datacontracts/Metadata.md | 38 +++++++++---------- .../debug/runtimeinfo/datadescriptor.h | 6 +-- src/coreclr/vm/methodtable.h | 4 +- .../Contracts/Metadata_1.MethodTableFlags.cs | 20 ++++++---- .../cdacreader/src/Contracts/Metadata_1.cs | 13 ++----- .../cdacreader/src/Data/MethodTable.cs | 8 ++-- .../src/Data/MethodTableAuxiliaryData.cs | 4 +- 7 files changed, 46 insertions(+), 47 deletions(-) diff --git a/docs/design/datacontracts/Metadata.md b/docs/design/datacontracts/Metadata.md index 89adbc65800671..7d4151b75daae0 100644 --- a/docs/design/datacontracts/Metadata.md +++ b/docs/design/datacontracts/Metadata.md @@ -17,34 +17,34 @@ struct MethodTableHandle ``` csharp #region MethodTable inspection APIs - public virtual MethodTableHandle GetMethodTableHandle(TargetPointer targetPointer) => throw new NotImplementedException(); + public virtual MethodTableHandle GetMethodTableHandle(TargetPointer targetPointer); - public virtual TargetPointer GetModule(MethodTableHandle methodTable) => throw new NotImplementedException(); + public virtual TargetPointer GetModule(MethodTableHandle methodTable); // A canonical method table is either the MethodTable itself, or in the case of a generic instantiation, it is the // MethodTable of the prototypical instance. - public virtual TargetPointer GetCanonicalMethodTable(MethodTableHandle methodTable) => throw new NotImplementedException(); - public virtual TargetPointer GetParentMethodTable(MethodTableHandle methodTable) => throw new NotImplementedException(); + public virtual TargetPointer GetCanonicalMethodTable(MethodTableHandle methodTable); + public virtual TargetPointer GetParentMethodTable(MethodTableHandle methodTable); - public virtual uint GetBaseSize(MethodTableHandle methodTable) => throw new NotImplementedException(); + public virtual uint GetBaseSize(MethodTableHandle methodTable); // The component size is only available for strings and arrays. It is the size of the element type of the array, or the size of an ECMA 335 character (2 bytes) - public virtual uint GetComponentSize(MethodTableHandle methodTable) => throw new NotImplementedException(); + public virtual uint GetComponentSize(MethodTableHandle methodTable); // True if the MethodTable is the sentinel value associated with unallocated space in the managed heap - public virtual bool IsFreeObjectMethodTable(MethodTableHandle methodTable) => throw new NotImplementedException(); - public virtual bool IsString(MethodTableHandle methodTable) => throw new NotImplementedException(); + public virtual bool IsFreeObjectMethodTable(MethodTableHandle methodTable); + public virtual bool IsString(MethodTableHandle methodTable); // True if the MethodTable represents a type that contains managed references - public virtual bool ContainsGCPointers(MethodTableHandle methodTable) => throw new NotImplementedException(); - public virtual bool IsDynamicStatics(MethodTableHandle methodTable) => throw new NotImplementedException(); - public virtual ushort GetNumMethods(MethodTableHandle methodTable) => throw new NotImplementedException(); - public virtual ushort GetNumInterfaces(MethodTableHandle methodTable) => throw new NotImplementedException(); - public virtual ushort GetNumVirtuals(MethodTableHandle methodTable) => throw new NotImplementedException(); - public virtual ushort GetNumVtableSlots(MethodTableHandle methodTable) => throw new NotImplementedException(); + public virtual bool ContainsGCPointers(MethodTableHandle methodTable); + public virtual bool IsDynamicStatics(MethodTableHandle methodTable); + public virtual ushort GetNumMethods(MethodTableHandle methodTable); + public virtual ushort GetNumInterfaces(MethodTableHandle methodTable); + public virtual ushort GetNumVirtuals(MethodTableHandle methodTable); + public virtual ushort GetNumVtableSlots(MethodTableHandle methodTable); // Returns an ECMA-335 TypeDef table token for this type, or for its generic type definition if it is a generic instantiation - public virtual uint GetTypeDefToken(MethodTableHandle methodTable) => throw new NotImplementedException(); + public virtual uint GetTypeDefToken(MethodTableHandle methodTable); // Returns the ECMA 335 TypeDef table Flags value (a bitmask of TypeAttributes) for this type, // or for its generic type definition if it is a generic instantiation - public virtual uint GetTypeDefTypeAttributes(MethodTableHandle methodTable) => throw new NotImplementedException(); + public virtual uint GetTypeDefTypeAttributes(MethodTableHandle methodTable); #endregion MethodTable inspection APIs ``` @@ -96,7 +96,7 @@ internal partial struct Metadata_1 public bool IsArray => GetFlag(WFLAGS_HIGH.Category_Array_Mask) == WFLAGS_HIGH.Category_Array; public bool IsStringOrArray => HasComponentSize; - public ushort RawGetComponentSize() => (ushort)(DwFlags >> 16); + public ushort RawGetComponentSize() => (ushort)(MTFlags >> 16); private bool TestFlagWithMask(WFLAGS_LOW mask, WFLAGS_LOW flag) { @@ -139,8 +139,8 @@ internal struct MethodTable_1 { Flags = new Metadata_1.MethodTableFlags { - DwFlags = data.DwFlags, - DwFlags2 = data.DwFlags2, + MTFlags = data.MTFlags, + MTFlags2 = data.MTFlags2, BaseSize = data.BaseSize, }; NumInterfaces = data.NumInterfaces; diff --git a/src/coreclr/debug/runtimeinfo/datadescriptor.h b/src/coreclr/debug/runtimeinfo/datadescriptor.h index 8116313bf60170..a59cb26cb005db 100644 --- a/src/coreclr/debug/runtimeinfo/datadescriptor.h +++ b/src/coreclr/debug/runtimeinfo/datadescriptor.h @@ -158,9 +158,9 @@ CDAC_TYPE_END(GCHandle) CDAC_TYPE_BEGIN(MethodTable) CDAC_TYPE_INDETERMINATE(MethodTable) -CDAC_TYPE_FIELD(MethodTable, /*uint32*/, DwFlags, cdac_offsets::DwFlags) +CDAC_TYPE_FIELD(MethodTable, /*uint32*/, MTFlags, cdac_offsets::MTFlags) CDAC_TYPE_FIELD(MethodTable, /*uint32*/, BaseSize, cdac_offsets::BaseSize) -CDAC_TYPE_FIELD(MethodTable, /*uint32*/, DwFlags2, cdac_offsets::DwFlags2) +CDAC_TYPE_FIELD(MethodTable, /*uint32*/, MTFlags2, cdac_offsets::MTFlags2) CDAC_TYPE_FIELD(MethodTable, /*nuint*/, EEClassOrCanonMT, cdac_offsets::EEClassOrCanonMT) CDAC_TYPE_FIELD(MethodTable, /*pointer*/, Module, cdac_offsets::Module) CDAC_TYPE_FIELD(MethodTable, /*pointer*/, AuxiliaryData, cdac_offsets::AuxiliaryData) @@ -179,7 +179,7 @@ CDAC_TYPE_END(EEClass) CDAC_TYPE_BEGIN(MethodTableAuxiliaryData) CDAC_TYPE_INDETERMINATE(MethodTableAuxiliaryData) -CDAC_TYPE_FIELD(MethodTableAuxiliaryData, /*uint32*/, DwFlags, offsetof(MethodTableAuxiliaryData, m_dwFlags)) +CDAC_TYPE_FIELD(MethodTableAuxiliaryData, /*uint32*/, AuxFlags, offsetof(MethodTableAuxiliaryData, m_dwFlags)) CDAC_TYPE_END(MethodTableAuxiliaryData) CDAC_TYPES_END() diff --git a/src/coreclr/vm/methodtable.h b/src/coreclr/vm/methodtable.h index db6e8d0e69bc2a..979a005480fcd5 100644 --- a/src/coreclr/vm/methodtable.h +++ b/src/coreclr/vm/methodtable.h @@ -3864,9 +3864,9 @@ public : template<> struct cdac_offsets { - static constexpr size_t DwFlags = offsetof(MethodTable, m_dwFlags); + static constexpr size_t MTFlags = offsetof(MethodTable, m_dwFlags); static constexpr size_t BaseSize = offsetof(MethodTable, m_BaseSize); - static constexpr size_t DwFlags2 = offsetof(MethodTable, m_dwFlags2); + static constexpr size_t MTFlags2 = offsetof(MethodTable, m_dwFlags2); static constexpr size_t EEClassOrCanonMT = offsetof(MethodTable, m_pEEClass); static constexpr size_t Module = offsetof(MethodTable, m_pModule); static constexpr size_t AuxiliaryData = offsetof(MethodTable, m_pAuxiliaryData); diff --git a/src/native/managed/cdacreader/src/Contracts/Metadata_1.MethodTableFlags.cs b/src/native/managed/cdacreader/src/Contracts/Metadata_1.MethodTableFlags.cs index 17d9d2f01cc587..fdd34178a0bf44 100644 --- a/src/native/managed/cdacreader/src/Contracts/Metadata_1.MethodTableFlags.cs +++ b/src/native/managed/cdacreader/src/Contracts/Metadata_1.MethodTableFlags.cs @@ -37,18 +37,22 @@ internal enum WFLAGS2_ENUM : uint internal struct MethodTableFlags { - public uint DwFlags { get; init; } - public uint DwFlags2 { get; init; } + + + public uint MTFlags { get; init; } + public uint MTFlags2 { get; init; } public uint BaseSize { get; init; } - private WFLAGS_HIGH FlagsHigh => (WFLAGS_HIGH)DwFlags; - private WFLAGS_LOW FlagsLow => (WFLAGS_LOW)DwFlags; - public int GetTypeDefRid() => (int)(DwFlags2 >> Constants.MethodTableDwFlags2TypeDefRidShift); + private const int MTFlags2TypeDefRidShift = 8; + private const int MTFlagsComponentSizeShift = 16; + private WFLAGS_HIGH FlagsHigh => (WFLAGS_HIGH)MTFlags; + private WFLAGS_LOW FlagsLow => (WFLAGS_LOW)MTFlags; + public int GetTypeDefRid() => (int)(MTFlags2 >> MTFlags2TypeDefRidShift); public WFLAGS_LOW GetFlag(WFLAGS_LOW mask) => throw new NotImplementedException("TODO"); public WFLAGS_HIGH GetFlag(WFLAGS_HIGH mask) => FlagsHigh & mask; - public WFLAGS2_ENUM GetFlag(WFLAGS2_ENUM mask) => (WFLAGS2_ENUM)DwFlags2 & mask; + public WFLAGS2_ENUM GetFlag(WFLAGS2_ENUM mask) => (WFLAGS2_ENUM)MTFlags2 & mask; public bool IsInterface => GetFlag(WFLAGS_HIGH.Category_Mask) == WFLAGS_HIGH.Category_Interface; public bool IsString => HasComponentSize && !IsArray && RawGetComponentSize() == 2; @@ -57,7 +61,7 @@ internal struct MethodTableFlags public bool IsArray => GetFlag(WFLAGS_HIGH.Category_Array_Mask) == WFLAGS_HIGH.Category_Array; public bool IsStringOrArray => HasComponentSize; - public ushort RawGetComponentSize() => (ushort)(DwFlags >> 16); + public ushort RawGetComponentSize() => (ushort)(MTFlags >> MTFlagsComponentSizeShift); private bool TestFlagWithMask(WFLAGS_LOW mask, WFLAGS_LOW flag) { @@ -73,7 +77,7 @@ private bool TestFlagWithMask(WFLAGS_LOW mask, WFLAGS_LOW flag) private bool TestFlagWithMask(WFLAGS2_ENUM mask, WFLAGS2_ENUM flag) { - return ((WFLAGS2_ENUM)DwFlags2 & mask) == flag; + return ((WFLAGS2_ENUM)MTFlags2 & mask) == flag; } public bool HasInstantiation => !TestFlagWithMask(WFLAGS_LOW.GenericsMask, WFLAGS_LOW.GenericsMask_NonGeneric); diff --git a/src/native/managed/cdacreader/src/Contracts/Metadata_1.cs b/src/native/managed/cdacreader/src/Contracts/Metadata_1.cs index 77f2e80f4b57e8..036254166c10ba 100644 --- a/src/native/managed/cdacreader/src/Contracts/Metadata_1.cs +++ b/src/native/managed/cdacreader/src/Contracts/Metadata_1.cs @@ -37,8 +37,8 @@ private Metadata_1.MethodTableFlags GetOrCreateFlags() // note: may throw if the method table Address is corrupted Metadata_1.MethodTableFlags flags = new Metadata_1.MethodTableFlags { - DwFlags = _target.Read(Address + (ulong)_type.Fields[nameof(Metadata_1.MethodTableFlags.DwFlags)].Offset), - DwFlags2 = _target.Read(Address + (ulong)_type.Fields[nameof(Metadata_1.MethodTableFlags.DwFlags2)].Offset), + MTFlags = _target.Read(Address + (ulong)_type.Fields[nameof(Metadata_1.MethodTableFlags.MTFlags)].Offset), + MTFlags2 = _target.Read(Address + (ulong)_type.Fields[nameof(Metadata_1.MethodTableFlags.MTFlags2)].Offset), BaseSize = _target.Read(Address + (ulong)_type.Fields[nameof(Metadata_1.MethodTableFlags.BaseSize)].Offset), }; _methodTableFlags = flags; @@ -96,8 +96,8 @@ internal MethodTable_1(Data.MethodTable data) { Flags = new Metadata_1.MethodTableFlags { - DwFlags = data.DwFlags, - DwFlags2 = data.DwFlags2, + MTFlags = data.MTFlags, + MTFlags2 = data.MTFlags2, BaseSize = data.BaseSize, }; NumInterfaces = data.NumInterfaces; @@ -116,11 +116,6 @@ internal partial struct Metadata_1 : IMetadata // FIXME: we mutate this dictionary - copies of the Metadata_1 struct share this instance private readonly Dictionary _methodTables = new(); - internal static class Constants - { - internal const int MethodTableDwFlags2TypeDefRidShift = 8; - } - [Flags] internal enum EEClassOrCanonMTBits { diff --git a/src/native/managed/cdacreader/src/Data/MethodTable.cs b/src/native/managed/cdacreader/src/Data/MethodTable.cs index 9fa82511cfb5e3..3319c6547f0568 100644 --- a/src/native/managed/cdacreader/src/Data/MethodTable.cs +++ b/src/native/managed/cdacreader/src/Data/MethodTable.cs @@ -12,9 +12,9 @@ public MethodTable(Target target, TargetPointer address) { Target.TypeInfo type = target.GetTypeInfo(DataType.MethodTable); - DwFlags = target.Read(address + (ulong)type.Fields[nameof(DwFlags)].Offset); + MTFlags = target.Read(address + (ulong)type.Fields[nameof(MTFlags)].Offset); BaseSize = target.Read(address + (ulong)type.Fields[nameof(BaseSize)].Offset); - DwFlags2 = target.Read(address + (ulong)type.Fields[nameof(DwFlags2)].Offset); + MTFlags2 = target.Read(address + (ulong)type.Fields[nameof(MTFlags2)].Offset); EEClassOrCanonMT = target.ReadPointer(address + (ulong)type.Fields[nameof(EEClassOrCanonMT)].Offset); Module = target.ReadPointer(address + (ulong)type.Fields[nameof(Module)].Offset); ParentMethodTable = target.ReadPointer(address + (ulong)type.Fields[nameof(ParentMethodTable)].Offset); @@ -22,9 +22,9 @@ public MethodTable(Target target, TargetPointer address) NumVirtuals = target.Read(address + (ulong)type.Fields[nameof(NumVirtuals)].Offset); } - public uint DwFlags { get; init; } + public uint MTFlags { get; init; } public uint BaseSize { get; init; } - public uint DwFlags2 { get; init; } + public uint MTFlags2 { get; init; } public TargetPointer EEClassOrCanonMT { get; init; } public TargetPointer Module { get; init; } public TargetPointer ParentMethodTable { get; init; } diff --git a/src/native/managed/cdacreader/src/Data/MethodTableAuxiliaryData.cs b/src/native/managed/cdacreader/src/Data/MethodTableAuxiliaryData.cs index aaba612d12e107..bdc711a5104735 100644 --- a/src/native/managed/cdacreader/src/Data/MethodTableAuxiliaryData.cs +++ b/src/native/managed/cdacreader/src/Data/MethodTableAuxiliaryData.cs @@ -13,9 +13,9 @@ private MethodTableAuxiliaryData(Target target, TargetPointer address) { Target.TypeInfo type = target.GetTypeInfo(DataType.MethodTableAuxiliaryData); - DwFlags = target.Read(address + (ulong)type.Fields[nameof(DwFlags)].Offset); + AuxFlags = target.Read(address + (ulong)type.Fields[nameof(AuxFlags)].Offset); } - public uint DwFlags { get; init; } + public uint AuxFlags { get; init; } } From 1071ca456a0db496ae460bb7be636fed07eae5b7 Mon Sep 17 00:00:00 2001 From: Aleksey Kliger Date: Tue, 25 Jun 2024 16:14:42 -0400 Subject: [PATCH 41/62] DAC: always set wNumVirtuals and wNumVtableSlots to 0 This information can be retreived from the MethodTable using normal lldb/windbg primitives and doesn't need to be part of the DAC API contract --- docs/design/datacontracts/Metadata.md | 20 ------------------- src/coreclr/debug/daccess/request.cpp | 6 +++--- src/coreclr/inc/dacprivate.h | 2 ++ .../cdacreader/src/Contracts/Metadata.cs | 2 -- .../cdacreader/src/Contracts/Metadata_1.cs | 20 ------------------- .../cdacreader/src/Legacy/SOSDacImpl.cs | 4 ++-- 6 files changed, 7 insertions(+), 47 deletions(-) diff --git a/docs/design/datacontracts/Metadata.md b/docs/design/datacontracts/Metadata.md index 7d4151b75daae0..734739a6519a6e 100644 --- a/docs/design/datacontracts/Metadata.md +++ b/docs/design/datacontracts/Metadata.md @@ -211,26 +211,6 @@ The contract additionally depends on the `EEClass` data descriptor. public ushort GetNumInterfaces(MethodTableHandle methodTableHandle) => _methodTables[methodTableHandle.Address].NumInterfaces; - public ushort GetNumVirtuals(MethodTableHandle methodTableHandle) => _methodTables[methodTableHandle.Address].NumVirtuals; - private ushort GetNumNonVirtualSlots(MethodTableHandle methodTableHandle) - { - MethodTable_1 methodTable = _methodTables[methodTableHandle.Address]; - TargetPointer eeClassOrCanonMT = methodTable.EEClassOrCanonMT; - if (GetEEClassOrCanonMTBits(eeClassOrCanonMT) == EEClassOrCanonMTBits.EEClass) - { - return GetClassData(methodTableHandle).NumNonVirtualSlots; - } - else - { - return 0; - } - } - - public ushort GetNumVtableSlots(MethodTableHandle methodTableHandle) - { - return checked((ushort)(GetNumVirtuals(methodTableHandle) + GetNumNonVirtualSlots(methodTableHandle))); - } - public uint GetTypeDefTypeAttributes(MethodTableHandle methodTableHandle) { return GetClassData(methodTableHandle).AttrClass; diff --git a/src/coreclr/debug/daccess/request.cpp b/src/coreclr/debug/daccess/request.cpp index 8f325dcfa00374..ebbcc0accf62c4 100644 --- a/src/coreclr/debug/daccess/request.cpp +++ b/src/coreclr/debug/daccess/request.cpp @@ -1858,9 +1858,9 @@ ClrDataAccess::GetMethodTableDataImpl(CLRDATA_ADDRESS mt, struct DacpMethodTable MTData->Class = HOST_CDADDR(pMT->GetClass()->GetMethodTable()); MTData->ParentMethodTable = HOST_CDADDR(pMT->GetParentMethodTable());; MTData->wNumInterfaces = (WORD)pMT->GetNumInterfaces(); - MTData->wNumMethods = pMT->GetNumMethods(); - MTData->wNumVtableSlots = pMT->GetNumVtableSlots(); - MTData->wNumVirtuals = pMT->GetNumVirtuals(); + MTData->wNumMethods = pMT->GetNumMethods(); // printed as "number of vtable slots" and used to iterate over method slots + MTData->wNumVtableSlots = 0; // always return 0 since .NET 9 + MTData->wNumVirtuals = 0; // always return 0 since .NET 9 MTData->cl = pMT->GetCl(); MTData->dwAttrClass = pMT->GetAttrClass(); MTData->bContainsPointers = pMT->ContainsGCPointers(); diff --git a/src/coreclr/inc/dacprivate.h b/src/coreclr/inc/dacprivate.h index 05e07b42c0b784..a316e50268a32b 100644 --- a/src/coreclr/inc/dacprivate.h +++ b/src/coreclr/inc/dacprivate.h @@ -279,7 +279,9 @@ struct MSLAYOUT DacpMethodTableData CLRDATA_ADDRESS ParentMethodTable = 0; WORD wNumInterfaces = 0; WORD wNumMethods = 0; + // Note: Always 0, since .NET 9 WORD wNumVtableSlots = 0; + // Note: Always 0, since .NET 9 WORD wNumVirtuals = 0; DWORD BaseSize = 0; DWORD ComponentSize = 0; diff --git a/src/native/managed/cdacreader/src/Contracts/Metadata.cs b/src/native/managed/cdacreader/src/Contracts/Metadata.cs index 97fd5fe83b13bb..03c70f5b4cc51b 100644 --- a/src/native/managed/cdacreader/src/Contracts/Metadata.cs +++ b/src/native/managed/cdacreader/src/Contracts/Metadata.cs @@ -52,8 +52,6 @@ static IContract IContract.Create(Target target, int version) public virtual bool IsDynamicStatics(MethodTableHandle methodTable) => throw new NotImplementedException(); public virtual ushort GetNumMethods(MethodTableHandle methodTable) => throw new NotImplementedException(); public virtual ushort GetNumInterfaces(MethodTableHandle methodTable) => throw new NotImplementedException(); - public virtual ushort GetNumVirtuals(MethodTableHandle methodTable) => throw new NotImplementedException(); - public virtual ushort GetNumVtableSlots(MethodTableHandle methodTable) => throw new NotImplementedException(); // Returns an ECMA-335 TypeDef table token for this type, or for its generic type definition if it is a generic instantiation public virtual uint GetTypeDefToken(MethodTableHandle methodTable) => throw new NotImplementedException(); diff --git a/src/native/managed/cdacreader/src/Contracts/Metadata_1.cs b/src/native/managed/cdacreader/src/Contracts/Metadata_1.cs index 036254166c10ba..602d824838c6f0 100644 --- a/src/native/managed/cdacreader/src/Contracts/Metadata_1.cs +++ b/src/native/managed/cdacreader/src/Contracts/Metadata_1.cs @@ -330,26 +330,6 @@ public uint GetTypeDefToken(MethodTableHandle methodTableHandle) public ushort GetNumInterfaces(MethodTableHandle methodTableHandle) => _methodTables[methodTableHandle.Address].NumInterfaces; - public ushort GetNumVirtuals(MethodTableHandle methodTableHandle) => _methodTables[methodTableHandle.Address].NumVirtuals; - private ushort GetNumNonVirtualSlots(MethodTableHandle methodTableHandle) - { - MethodTable_1 methodTable = _methodTables[methodTableHandle.Address]; - TargetPointer eeClassOrCanonMT = methodTable.EEClassOrCanonMT; - if (GetEEClassOrCanonMTBits(eeClassOrCanonMT) == EEClassOrCanonMTBits.EEClass) - { - return GetClassData(methodTableHandle).NumNonVirtualSlots; - } - else - { - return 0; - } - } - - public ushort GetNumVtableSlots(MethodTableHandle methodTableHandle) - { - return checked((ushort)(GetNumVirtuals(methodTableHandle) + GetNumNonVirtualSlots(methodTableHandle))); - } - public uint GetTypeDefTypeAttributes(MethodTableHandle methodTableHandle) => GetClassData(methodTableHandle).AttrClass; public bool IsDynamicStatics(MethodTableHandle methodTableHandle) => _methodTables[methodTableHandle.Address].Flags.GetFlag(WFLAGS2_ENUM.DynamicStatics) != 0; diff --git a/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs b/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs index 499df64bfac936..495167857394a2 100644 --- a/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs +++ b/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs @@ -113,8 +113,8 @@ public unsafe int GetMethodTableData(ulong mt, DacpMethodTableData* data) result.parentMethodTable = contract.GetParentMethodTable(methodTable); result.wNumInterfaces = contract.GetNumInterfaces(methodTable); result.wNumMethods = contract.GetNumMethods(methodTable); - result.wNumVtableSlots = contract.GetNumVtableSlots(methodTable); - result.wNumVirtuals = contract.GetNumVirtuals(methodTable); + result.wNumVtableSlots = 0; // always return 0 since .NET 9 + result.wNumVirtuals = 0; // always return 0 since .NET 9 result.cl = contract.GetTypeDefToken(methodTable); result.dwAttrClass = contract.GetTypeDefTypeAttributes(methodTable); result.bContainsGCPointers = contract.ContainsGCPointers(methodTable) ? 1 : 0; From 6c5235c4b167fa304063876ef21bb1823b8b01ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aleksey=20Kliger=20=28=CE=BBgeek=29?= Date: Thu, 27 Jun 2024 08:51:32 -0400 Subject: [PATCH 42/62] Remove NumVirtuals and NumVtableSlots from Metadata.md Co-authored-by: Jan Kotas --- docs/design/datacontracts/Metadata.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/docs/design/datacontracts/Metadata.md b/docs/design/datacontracts/Metadata.md index 734739a6519a6e..1f8625a67abd0d 100644 --- a/docs/design/datacontracts/Metadata.md +++ b/docs/design/datacontracts/Metadata.md @@ -37,8 +37,6 @@ struct MethodTableHandle public virtual bool IsDynamicStatics(MethodTableHandle methodTable); public virtual ushort GetNumMethods(MethodTableHandle methodTable); public virtual ushort GetNumInterfaces(MethodTableHandle methodTable); - public virtual ushort GetNumVirtuals(MethodTableHandle methodTable); - public virtual ushort GetNumVtableSlots(MethodTableHandle methodTable); // Returns an ECMA-335 TypeDef table token for this type, or for its generic type definition if it is a generic instantiation public virtual uint GetTypeDefToken(MethodTableHandle methodTable); From 6573e143d1c1513a1bd61ecad4165cbd12b43708 Mon Sep 17 00:00:00 2001 From: Aleksey Kliger Date: Fri, 28 Jun 2024 11:43:20 -0400 Subject: [PATCH 43/62] "untrusted" -> "non-validated" --- .../cdacreader/src/Contracts/Metadata_1.cs | 36 +++++++++---------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/src/native/managed/cdacreader/src/Contracts/Metadata_1.cs b/src/native/managed/cdacreader/src/Contracts/Metadata_1.cs index 602d824838c6f0..918c39fb69dd33 100644 --- a/src/native/managed/cdacreader/src/Contracts/Metadata_1.cs +++ b/src/native/managed/cdacreader/src/Contracts/Metadata_1.cs @@ -13,8 +13,8 @@ namespace Microsoft.Diagnostics.DataContractReader.Contracts; // trust to be valid. // see Metadata_1.ValidateMethodTablePointer // This doesn't need as many properties as MethodTable because we don't want to be operating on -// an UntrustedMethodTable for too long -internal struct UntrustedMethodTable_1 +// a NonValidatedMethodTable for too long +internal struct NonValidatedMethodTable_1 { private readonly Target _target; private readonly Target.TypeInfo _type; @@ -22,7 +22,7 @@ internal struct UntrustedMethodTable_1 private Metadata_1.MethodTableFlags? _methodTableFlags; - internal UntrustedMethodTable_1(Target target, TargetPointer methodTablePointer) + internal NonValidatedMethodTable_1(Target target, TargetPointer methodTablePointer) { _target = target; _type = target.GetTypeInfo(DataType.MethodTable); @@ -66,14 +66,14 @@ internal TargetPointer CanonMT } } -internal struct UntrustedEEClass_1 +internal struct NonValidatedEEClass_1 { public readonly Target _target; private readonly Target.TypeInfo _type; internal TargetPointer Address { get; init; } - internal UntrustedEEClass_1(Target target, TargetPointer eeClassPointer) + internal NonValidatedEEClass_1(Target target, TargetPointer eeClassPointer) { _target = target; Address = eeClassPointer; @@ -132,14 +132,14 @@ internal Metadata_1(Target target, TargetPointer freeObjectMethodTablePointer) internal TargetPointer FreeObjectMethodTablePointer => _freeObjectMethodTablePointer; - private UntrustedMethodTable_1 GetUntrustedMethodTableData(TargetPointer methodTablePointer) + private NonValidatedMethodTable_1 GetUntrustedMethodTableData(TargetPointer methodTablePointer) { - return new UntrustedMethodTable_1(_target, methodTablePointer); + return new NonValidatedMethodTable_1(_target, methodTablePointer); } - private UntrustedEEClass_1 GetUntrustedEEClassData(TargetPointer eeClassPointer) + private NonValidatedEEClass_1 GetUntrustedEEClassData(TargetPointer eeClassPointer) { - return new UntrustedEEClass_1(_target, eeClassPointer); + return new NonValidatedEEClass_1(_target, eeClassPointer); } public MethodTableHandle GetMethodTableHandle(TargetPointer methodTablePointer) @@ -159,7 +159,7 @@ public MethodTableHandle GetMethodTableHandle(TargetPointer methodTablePointer) } // Otherwse, don't trust it yet - UntrustedMethodTable_1 untrustedMethodTable = GetUntrustedMethodTableData(methodTablePointer); + NonValidatedMethodTable_1 untrustedMethodTable = GetUntrustedMethodTableData(methodTablePointer); // if it's the free object method table, we can trust it if (methodTablePointer == FreeObjectMethodTablePointer) @@ -180,7 +180,7 @@ public MethodTableHandle GetMethodTableHandle(TargetPointer methodTablePointer) return new MethodTableHandle(methodTablePointer); } - private bool ValidateMethodTablePointer(UntrustedMethodTable_1 umt) + private bool ValidateMethodTablePointer(NonValidatedMethodTable_1 umt) { // FIXME: is methodTablePointer properly sign-extended from 32-bit targets? // FIXME2: do we need this? Data.MethodTable probably would throw if methodTablePointer is invalid @@ -207,7 +207,7 @@ private bool ValidateMethodTablePointer(UntrustedMethodTable_1 umt) return true; } - private bool ValidateWithPossibleAV(UntrustedMethodTable_1 methodTable) + private bool ValidateWithPossibleAV(NonValidatedMethodTable_1 methodTable) { // For non-generic classes, we can rely on comparing // object->methodtable->class->methodtable @@ -223,7 +223,7 @@ private bool ValidateWithPossibleAV(UntrustedMethodTable_1 methodTable) TargetPointer eeClassPtr = GetClassWithPossibleAV(methodTable); if (eeClassPtr != TargetPointer.Null) { - UntrustedEEClass_1 eeClass = GetUntrustedEEClassData(eeClassPtr); + NonValidatedEEClass_1 eeClass = GetUntrustedEEClassData(eeClassPtr); TargetPointer methodTablePtrFromClass = GetMethodTableWithPossibleAV(eeClass); if (methodTable.Address == methodTablePtrFromClass) { @@ -231,7 +231,7 @@ private bool ValidateWithPossibleAV(UntrustedMethodTable_1 methodTable) } if (methodTable.Flags.HasInstantiation || methodTable.Flags.IsArray) { - UntrustedMethodTable_1 methodTableFromClass = GetUntrustedMethodTableData(methodTablePtrFromClass); + NonValidatedMethodTable_1 methodTableFromClass = GetUntrustedMethodTableData(methodTablePtrFromClass); TargetPointer classFromMethodTable = GetClassWithPossibleAV(methodTableFromClass); return classFromMethodTable == eeClassPtr; } @@ -239,7 +239,7 @@ private bool ValidateWithPossibleAV(UntrustedMethodTable_1 methodTable) return false; } - private bool ValidateMethodTable(UntrustedMethodTable_1 methodTable) + private bool ValidateMethodTable(NonValidatedMethodTable_1 methodTable) { if (!methodTable.Flags.IsInterface && !methodTable.Flags.IsString) { @@ -255,7 +255,7 @@ internal static EEClassOrCanonMTBits GetEEClassOrCanonMTBits(TargetPointer eeCla { return (EEClassOrCanonMTBits)(eeClassOrCanonMTPtr & (ulong)EEClassOrCanonMTBits.Mask); } - private TargetPointer GetClassWithPossibleAV(UntrustedMethodTable_1 methodTable) + private TargetPointer GetClassWithPossibleAV(NonValidatedMethodTable_1 methodTable) { TargetPointer eeClassOrCanonMT = methodTable.EEClassOrCanonMT; @@ -266,12 +266,12 @@ private TargetPointer GetClassWithPossibleAV(UntrustedMethodTable_1 methodTable) else { TargetPointer canonicalMethodTablePtr = methodTable.CanonMT; - UntrustedMethodTable_1 umt = GetUntrustedMethodTableData(canonicalMethodTablePtr); + NonValidatedMethodTable_1 umt = GetUntrustedMethodTableData(canonicalMethodTablePtr); return umt.EEClass; } } - private static TargetPointer GetMethodTableWithPossibleAV(UntrustedEEClass_1 eeClass) => eeClass.MethodTable; + private static TargetPointer GetMethodTableWithPossibleAV(NonValidatedEEClass_1 eeClass) => eeClass.MethodTable; public uint GetBaseSize(MethodTableHandle methodTableHandle) => _methodTables[methodTableHandle.Address].Flags.BaseSize; From 8596892e0862b7825245eff86a22b7274366247d Mon Sep 17 00:00:00 2001 From: Aleksey Kliger Date: Fri, 28 Jun 2024 11:44:21 -0400 Subject: [PATCH 44/62] merge fixup catch System.Exception not Data.Exception --- src/native/managed/cdacreader/src/Contracts/Metadata_1.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/native/managed/cdacreader/src/Contracts/Metadata_1.cs b/src/native/managed/cdacreader/src/Contracts/Metadata_1.cs index 918c39fb69dd33..173665547d3b59 100644 --- a/src/native/managed/cdacreader/src/Contracts/Metadata_1.cs +++ b/src/native/managed/cdacreader/src/Contracts/Metadata_1.cs @@ -199,7 +199,7 @@ private bool ValidateMethodTablePointer(NonValidatedMethodTable_1 umt) return false; } } - catch (Exception) + catch (System.Exception) { // FIXME: maybe don't swallow all exceptions? return false; From 6eabf42b0f44e0fefa37a1547579c055a4dd2576 Mon Sep 17 00:00:00 2001 From: Aleksey Kliger Date: Fri, 28 Jun 2024 11:44:30 -0400 Subject: [PATCH 45/62] remove #if 0 --- src/coreclr/debug/daccess/request.cpp | 9 --------- 1 file changed, 9 deletions(-) diff --git a/src/coreclr/debug/daccess/request.cpp b/src/coreclr/debug/daccess/request.cpp index ff4950fb15c471..6abaabb885a854 100644 --- a/src/coreclr/debug/daccess/request.cpp +++ b/src/coreclr/debug/daccess/request.cpp @@ -161,15 +161,6 @@ BOOL DacValidateMethodTable(PTR_MethodTable pMT, BOOL &bIsFree) // In rare cases, we've seen the standard check above pass when it shouldn't. // Insert additional/ad-hoc tests below. - // FIXME(cdac): ak - this check is trivially true - GetCl() runs in the DAC and synthesizes a token with a mdtTypeDef table - // so the check is always true. -#if 0 - // Metadata token should look valid for a class - mdTypeDef td = pMT->GetCl(); - if (td != mdTokenNil && TypeFromToken(td) != mdtTypeDef) - goto BadMethodTable; -#endif - // BaseSize should always be greater than 0 for valid objects (unless it's an interface) // For strings, baseSize is not ptr-aligned if (!pMT->IsInterface() && !pMT->IsString()) From 4d3200d9fa35874bf21c9ab648535d23c659fdda Mon Sep 17 00:00:00 2001 From: Aleksey Kliger Date: Fri, 28 Jun 2024 11:58:16 -0400 Subject: [PATCH 46/62] cleanup --- docs/design/datacontracts/Metadata.md | 31 ++++++------ .../cdacreader/src/Contracts/Metadata_1.cs | 50 +++++++++---------- 2 files changed, 38 insertions(+), 43 deletions(-) diff --git a/docs/design/datacontracts/Metadata.md b/docs/design/datacontracts/Metadata.md index 1f8625a67abd0d..170a58da4aec2d 100644 --- a/docs/design/datacontracts/Metadata.md +++ b/docs/design/datacontracts/Metadata.md @@ -120,6 +120,7 @@ internal partial struct Metadata_1 CanonMT = 1, Mask = 1, } +} ``` Internally the contract has a `MethodTable_1` struct that depends on the `MethodTable` data descriptor @@ -160,7 +161,10 @@ The contract additionally depends on the `EEClass` data descriptor. public MethodTableHandle GetMethodTableHandle(TargetPointer methodTablePointer) { - ... + ... // validate that methodTablePointer points to something that looks like a MethodTable. + ... // read Data.MethodTable from methodTablePointer. + ... // create a MethodTable_1 and add it to _methodTables. + return MethodTableHandle { Address = methodTablePointer } } internal static EEClassOrCanonMTBits GetEEClassOrCanonMTBits(TargetPointer eeClassOrCanonMTPtr) @@ -172,12 +176,7 @@ The contract additionally depends on the `EEClass` data descriptor. public uint GetComponentSize(MethodTableHandle methodTableHandle) => GetComponentSize(_methodTables[methodTableHandle.Address]); - private Data.EEClass GetClassData(MethodTableHandle methodTableHandle) - { - ... - } - - private TargetPointer GetClass(MethodTableHandle methodTableHandle) + private TargetPointer GetClassPointer(MethodTableHandle methodTableHandle) { ... // if the MethodTable stores a pointer to the EEClass, return it // otherwise the MethodTable stores a pointer to the canonical MethodTable @@ -185,6 +184,13 @@ The contract additionally depends on the `EEClass` data descriptor. // Canonical MethodTables always store an EEClass pointer. } + private Data.EEClass GetClassData(MethodTableHandle methodTableHandle) + { + TargetPointer eeClassPtr = GetClassPointer(methodTableHandle); + ... // read Data.EEClass data from eeClassPtr + } + + public TargetPointer GetCanonicalMethodTable(MethodTableHandle methodTableHandle) => GetClassData(methodTableHandle).MethodTable; public TargetPointer GetModule(MethodTableHandle methodTableHandle) => _methodTables[methodTableHandle.Address].Module; @@ -201,18 +207,11 @@ The contract additionally depends on the `EEClass` data descriptor. return (uint)(methodTable.Flags.GetTypeDefRid() | ((int)TableIndex.TypeDef << 24)); } - public ushort GetNumMethods(MethodTableHandle methodTableHandle) - { - EEClass_1 cls = GetClassData(methodTableHandle); - return cls.NumMethods; - } + public ushort GetNumMethods(MethodTableHandle methodTableHandle) => GetClassData(methodTableHandle).NumMethods; public ushort GetNumInterfaces(MethodTableHandle methodTableHandle) => _methodTables[methodTableHandle.Address].NumInterfaces; - public uint GetTypeDefTypeAttributes(MethodTableHandle methodTableHandle) - { - return GetClassData(methodTableHandle).AttrClass; - } + public uint GetTypeDefTypeAttributes(MethodTableHandle methodTableHandle) => GetClassData(methodTableHandle).AttrClass; public bool IsDynamicStatics(MethodTableHandle methodTableHandle) => _methodTables[methodTableHandle.Address].Flags.GetFlag(WFLAGS2_ENUM.DynamicStatics) != 0; ``` diff --git a/src/native/managed/cdacreader/src/Contracts/Metadata_1.cs b/src/native/managed/cdacreader/src/Contracts/Metadata_1.cs index 173665547d3b59..61b3591e34ee97 100644 --- a/src/native/managed/cdacreader/src/Contracts/Metadata_1.cs +++ b/src/native/managed/cdacreader/src/Contracts/Metadata_1.cs @@ -144,7 +144,7 @@ private NonValidatedEEClass_1 GetUntrustedEEClassData(TargetPointer eeClassPoint public MethodTableHandle GetMethodTableHandle(TargetPointer methodTablePointer) { - // if we already trust that address, return a handle + // if we already validated this address, return a handle if (_methodTables.ContainsKey(methodTablePointer)) { return new MethodTableHandle(methodTablePointer); @@ -152,16 +152,16 @@ public MethodTableHandle GetMethodTableHandle(TargetPointer methodTablePointer) // Check if we cached the underlying data already if (_target.ProcessedData.TryGet(methodTablePointer, out Data.MethodTable? methodTableData)) { - // we already cached the data, we trust it, create the representation struct for our use + // we already cached the data, we must have validated the address, create the representation struct for our use MethodTable_1 trustedMethodTable = new MethodTable_1(methodTableData); _ = _methodTables.TryAdd(methodTablePointer, trustedMethodTable); return new MethodTableHandle(methodTablePointer); } - // Otherwse, don't trust it yet - NonValidatedMethodTable_1 untrustedMethodTable = GetUntrustedMethodTableData(methodTablePointer); + // Otherwse, get ready to validate + NonValidatedMethodTable_1 nonvalidatedMethodTable = GetUntrustedMethodTableData(methodTablePointer); - // if it's the free object method table, we can trust it + // if it's the free object method table, we trust it to be valid if (methodTablePointer == FreeObjectMethodTablePointer) { Data.MethodTable freeObjectMethodTableData = _target.ProcessedData.GetOrAdd(methodTablePointer); @@ -169,11 +169,11 @@ public MethodTableHandle GetMethodTableHandle(TargetPointer methodTablePointer) _ = _methodTables.TryAdd(methodTablePointer, trustedMethodTable); return new MethodTableHandle(methodTablePointer); } - if (!ValidateMethodTablePointer(untrustedMethodTable)) + if (!ValidateMethodTablePointer(nonvalidatedMethodTable)) { throw new ArgumentException("Invalid method table pointer"); } - // ok, we trust it, cache the data + // ok, we validated it, cache the data and add the MethodTable_1 struct to the dictionary Data.MethodTable trustedMethodTableData = _target.ProcessedData.GetOrAdd(methodTablePointer); MethodTable_1 trustedMethodTableF = new MethodTable_1(trustedMethodTableData); _ = _methodTables.TryAdd(methodTablePointer, trustedMethodTableF); @@ -182,12 +182,6 @@ public MethodTableHandle GetMethodTableHandle(TargetPointer methodTablePointer) private bool ValidateMethodTablePointer(NonValidatedMethodTable_1 umt) { - // FIXME: is methodTablePointer properly sign-extended from 32-bit targets? - // FIXME2: do we need this? Data.MethodTable probably would throw if methodTablePointer is invalid - //if (umt.MethodTablePointer == TargetPointer.Null || umt.MethodTablePointer == TargetPointer.MinusOne) - //{ - // return false; - //} try { if (!ValidateWithPossibleAV(umt)) @@ -241,6 +235,7 @@ private bool ValidateWithPossibleAV(NonValidatedMethodTable_1 methodTable) private bool ValidateMethodTable(NonValidatedMethodTable_1 methodTable) { + // ad-hoc checks; add more here as needed if (!methodTable.Flags.IsInterface && !methodTable.Flags.IsString) { if (methodTable.Flags.BaseSize == 0 || !_target.IsAlignedToPointerSize(methodTable.Flags.BaseSize)) @@ -281,20 +276,7 @@ private static uint GetComponentSize(MethodTable_1 methodTable) } public uint GetComponentSize(MethodTableHandle methodTableHandle) => GetComponentSize(_methodTables[methodTableHandle.Address]); - // only called on trusted method tables, so we always trust the resulting EEClass - private Data.EEClass GetClassData(MethodTableHandle methodTableHandle) - { - TargetPointer clsPtr = GetClass(methodTableHandle); - // Check if we cached it already - if (_target.ProcessedData.TryGet(clsPtr, out Data.EEClass? eeClassData)) - { - return eeClassData; - } - eeClassData = _target.ProcessedData.GetOrAdd(clsPtr); - return eeClassData; - } - - private TargetPointer GetClass(MethodTableHandle methodTableHandle) + private TargetPointer GetClassPointer(MethodTableHandle methodTableHandle) { MethodTable_1 methodTable = _methodTables[methodTableHandle.Address]; switch (GetEEClassOrCanonMTBits(methodTable.EEClassOrCanonMT)) @@ -310,6 +292,20 @@ private TargetPointer GetClass(MethodTableHandle methodTableHandle) throw new InvalidOperationException(); } } + + // only called on validated method tables, so we don't need to re-validate the EEClass + private Data.EEClass GetClassData(MethodTableHandle methodTableHandle) + { + TargetPointer clsPtr = GetClassPointer(methodTableHandle); + // Check if we cached it already + if (_target.ProcessedData.TryGet(clsPtr, out Data.EEClass? eeClassData)) + { + return eeClassData; + } + eeClassData = _target.ProcessedData.GetOrAdd(clsPtr); + return eeClassData; + } + public TargetPointer GetCanonicalMethodTable(MethodTableHandle methodTableHandle) => GetClassData(methodTableHandle).MethodTable; public TargetPointer GetModule(MethodTableHandle methodTableHandle) => _methodTables[methodTableHandle.Address].Module; From 2e667404c27de89f1560470e8b366c866580b39f Mon Sep 17 00:00:00 2001 From: Aleksey Kliger Date: Fri, 28 Jun 2024 17:10:28 -0400 Subject: [PATCH 47/62] pull test target helpers out goal is to be able to use this for testing contracts that depend on some data in the heap --- .../cdacreader/tests/TargetTestHelpers.cs | 191 ++++++++++++++++++ .../managed/cdacreader/tests/TargetTests.cs | 187 ++--------------- 2 files changed, 205 insertions(+), 173 deletions(-) create mode 100644 src/native/managed/cdacreader/tests/TargetTestHelpers.cs diff --git a/src/native/managed/cdacreader/tests/TargetTestHelpers.cs b/src/native/managed/cdacreader/tests/TargetTestHelpers.cs new file mode 100644 index 00000000000000..cc1e83b385f928 --- /dev/null +++ b/src/native/managed/cdacreader/tests/TargetTestHelpers.cs @@ -0,0 +1,191 @@ +// 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.Buffers.Binary; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Text; +using Xunit; + +namespace Microsoft.Diagnostics.DataContractReader.UnitTests; +internal unsafe class TargetTestHelpers +{ + private const ulong ContractDescriptorAddr = 0xaaaaaaaa; + private const uint JsonDescriptorAddr = 0xdddddddd; + private const uint PointerDataAddr = 0xeeeeeeee; + + // Note: all the spans should be stackalloc or pinned. + public static ReadContext CreateContext(ReadOnlySpan descriptor, ReadOnlySpan json, ReadOnlySpan pointerData = default) + { + return new ReadContext + { + ContractDescriptor = (byte*)Unsafe.AsPointer(ref MemoryMarshal.GetReference(descriptor)), + ContractDescriptorLength = descriptor.Length, + JsonDescriptor = (byte*)Unsafe.AsPointer(ref MemoryMarshal.GetReference(json)), + JsonDescriptorLength = json.Length, + PointerData = pointerData.Length > 0 ? (byte*)Unsafe.AsPointer(ref MemoryMarshal.GetReference(pointerData)) : (byte*)null, + PointerDataLength = pointerData.Length + }; + } + + public static bool TryCreateTarget(ReadContext* context, out Target? target) + { + return Target.TryCreate(ContractDescriptorAddr, &ReadFromTarget, context, out target); + } + + // FIXME: make somethign more usable + public static void AddHeapCallback(ref ReadContext context, delegate* callback, void* callbackContext) + { + context.HeapCallback = callback; + context.HeapCallbackContext = callbackContext; + } + + internal static class ContractDescriptor + { + public static int Size(bool is64Bit) => is64Bit ? sizeof(ContractDescriptor64) : sizeof(ContractDescriptor32); + + public static void Fill(Span dest, bool isLittleEndian, bool is64Bit, int jsonDescriptorSize, int pointerDataCount) + { + if (is64Bit) + { + ContractDescriptor64.Fill(dest, isLittleEndian, jsonDescriptorSize, pointerDataCount); + } + else + { + ContractDescriptor32.Fill(dest, isLittleEndian, jsonDescriptorSize, pointerDataCount); + } + } + + private struct ContractDescriptor32 + { + public ulong Magic = BitConverter.ToUInt64("DNCCDAC\0"u8); + public uint Flags = 0x2 /*32-bit*/ | 0x1; + public uint DescriptorSize; + public uint Descriptor = JsonDescriptorAddr; + public uint PointerDataCount; + public uint Pad0 = 0; + public uint PointerData = PointerDataAddr; + + public ContractDescriptor32() { } + + public static void Fill(Span dest, bool isLittleEndian, int jsonDescriptorSize, int pointerDataCount) + { + ContractDescriptor32 descriptor = new() + { + DescriptorSize = (uint)jsonDescriptorSize, + PointerDataCount = (uint)pointerDataCount, + }; + if (BitConverter.IsLittleEndian != isLittleEndian) + descriptor.ReverseEndianness(); + + MemoryMarshal.AsBytes(MemoryMarshal.CreateSpan(ref descriptor, 1)).CopyTo(dest); + } + + private void ReverseEndianness() + { + Magic = BinaryPrimitives.ReverseEndianness(Magic); + Flags = BinaryPrimitives.ReverseEndianness(Flags); + DescriptorSize = BinaryPrimitives.ReverseEndianness(DescriptorSize); + Descriptor = BinaryPrimitives.ReverseEndianness(Descriptor); + PointerDataCount = BinaryPrimitives.ReverseEndianness(PointerDataCount); + Pad0 = BinaryPrimitives.ReverseEndianness(Pad0); + PointerData = BinaryPrimitives.ReverseEndianness(PointerData); + } + } + + private struct ContractDescriptor64 + { + public ulong Magic = BitConverter.ToUInt64("DNCCDAC\0"u8); + public uint Flags = 0x1; + public uint DescriptorSize; + public ulong Descriptor = JsonDescriptorAddr; + public uint PointerDataCount; + public uint Pad0 = 0; + public ulong PointerData = PointerDataAddr; + + public ContractDescriptor64() { } + + public static void Fill(Span dest, bool isLittleEndian, int jsonDescriptorSize, int pointerDataCount) + { + ContractDescriptor64 descriptor = new() + { + DescriptorSize = (uint)jsonDescriptorSize, + PointerDataCount = (uint)pointerDataCount, + }; + if (BitConverter.IsLittleEndian != isLittleEndian) + descriptor.ReverseEndianness(); + + MemoryMarshal.AsBytes(MemoryMarshal.CreateSpan(ref descriptor, 1)).CopyTo(dest); + } + + private void ReverseEndianness() + { + Magic = BinaryPrimitives.ReverseEndianness(Magic); + Flags = BinaryPrimitives.ReverseEndianness(Flags); + DescriptorSize = BinaryPrimitives.ReverseEndianness(DescriptorSize); + Descriptor = BinaryPrimitives.ReverseEndianness(Descriptor); + PointerDataCount = BinaryPrimitives.ReverseEndianness(PointerDataCount); + Pad0 = BinaryPrimitives.ReverseEndianness(Pad0); + PointerData = BinaryPrimitives.ReverseEndianness(PointerData); + } + } + } + + [UnmanagedCallersOnly] + private static int ReadFromTarget(ulong address, byte* buffer, uint length, void* context) + { + ReadContext* readContext = (ReadContext*)context; + var span = new Span(buffer, (int)length); + + // Populate the span with the requested portion of the contract descriptor + if (address >= ContractDescriptorAddr && address <= ContractDescriptorAddr + (ulong)readContext->ContractDescriptorLength - length) + { + ulong offset = address - ContractDescriptorAddr; + new ReadOnlySpan(readContext->ContractDescriptor + offset, (int)length).CopyTo(span); + return 0; + } + + // Populate the span with the JSON descriptor - this assumes the product will read it all at once. + if (address == JsonDescriptorAddr) + { + new ReadOnlySpan(readContext->JsonDescriptor, readContext->JsonDescriptorLength).CopyTo(span); + return 0; + } + + // Populate the span with the requested portion of the pointer data + if (address >= PointerDataAddr && address <= PointerDataAddr + (ulong)readContext->PointerDataLength - length) + { + ulong offset = address - PointerDataAddr; + new ReadOnlySpan(readContext->PointerData + offset, (int)length).CopyTo(span); + return 0; + } + + if (readContext->HeapCallback != null) + { + return readContext->HeapCallback(address, buffer, length, readContext->HeapCallbackContext); + } + + return -1; + } + + // Used by ReadFromTarget to return the appropriate bytes + internal ref struct ReadContext + { + public byte* ContractDescriptor; + public int ContractDescriptorLength; + + public byte* JsonDescriptor; + public int JsonDescriptorLength; + + public byte* PointerData; + public int PointerDataLength; + + public delegate* HeapCallback; + + public void* HeapCallbackContext; + } + +} diff --git a/src/native/managed/cdacreader/tests/TargetTests.cs b/src/native/managed/cdacreader/tests/TargetTests.cs index ec1899327b3fdf..72ae53aa21a46c 100644 --- a/src/native/managed/cdacreader/tests/TargetTests.cs +++ b/src/native/managed/cdacreader/tests/TargetTests.cs @@ -14,12 +14,9 @@ namespace Microsoft.Diagnostics.DataContractReader.UnitTests; public unsafe class TargetTests { - private const ulong ContractDescriptorAddr = 0xaaaaaaaa; - private const uint JsonDescriptorAddr = 0xdddddddd; - private const uint PointerDataAddr = 0xeeeeeeee; private static readonly (DataType Type, Target.TypeInfo Info)[] TestTypes = - [ + [ // Size and fields (DataType.Thread, new(){ Size = 56, @@ -57,19 +54,13 @@ public void GetTypeInfo(bool isLittleEndian, bool is64Bit) "globals": {} } """); - Span descriptor = stackalloc byte[ContractDescriptor.Size(is64Bit)]; - ContractDescriptor.Fill(descriptor, isLittleEndian, is64Bit, json.Length, 0); + Span descriptor = stackalloc byte[TargetTestHelpers.ContractDescriptor.Size(is64Bit)]; + TargetTestHelpers.ContractDescriptor.Fill(descriptor, isLittleEndian, is64Bit, json.Length, 0); fixed (byte* jsonPtr = json) { - ReadContext context = new ReadContext - { - ContractDescriptor = (byte*)Unsafe.AsPointer(ref MemoryMarshal.GetReference(descriptor)), - ContractDescriptorLength = descriptor.Length, - JsonDescriptor = jsonPtr, - JsonDescriptorLength = json.Length, - }; + TargetTestHelpers.ReadContext context = TargetTestHelpers.CreateContext(descriptor, json); - bool success = Target.TryCreate(ContractDescriptorAddr, &ReadFromTarget, &context, out Target? target); + bool success = TargetTestHelpers.TryCreateTarget(&context, out Target? target); Assert.True(success); foreach ((DataType type, Target.TypeInfo info) in TestTypes) @@ -131,19 +122,13 @@ public void ReadGlobalValue(bool isLittleEndian, bool is64Bit) "globals": { {{globalsJson}} } } """); - Span descriptor = stackalloc byte[ContractDescriptor.Size(is64Bit)]; - ContractDescriptor.Fill(descriptor, isLittleEndian, is64Bit, json.Length, 0); + Span descriptor = stackalloc byte[TargetTestHelpers.ContractDescriptor.Size(is64Bit)]; + TargetTestHelpers.ContractDescriptor.Fill(descriptor, isLittleEndian, is64Bit, json.Length, 0); fixed (byte* jsonPtr = json) { - ReadContext context = new ReadContext - { - ContractDescriptor = (byte*)Unsafe.AsPointer(ref MemoryMarshal.GetReference(descriptor)), - ContractDescriptorLength = descriptor.Length, - JsonDescriptor = jsonPtr, - JsonDescriptorLength = json.Length, - }; + TargetTestHelpers.ReadContext context = TargetTestHelpers.CreateContext(descriptor, json); - bool success = Target.TryCreate(ContractDescriptorAddr, &ReadFromTarget, &context, out Target? target); + bool success = TargetTestHelpers.TryCreateTarget(&context, out Target? target); Assert.True(success); ValidateGlobals(target, TestGlobals); @@ -175,21 +160,13 @@ public void ReadIndirectGlobalValue(bool isLittleEndian, bool is64Bit) "globals": { {{globalsJson}} } } """); - Span descriptor = stackalloc byte[ContractDescriptor.Size(is64Bit)]; - ContractDescriptor.Fill(descriptor, isLittleEndian, is64Bit, json.Length, pointerData.Length / pointerSize); + Span descriptor = stackalloc byte[TargetTestHelpers.ContractDescriptor.Size(is64Bit)]; + TargetTestHelpers.ContractDescriptor.Fill(descriptor, isLittleEndian, is64Bit, json.Length, pointerData.Length / pointerSize); fixed (byte* jsonPtr = json) { - ReadContext context = new ReadContext - { - ContractDescriptor = (byte*)Unsafe.AsPointer(ref MemoryMarshal.GetReference(descriptor)), - ContractDescriptorLength = descriptor.Length, - JsonDescriptor = jsonPtr, - JsonDescriptorLength = json.Length, - PointerData = (byte*)Unsafe.AsPointer(ref MemoryMarshal.GetReference(pointerData)), - PointerDataLength = pointerData.Length - }; + TargetTestHelpers.ReadContext context = TargetTestHelpers.CreateContext(descriptor, json, pointerData); - bool success = Target.TryCreate(ContractDescriptorAddr, &ReadFromTarget, &context, out Target? target); + bool success = TargetTestHelpers.TryCreateTarget(&context, out Target? target); Assert.True(success); // Indirect values are pointer-sized, so max 32-bits for a 32-bit target @@ -302,146 +279,10 @@ private static void ValidateGlobals( } } - void AssertEqualsWithCallerInfo(T expected, T actual) + void AssertEqualsWithCallerInfo(T expected, T actual) { Assert.True((expected is null && actual is null) || expected.Equals(actual), $"Expected: {expected}. Actual: {actual}. [test case: {caller} in {filePath}:{lineNumber}]"); } } - [UnmanagedCallersOnly] - private static int ReadFromTarget(ulong address, byte* buffer, uint length, void* context) - { - ReadContext* readContext = (ReadContext*)context; - var span = new Span(buffer, (int)length); - - // Populate the span with the requested portion of the contract descriptor - if (address >= ContractDescriptorAddr && address <= ContractDescriptorAddr + (ulong)readContext->ContractDescriptorLength - length) - { - ulong offset = address - ContractDescriptorAddr; - new ReadOnlySpan(readContext->ContractDescriptor + offset, (int)length).CopyTo(span); - return 0; - } - - // Populate the span with the JSON descriptor - this assumes the product will read it all at once. - if (address == JsonDescriptorAddr) - { - new ReadOnlySpan(readContext->JsonDescriptor, readContext->JsonDescriptorLength).CopyTo(span); - return 0; - } - - // Populate the span with the requested portion of the pointer data - if (address >= PointerDataAddr && address <= PointerDataAddr + (ulong)readContext->PointerDataLength - length) - { - ulong offset = address - PointerDataAddr; - new ReadOnlySpan(readContext->PointerData + offset, (int)length).CopyTo(span); - return 0; - } - - return -1; - } - - // Used by ReadFromTarget to return the appropriate bytes - private struct ReadContext - { - public byte* ContractDescriptor; - public int ContractDescriptorLength; - - public byte* JsonDescriptor; - public int JsonDescriptorLength; - - public byte* PointerData; - public int PointerDataLength; - } - - private static class ContractDescriptor - { - public static int Size(bool is64Bit) => is64Bit ? sizeof(ContractDescriptor64) : sizeof(ContractDescriptor32); - - public static void Fill(Span dest, bool isLittleEndian, bool is64Bit, int jsonDescriptorSize, int pointerDataCount) - { - if (is64Bit) - { - ContractDescriptor64.Fill(dest, isLittleEndian, jsonDescriptorSize, pointerDataCount); - } - else - { - ContractDescriptor32.Fill(dest, isLittleEndian, jsonDescriptorSize, pointerDataCount); - } - } - - private struct ContractDescriptor32 - { - public ulong Magic = BitConverter.ToUInt64("DNCCDAC\0"u8); - public uint Flags = 0x2 /*32-bit*/ | 0x1; - public uint DescriptorSize; - public uint Descriptor = JsonDescriptorAddr; - public uint PointerDataCount; - public uint Pad0 = 0; - public uint PointerData = PointerDataAddr; - - public ContractDescriptor32() { } - - public static void Fill(Span dest, bool isLittleEndian, int jsonDescriptorSize, int pointerDataCount) - { - ContractDescriptor32 descriptor = new() - { - DescriptorSize = (uint)jsonDescriptorSize, - PointerDataCount = (uint)pointerDataCount, - }; - if (BitConverter.IsLittleEndian != isLittleEndian) - descriptor.ReverseEndianness(); - - MemoryMarshal.AsBytes(MemoryMarshal.CreateSpan(ref descriptor, 1)).CopyTo(dest); - } - - private void ReverseEndianness() - { - Magic = BinaryPrimitives.ReverseEndianness(Magic); - Flags = BinaryPrimitives.ReverseEndianness(Flags); - DescriptorSize = BinaryPrimitives.ReverseEndianness(DescriptorSize); - Descriptor = BinaryPrimitives.ReverseEndianness(Descriptor); - PointerDataCount = BinaryPrimitives.ReverseEndianness(PointerDataCount); - Pad0 = BinaryPrimitives.ReverseEndianness(Pad0); - PointerData = BinaryPrimitives.ReverseEndianness(PointerData); - } - } - - private struct ContractDescriptor64 - { - public ulong Magic = BitConverter.ToUInt64("DNCCDAC\0"u8); - public uint Flags = 0x1; - public uint DescriptorSize; - public ulong Descriptor = JsonDescriptorAddr; - public uint PointerDataCount; - public uint Pad0 = 0; - public ulong PointerData = PointerDataAddr; - - public ContractDescriptor64() { } - - public static void Fill(Span dest, bool isLittleEndian, int jsonDescriptorSize, int pointerDataCount) - { - ContractDescriptor64 descriptor = new() - { - DescriptorSize = (uint)jsonDescriptorSize, - PointerDataCount = (uint)pointerDataCount, - }; - if (BitConverter.IsLittleEndian != isLittleEndian) - descriptor.ReverseEndianness(); - - MemoryMarshal.AsBytes(MemoryMarshal.CreateSpan(ref descriptor, 1)).CopyTo(dest); - } - - private void ReverseEndianness() - { - Magic = BinaryPrimitives.ReverseEndianness(Magic); - Flags = BinaryPrimitives.ReverseEndianness(Flags); - DescriptorSize = BinaryPrimitives.ReverseEndianness(DescriptorSize); - Descriptor = BinaryPrimitives.ReverseEndianness(Descriptor); - PointerDataCount = BinaryPrimitives.ReverseEndianness(PointerDataCount); - Pad0 = BinaryPrimitives.ReverseEndianness(Pad0); - PointerData = BinaryPrimitives.ReverseEndianness(PointerData); - } - } - } - } From 8533148140d98face596108500d4234c419d722d Mon Sep 17 00:00:00 2001 From: Aleksey Kliger Date: Mon, 1 Jul 2024 16:07:41 -0400 Subject: [PATCH 48/62] Add one FreeObjectMethodTable unit test --- .../debug/runtimeinfo/datadescriptor.h | 7 - src/coreclr/vm/class.h | 1 - .../managed/cdacreader/src/Data/EEClass.cs | 2 - .../managed/cdacreader/src/cdacreader.csproj | 3 + .../cdacreader/tests/MethodTableTests.cs | 129 ++++++++++ .../cdacreader/tests/TargetTestHelpers.cs | 238 ++++++++++++++++-- .../managed/cdacreader/tests/TargetTests.cs | 62 ++--- 7 files changed, 363 insertions(+), 79 deletions(-) create mode 100644 src/native/managed/cdacreader/tests/MethodTableTests.cs diff --git a/src/coreclr/debug/runtimeinfo/datadescriptor.h b/src/coreclr/debug/runtimeinfo/datadescriptor.h index 76f3c1227ab383..9231a3d7efdc55 100644 --- a/src/coreclr/debug/runtimeinfo/datadescriptor.h +++ b/src/coreclr/debug/runtimeinfo/datadescriptor.h @@ -165,7 +165,6 @@ CDAC_TYPE_FIELD(MethodTable, /*uint32*/, BaseSize, cdac_offsets::Ba CDAC_TYPE_FIELD(MethodTable, /*uint32*/, MTFlags2, cdac_offsets::MTFlags2) CDAC_TYPE_FIELD(MethodTable, /*nuint*/, EEClassOrCanonMT, cdac_offsets::EEClassOrCanonMT) CDAC_TYPE_FIELD(MethodTable, /*pointer*/, Module, cdac_offsets::Module) -CDAC_TYPE_FIELD(MethodTable, /*pointer*/, AuxiliaryData, cdac_offsets::AuxiliaryData) CDAC_TYPE_FIELD(MethodTable, /*pointer*/, ParentMethodTable, cdac_offsets::ParentMethodTable) CDAC_TYPE_FIELD(MethodTable, /*uint16*/, NumInterfaces, cdac_offsets::NumInterfaces) CDAC_TYPE_FIELD(MethodTable, /*uint16*/, NumVirtuals, cdac_offsets::NumVirtuals) @@ -175,15 +174,9 @@ CDAC_TYPE_BEGIN(EEClass) CDAC_TYPE_INDETERMINATE(EEClass) CDAC_TYPE_FIELD(EEClass, /*pointer*/, MethodTable, cdac_offsets::MethodTable) CDAC_TYPE_FIELD(EEClass, /*uint16*/, NumMethods, cdac_offsets::NumMethods) -CDAC_TYPE_FIELD(EEClass, /*uint16*/, NumNonVirtualSlots, cdac_offsets::NumNonVirtualSlots) CDAC_TYPE_FIELD(EEClass, /*uint32*/, AttrClass, cdac_offsets::AttrClass) CDAC_TYPE_END(EEClass) -CDAC_TYPE_BEGIN(MethodTableAuxiliaryData) -CDAC_TYPE_INDETERMINATE(MethodTableAuxiliaryData) -CDAC_TYPE_FIELD(MethodTableAuxiliaryData, /*uint32*/, AuxFlags, offsetof(MethodTableAuxiliaryData, m_dwFlags)) -CDAC_TYPE_END(MethodTableAuxiliaryData) - CDAC_TYPES_END() CDAC_GLOBALS_BEGIN() diff --git a/src/coreclr/vm/class.h b/src/coreclr/vm/class.h index a247342360515a..95144fb4c5ad06 100644 --- a/src/coreclr/vm/class.h +++ b/src/coreclr/vm/class.h @@ -1805,7 +1805,6 @@ template<> struct cdac_offsets { static constexpr size_t MethodTable = offsetof(EEClass, m_pMethodTable); static constexpr size_t NumMethods = offsetof(EEClass, m_NumMethods); - static constexpr size_t NumNonVirtualSlots = offsetof(EEClass, m_NumNonVirtualSlots); static constexpr size_t AttrClass = offsetof(EEClass, m_dwAttrClass); }; diff --git a/src/native/managed/cdacreader/src/Data/EEClass.cs b/src/native/managed/cdacreader/src/Data/EEClass.cs index ec056e9695e11a..eb3cf6e7acb4fd 100644 --- a/src/native/managed/cdacreader/src/Data/EEClass.cs +++ b/src/native/managed/cdacreader/src/Data/EEClass.cs @@ -14,12 +14,10 @@ public EEClass(Target target, TargetPointer address) MethodTable = target.ReadPointer(address + (ulong)type.Fields[nameof(MethodTable)].Offset); NumMethods = target.Read(address + (ulong)type.Fields[nameof(NumMethods)].Offset); - NumNonVirtualSlots = target.Read(address + (ulong)type.Fields[nameof(NumNonVirtualSlots)].Offset); AttrClass = target.Read(address + (ulong)type.Fields[nameof(AttrClass)].Offset); } public TargetPointer MethodTable { get; init; } public ushort NumMethods { get; init; } - public ushort NumNonVirtualSlots { get; init; } public uint AttrClass { get; init; } } diff --git a/src/native/managed/cdacreader/src/cdacreader.csproj b/src/native/managed/cdacreader/src/cdacreader.csproj index 20ecd197c70460..1c973cac51a571 100644 --- a/src/native/managed/cdacreader/src/cdacreader.csproj +++ b/src/native/managed/cdacreader/src/cdacreader.csproj @@ -15,6 +15,9 @@ + + + diff --git a/src/native/managed/cdacreader/tests/MethodTableTests.cs b/src/native/managed/cdacreader/tests/MethodTableTests.cs new file mode 100644 index 00000000000000..a29fb3ff5b68ca --- /dev/null +++ b/src/native/managed/cdacreader/tests/MethodTableTests.cs @@ -0,0 +1,129 @@ +// 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.Buffers.Binary; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Text; +using Microsoft.Diagnostics.DataContractReader.Data; +using Xunit; + +namespace Microsoft.Diagnostics.DataContractReader.UnitTests; + +public unsafe class MethodTableTests +{ + struct TargetConfig + { + public bool IsLittleEndian { get; init; } + public bool Is64Bit { get; init; } + } + const ulong TestFreeObjectMethodTableGlobalAddress = 0x00000000_7a0000a0; + const ulong TestFreeObjectMethodTableAddress = 0x00000000_7a0000a8; + + private static readonly Target.TypeInfo MethodTableTypeInfo = new() + { + Fields = { + { nameof(Data.MethodTable.MTFlags), new() { Offset = 4, Type = DataType.uint32}}, + { nameof(Data.MethodTable.BaseSize), new() { Offset = 8, Type = DataType.uint32}}, + { nameof(Data.MethodTable.MTFlags2), new() { Offset = 12, Type = DataType.uint32}}, + { nameof(Data.MethodTable.EEClassOrCanonMT), new () { Offset = 16, Type = DataType.nuint}}, + { nameof(Data.MethodTable.Module), new () { Offset = 24, Type = DataType.pointer}}, + { nameof(Data.MethodTable.ParentMethodTable), new () { Offset = 40, Type = DataType.pointer}}, + { nameof(Data.MethodTable.NumInterfaces), new () { Offset = 48, Type = DataType.uint16}}, + { nameof(Data.MethodTable.NumVirtuals), new () { Offset = 50, Type = DataType.uint16}}, + } + }; + + private static readonly (DataType Type, Target.TypeInfo Info)[] MetadataTypes = + [ + (DataType.MethodTable, MethodTableTypeInfo), + (DataType.EEClass, new(){ + Fields = { + { nameof (Data.EEClass.MethodTable), new () { Offset = 8, Type = DataType.pointer}}, + { nameof (Data.EEClass.AttrClass), new () { Offset = 16, Type = DataType.uint32}}, + { nameof (Data.EEClass.NumMethods), new () { Offset = 20, Type = DataType.uint16}}, + }}), + ]; + + + private static readonly (string Name, ulong Value, string? Type)[] MetadataGlobals = + [ + (nameof(Constants.Globals.FreeObjectMethodTable), TestFreeObjectMethodTableGlobalAddress, null), + ]; + + private static TargetTestHelpers.HeapFragment[] FreeObjectMethodTableHeapFragments(TargetConfig targetConfig) + { + var globalAddr = new TargetTestHelpers.HeapFragment { Name = "Address of Free Object Method Table", Address = TestFreeObjectMethodTableGlobalAddress, Data = new byte[targetConfig.Is64Bit ? 8 : 4] }; + TargetTestHelpers.WritePointer(globalAddr.Data, TestFreeObjectMethodTableAddress, targetConfig.IsLittleEndian, targetConfig.Is64Bit ? 8 : 4); + return [ + globalAddr, + new TargetTestHelpers.HeapFragment { Name = "Free Object Method Table", Address = TestFreeObjectMethodTableAddress, Data = new byte[TargetTestHelpers.SizeOfTypeInfo(targetConfig.Is64Bit, MethodTableTypeInfo)] } + ]; + } + + private static void MetadataContractHelper(TargetConfig targetConfig, Action testCase) + { + string metadataTypesJson = TargetTestHelpers.MakeTypesJson(MetadataTypes); + string metadataGlobalsJson = TargetTestHelpers.MakeGlobalsJson(MetadataGlobals); + byte[] json = Encoding.UTF8.GetBytes($$""" + { + "version": 0, + "baseline": "empty", + "contracts": { + "Metadata": 1 + }, + "types": { {{metadataTypesJson}} }, + "globals": { {{metadataGlobalsJson}} } + } + """); + Span descriptor = stackalloc byte[TargetTestHelpers.ContractDescriptor.Size(targetConfig.Is64Bit)]; + TargetTestHelpers.ContractDescriptor.Fill(descriptor, targetConfig.IsLittleEndian, targetConfig.Is64Bit, json.Length, MetadataGlobals.Length); + + int pointerSize = targetConfig.Is64Bit ? sizeof(ulong) : sizeof(uint); + Span pointerData = stackalloc byte[MetadataGlobals.Length * pointerSize]; + for (int i = 0; i < MetadataGlobals.Length; i++) + { + var (_, value, _) = MetadataGlobals[i]; + TargetTestHelpers.WritePointer(pointerData.Slice(i * pointerSize), value, targetConfig.IsLittleEndian, pointerSize); + } + + fixed (byte* jsonPtr = json) + { + TargetTestHelpers.ContextBuilder builder = new(); + + builder = builder.SetDescriptor(descriptor) + .SetJson(json) + .SetPointerData(pointerData) + .AddHeapFragments(FreeObjectMethodTableHeapFragments(targetConfig)); + + using TargetTestHelpers.ReadContext context = builder.Create(); + + bool success = TargetTestHelpers.TryCreateTarget(&context, out Target? target); + Assert.True(success); + + testCase(targetConfig, target); + } + GC.KeepAlive(json); + } + + [Theory] + [InlineData(true, true)] + [InlineData(true, false)] + [InlineData(false, true)] + [InlineData(false, false)] + public void HasMetadataContract(bool isLittleEndian, bool is64Bit) + { + TargetConfig targetConfig = new TargetConfig { IsLittleEndian = isLittleEndian, Is64Bit = is64Bit }; + MetadataContractHelper(targetConfig, static (arch, target) => + { + Contracts.IMetadata metadataContract = target.Contracts.Metadata; + Assert.NotNull(metadataContract); + Contracts.MethodTableHandle handle = metadataContract.GetMethodTableHandle(TestFreeObjectMethodTableAddress); + Assert.NotEqual(TargetPointer.Null, handle.Address); + Assert.True(metadataContract.IsFreeObjectMethodTable(handle)); + }); + } +} diff --git a/src/native/managed/cdacreader/tests/TargetTestHelpers.cs b/src/native/managed/cdacreader/tests/TargetTestHelpers.cs index cc1e83b385f928..dd9bf78d56b57c 100644 --- a/src/native/managed/cdacreader/tests/TargetTestHelpers.cs +++ b/src/native/managed/cdacreader/tests/TargetTestHelpers.cs @@ -17,18 +17,113 @@ internal unsafe class TargetTestHelpers private const uint JsonDescriptorAddr = 0xdddddddd; private const uint PointerDataAddr = 0xeeeeeeee; + internal struct HeapFragment + { + public ulong Address; + public byte[] Data; + public string? Name; + } + + /// + /// Helper to build a context for reading from a target. + /// + /// + /// All the spans should be stackalloc or pinned while the context is being used. + /// + internal unsafe ref struct ContextBuilder + { + private bool _created = false; + private byte* _descriptor = null; + private int _descriptorLength = 0; + private byte* _json = null; + private int _jsonLength = 0; + private byte* _pointerData = null; + private int _pointerDataLength = 0; + private List _heapFragments = new(); + + public ContextBuilder() + { + + } + + public ContextBuilder SetDescriptor(scoped ReadOnlySpan descriptor) + { + if (_created) + throw new InvalidOperationException("Context already created"); + _descriptor = (byte*)Unsafe.AsPointer(ref MemoryMarshal.GetReference(descriptor)); + _descriptorLength = descriptor.Length; + return this; + } + + public ContextBuilder SetJson(scoped ReadOnlySpan json) + { + if (_created) + throw new InvalidOperationException("Context already created"); + _json = (byte*)Unsafe.AsPointer(ref MemoryMarshal.GetReference(json)); + _jsonLength = json.Length; + return this; + } + + public ContextBuilder SetPointerData(scoped ReadOnlySpan pointerData) + { + if (_created) + throw new InvalidOperationException("Context already created"); + if (pointerData.Length >= 0) + { + _pointerData = (byte*)Unsafe.AsPointer(ref MemoryMarshal.GetReference(pointerData)); + _pointerDataLength = pointerData.Length; + } + return this; + } + + public ContextBuilder AddHeapFragment(HeapFragment fragment) + { + if (_created) + throw new InvalidOperationException("Context already created"); + _heapFragments.Add(fragment); + return this; + } + + public ContextBuilder AddHeapFragments(IEnumerable fragments) + { + if (_created) + throw new InvalidOperationException("Context already created"); + _heapFragments.AddRange(fragments); + return this; + } + + public ReadContext Create() + { + if (_created) + throw new InvalidOperationException("Context already created"); + GCHandle fragmentReaderHandle = default; ; + if (_heapFragments.Count > 0) + { + fragmentReaderHandle = GCHandle.Alloc(new HeapFragmentReader(_heapFragments)); + } + ReadContext context = new ReadContext + { + ContractDescriptor = _descriptor, + ContractDescriptorLength = _descriptorLength, + JsonDescriptor = _json, + JsonDescriptorLength = _jsonLength, + PointerData = _pointerData, + PointerDataLength = _pointerDataLength, + HeapFragmentReader = GCHandle.ToIntPtr(fragmentReaderHandle) + }; + _created = true; + return context; + } + } + // Note: all the spans should be stackalloc or pinned. public static ReadContext CreateContext(ReadOnlySpan descriptor, ReadOnlySpan json, ReadOnlySpan pointerData = default) { - return new ReadContext - { - ContractDescriptor = (byte*)Unsafe.AsPointer(ref MemoryMarshal.GetReference(descriptor)), - ContractDescriptorLength = descriptor.Length, - JsonDescriptor = (byte*)Unsafe.AsPointer(ref MemoryMarshal.GetReference(json)), - JsonDescriptorLength = json.Length, - PointerData = pointerData.Length > 0 ? (byte*)Unsafe.AsPointer(ref MemoryMarshal.GetReference(pointerData)) : (byte*)null, - PointerDataLength = pointerData.Length - }; + ContextBuilder builder = new ContextBuilder() + .SetJson(json) + .SetDescriptor(descriptor) + .SetPointerData(pointerData); + return builder.Create(); } public static bool TryCreateTarget(ReadContext* context, out Target? target) @@ -36,13 +131,6 @@ public static bool TryCreateTarget(ReadContext* context, out Target? target) return Target.TryCreate(ContractDescriptorAddr, &ReadFromTarget, context, out target); } - // FIXME: make somethign more usable - public static void AddHeapCallback(ref ReadContext context, delegate* callback, void* callbackContext) - { - context.HeapCallback = callback; - context.HeapCallbackContext = callbackContext; - } - internal static class ContractDescriptor { public static int Size(bool is64Bit) => is64Bit ? sizeof(ContractDescriptor64) : sizeof(ContractDescriptor32); @@ -163,16 +251,17 @@ private static int ReadFromTarget(ulong address, byte* buffer, uint length, void return 0; } - if (readContext->HeapCallback != null) + HeapFragmentReader? heapFragmentReader = GCHandle.FromIntPtr(readContext->HeapFragmentReader).Target as HeapFragmentReader; + if (heapFragmentReader is not null) { - return readContext->HeapCallback(address, buffer, length, readContext->HeapCallbackContext); + return heapFragmentReader.ReadFragment(address, span); } return -1; } // Used by ReadFromTarget to return the appropriate bytes - internal ref struct ReadContext + internal ref struct ReadContext : IDisposable { public byte* ContractDescriptor; public int ContractDescriptorLength; @@ -183,9 +272,116 @@ internal ref struct ReadContext public byte* PointerData; public int PointerDataLength; - public delegate* HeapCallback; + public IntPtr HeapFragmentReader; + + public void Dispose() + { + if (HeapFragmentReader != IntPtr.Zero) + { + GCHandle.FromIntPtr(HeapFragmentReader).Free(); + HeapFragmentReader = IntPtr.Zero; + } + } + } + + private class HeapFragmentReader + { + private readonly IReadOnlyList _fragments; + public HeapFragmentReader(IReadOnlyList fragments) + { + _fragments = fragments; + } + + public int ReadFragment(ulong address, Span buffer) + { + foreach (var fragment in _fragments) + { + if (address >= fragment.Address && address < fragment.Address + (ulong)fragment.Data.Length) + { + int offset = (int)(address - fragment.Address); + int availableLength = fragment.Data.Length - offset; + if (availableLength >= buffer.Length) + { + fragment.Data.AsSpan(offset, buffer.Length).CopyTo(buffer); + return 0; + } + else + { + throw new InvalidOperationException($"Not enough data in fragment at {fragment.Address:X} ('{fragment.Name}') to read {buffer.Length} bytes at {address:X} (only {availableLength} bytes available)"); + } + } + } + return -1; + } + } - public void* HeapCallbackContext; + private static string GetTypeJson(string name, Target.TypeInfo info) + { + string ret = string.Empty; + List fields = info.Size is null ? [] : [$"\"!\":{info.Size}"]; + fields.AddRange(info.Fields.Select(f => $"\"{f.Key}\":{(f.Value.TypeName is null ? f.Value.Offset : $"[{f.Value.Offset},\"{f.Value.TypeName}\"]")}")); + return $"\"{name}\":{{{string.Join(',', fields)}}}"; } + public static string MakeTypesJson(IEnumerable<(DataType Type, Target.TypeInfo Info)> types) + { + return string.Join(',', types.Select(t => GetTypeJson(t.Type.ToString(), t.Info))); + } + + public static string MakeGlobalsJson(IEnumerable<(string Name, ulong Value, string? Type)> globals) + { + return string.Join(',', globals.Select(i => $"\"{i.Name}\": {(i.Type is null ? i.Value.ToString() : $"[{i.Value}, \"{i.Type}\"]")}")); + } + + internal static void WritePointer(Span dest, ulong value, bool isLittleEndian, int pointerSize) + { + if (pointerSize == sizeof(ulong)) + { + if (isLittleEndian) + { + BinaryPrimitives.WriteUInt64LittleEndian(dest, value); + } + else + { + BinaryPrimitives.WriteUInt64BigEndian(dest, value); + } + } + else if (pointerSize == sizeof(uint)) + { + if (isLittleEndian) + { + BinaryPrimitives.WriteUInt32LittleEndian(dest, (uint)value); + } + else + { + BinaryPrimitives.WriteUInt32BigEndian(dest, (uint)value); + } + } + } + + internal static int SizeOfPrimitive(DataType type, bool is64Bit) + { + return type switch + { + DataType.uint8 or DataType.int8 => sizeof(byte), + DataType.uint16 or DataType.int16 => sizeof(ushort), + DataType.uint32 or DataType.int32 => sizeof(uint), + DataType.uint64 or DataType.int64 => sizeof(ulong), + DataType.pointer or DataType.nint or DataType.nuint => is64Bit ? sizeof(ulong) : sizeof(uint), + _ => throw new InvalidOperationException($"Not a primitive: {type}"), + }; + } + + internal static int SizeOfTypeInfo(bool is64Bit, Target.TypeInfo info) + { + int size = 0; + foreach (var (_, field) in info.Fields) + { + size = Math.Max(size, field.Offset + SizeOfPrimitive(field.Type, is64Bit)); + } + + return size; + } + + } diff --git a/src/native/managed/cdacreader/tests/TargetTests.cs b/src/native/managed/cdacreader/tests/TargetTests.cs index 72ae53aa21a46c..15f42929b94627 100644 --- a/src/native/managed/cdacreader/tests/TargetTests.cs +++ b/src/native/managed/cdacreader/tests/TargetTests.cs @@ -44,21 +44,21 @@ private static readonly (DataType Type, Target.TypeInfo Info)[] TestTypes = [InlineData(false, false)] public void GetTypeInfo(bool isLittleEndian, bool is64Bit) { - string typesJson = string.Join(',', TestTypes.Select(t => GetTypeJson(t.Type.ToString(), t.Info))); + string typesJson = TargetTestHelpers.MakeTypesJson(TestTypes); byte[] json = Encoding.UTF8.GetBytes($$""" - { - "version": 0, - "baseline": "empty", - "contracts": {}, - "types": { {{typesJson}} }, - "globals": {} - } - """); + { + "version": 0, + "baseline": "empty", + "contracts": {}, + "types": { {{typesJson}} }, + "globals": {} + } + """); Span descriptor = stackalloc byte[TargetTestHelpers.ContractDescriptor.Size(is64Bit)]; TargetTestHelpers.ContractDescriptor.Fill(descriptor, isLittleEndian, is64Bit, json.Length, 0); fixed (byte* jsonPtr = json) { - TargetTestHelpers.ReadContext context = TargetTestHelpers.CreateContext(descriptor, json); + using TargetTestHelpers.ReadContext context = TargetTestHelpers.CreateContext(descriptor, json); bool success = TargetTestHelpers.TryCreateTarget(&context, out Target? target); Assert.True(success); @@ -79,14 +79,6 @@ public void GetTypeInfo(bool isLittleEndian, bool is64Bit) } } } - - static string GetTypeJson(string name, Target.TypeInfo info) - { - string ret = string.Empty; - List fields = info.Size is null ? [] : [$"\"!\":{info.Size}"]; - fields.AddRange(info.Fields.Select(f => $"\"{f.Key}\":{(f.Value.TypeName is null ? f.Value.Offset : $"[{f.Value.Offset},\"{f.Value.TypeName}\"]")}")); - return $"\"{name}\":{{{string.Join(',', fields)}}}"; - } } private static readonly (string Name, ulong Value, string? Type)[] TestGlobals = @@ -112,7 +104,7 @@ private static readonly (string Name, ulong Value, string? Type)[] TestGlobals = [InlineData(false, false)] public void ReadGlobalValue(bool isLittleEndian, bool is64Bit) { - string globalsJson = string.Join(',', TestGlobals.Select(i => $"\"{i.Name}\": {(i.Type is null ? i.Value.ToString() : $"[{i.Value}, \"{i.Type}\"]")}")); + string globalsJson = TargetTestHelpers.MakeGlobalsJson(TestGlobals); byte[] json = Encoding.UTF8.GetBytes($$""" { "version": 0, @@ -126,7 +118,7 @@ public void ReadGlobalValue(bool isLittleEndian, bool is64Bit) TargetTestHelpers.ContractDescriptor.Fill(descriptor, isLittleEndian, is64Bit, json.Length, 0); fixed (byte* jsonPtr = json) { - TargetTestHelpers.ReadContext context = TargetTestHelpers.CreateContext(descriptor, json); + using TargetTestHelpers.ReadContext context = TargetTestHelpers.CreateContext(descriptor, json); bool success = TargetTestHelpers.TryCreateTarget(&context, out Target? target); Assert.True(success); @@ -147,7 +139,7 @@ public void ReadIndirectGlobalValue(bool isLittleEndian, bool is64Bit) for (int i = 0; i < TestGlobals.Length; i++) { var (_, value, _) = TestGlobals[i]; - WritePointer(pointerData.Slice(i * pointerSize), value, isLittleEndian, pointerSize); + TargetTestHelpers.WritePointer(pointerData.Slice(i * pointerSize), value, isLittleEndian, pointerSize); } string globalsJson = string.Join(',', TestGlobals.Select((g, i) => $"\"{g.Name}\": {(g.Type is null ? $"[{i}]" : $"[[{i}], \"{g.Type}\"]")}")); @@ -164,7 +156,7 @@ public void ReadIndirectGlobalValue(bool isLittleEndian, bool is64Bit) TargetTestHelpers.ContractDescriptor.Fill(descriptor, isLittleEndian, is64Bit, json.Length, pointerData.Length / pointerSize); fixed (byte* jsonPtr = json) { - TargetTestHelpers.ReadContext context = TargetTestHelpers.CreateContext(descriptor, json, pointerData); + using TargetTestHelpers.ReadContext context = TargetTestHelpers.CreateContext(descriptor, json, pointerData); bool success = TargetTestHelpers.TryCreateTarget(&context, out Target? target); Assert.True(success); @@ -178,32 +170,6 @@ public void ReadIndirectGlobalValue(bool isLittleEndian, bool is64Bit) } } - private static void WritePointer(Span dest, ulong value, bool isLittleEndian, int pointerSize) - { - if (pointerSize == sizeof(ulong)) - { - if (isLittleEndian) - { - BinaryPrimitives.WriteUInt64LittleEndian(dest, value); - } - else - { - BinaryPrimitives.WriteUInt64BigEndian(dest, value); - } - } - else if (pointerSize == sizeof(uint)) - { - if (isLittleEndian) - { - BinaryPrimitives.WriteUInt32LittleEndian(dest, (uint)value); - } - else - { - BinaryPrimitives.WriteUInt32BigEndian(dest, (uint)value); - } - } - } - private static void ValidateGlobals( Target target, (string Name, ulong Value, string? Type)[] globals, From 77cf405a07256f496daa983f2cf724a2f60ae723 Mon Sep 17 00:00:00 2001 From: Aleksey Kliger Date: Tue, 2 Jul 2024 12:32:48 -0400 Subject: [PATCH 49/62] clean up the test helpers a bit --- .../cdacreader/tests/MethodTableTests.cs | 132 ++++++--- .../cdacreader/tests/MockMemorySpace.cs | 261 ++++++++++++++++++ .../managed/cdacreader/tests/MockTarget.cs | 47 ++++ .../cdacreader/tests/TargetTestHelpers.cs | 252 +++-------------- .../managed/cdacreader/tests/TargetTests.cs | 54 ++-- 5 files changed, 453 insertions(+), 293 deletions(-) create mode 100644 src/native/managed/cdacreader/tests/MockMemorySpace.cs create mode 100644 src/native/managed/cdacreader/tests/MockTarget.cs diff --git a/src/native/managed/cdacreader/tests/MethodTableTests.cs b/src/native/managed/cdacreader/tests/MethodTableTests.cs index a29fb3ff5b68ca..51f4584a985dd2 100644 --- a/src/native/managed/cdacreader/tests/MethodTableTests.cs +++ b/src/native/managed/cdacreader/tests/MethodTableTests.cs @@ -15,38 +15,37 @@ namespace Microsoft.Diagnostics.DataContractReader.UnitTests; public unsafe class MethodTableTests { - struct TargetConfig - { - public bool IsLittleEndian { get; init; } - public bool Is64Bit { get; init; } - } const ulong TestFreeObjectMethodTableGlobalAddress = 0x00000000_7a0000a0; const ulong TestFreeObjectMethodTableAddress = 0x00000000_7a0000a8; private static readonly Target.TypeInfo MethodTableTypeInfo = new() { Fields = { - { nameof(Data.MethodTable.MTFlags), new() { Offset = 4, Type = DataType.uint32}}, - { nameof(Data.MethodTable.BaseSize), new() { Offset = 8, Type = DataType.uint32}}, - { nameof(Data.MethodTable.MTFlags2), new() { Offset = 12, Type = DataType.uint32}}, - { nameof(Data.MethodTable.EEClassOrCanonMT), new () { Offset = 16, Type = DataType.nuint}}, - { nameof(Data.MethodTable.Module), new () { Offset = 24, Type = DataType.pointer}}, - { nameof(Data.MethodTable.ParentMethodTable), new () { Offset = 40, Type = DataType.pointer}}, - { nameof(Data.MethodTable.NumInterfaces), new () { Offset = 48, Type = DataType.uint16}}, - { nameof(Data.MethodTable.NumVirtuals), new () { Offset = 50, Type = DataType.uint16}}, - } + { nameof(Data.MethodTable.MTFlags), new() { Offset = 4, Type = DataType.uint32}}, + { nameof(Data.MethodTable.BaseSize), new() { Offset = 8, Type = DataType.uint32}}, + { nameof(Data.MethodTable.MTFlags2), new() { Offset = 12, Type = DataType.uint32}}, + { nameof(Data.MethodTable.EEClassOrCanonMT), new () { Offset = 16, Type = DataType.nuint}}, + { nameof(Data.MethodTable.Module), new () { Offset = 24, Type = DataType.pointer}}, + { nameof(Data.MethodTable.ParentMethodTable), new () { Offset = 40, Type = DataType.pointer}}, + { nameof(Data.MethodTable.NumInterfaces), new () { Offset = 48, Type = DataType.uint16}}, + { nameof(Data.MethodTable.NumVirtuals), new () { Offset = 50, Type = DataType.uint16}}, + } + }; + + private static readonly Target.TypeInfo EEClassTypeInfo = new Target.TypeInfo() + { + Fields = { + { nameof (Data.EEClass.MethodTable), new () { Offset = 8, Type = DataType.pointer}}, + { nameof (Data.EEClass.AttrClass), new () { Offset = 16, Type = DataType.uint32}}, + { nameof (Data.EEClass.NumMethods), new () { Offset = 20, Type = DataType.uint16}}, + } }; private static readonly (DataType Type, Target.TypeInfo Info)[] MetadataTypes = - [ + [ (DataType.MethodTable, MethodTableTypeInfo), - (DataType.EEClass, new(){ - Fields = { - { nameof (Data.EEClass.MethodTable), new () { Offset = 8, Type = DataType.pointer}}, - { nameof (Data.EEClass.AttrClass), new () { Offset = 16, Type = DataType.uint32}}, - { nameof (Data.EEClass.NumMethods), new () { Offset = 20, Type = DataType.uint16}}, - }}), - ]; + (DataType.EEClass, EEClassTypeInfo), + ]; private static readonly (string Name, ulong Value, string? Type)[] MetadataGlobals = @@ -54,18 +53,32 @@ private static readonly (string Name, ulong Value, string? Type)[] MetadataGloba (nameof(Constants.Globals.FreeObjectMethodTable), TestFreeObjectMethodTableGlobalAddress, null), ]; - private static TargetTestHelpers.HeapFragment[] FreeObjectMethodTableHeapFragments(TargetConfig targetConfig) + private static MockMemorySpace.Builder AddFreeObjectMethodTable(TargetTestHelpers targetTestHelpers, MockMemorySpace.Builder builder) { - var globalAddr = new TargetTestHelpers.HeapFragment { Name = "Address of Free Object Method Table", Address = TestFreeObjectMethodTableGlobalAddress, Data = new byte[targetConfig.Is64Bit ? 8 : 4] }; - TargetTestHelpers.WritePointer(globalAddr.Data, TestFreeObjectMethodTableAddress, targetConfig.IsLittleEndian, targetConfig.Is64Bit ? 8 : 4); - return [ + MockMemorySpace.HeapFragment globalAddr = new() { Name = "Address of Free Object Method Table", Address = TestFreeObjectMethodTableGlobalAddress, Data = new byte[targetTestHelpers.PointerSize] }; + targetTestHelpers.WritePointer(globalAddr.Data, TestFreeObjectMethodTableAddress); + return builder.AddHeapFragments([ globalAddr, - new TargetTestHelpers.HeapFragment { Name = "Free Object Method Table", Address = TestFreeObjectMethodTableAddress, Data = new byte[TargetTestHelpers.SizeOfTypeInfo(targetConfig.Is64Bit, MethodTableTypeInfo)] } - ]; + new () { Name = "Free Object Method Table", Address = TestFreeObjectMethodTableAddress, Data = new byte[targetTestHelpers.SizeOfTypeInfo(MethodTableTypeInfo)] } + ]); + } + +#if false + private static TargetTestHelpers.ContextBuilder AddEEClass(MockTarget.Architecture arch, TargetTestHelpers.ContextBuilder builder, TargetPointer eeClassPtr, TargetPointer canonMTPtr, uint attr, ushort numMethods) + { + TargetTestHelpers.HeapFragment eeClassFragment = new() { Name = "EEClass", Address = eeClassPtr, Data = new byte[TargetTestHelpers.SizeOfTypeInfo(arch.Is64Bit, EEClassTypeInfo)] }; + Span dest = eeClassFragment.Data; + TargetTestHelpers.WritePointer(dest.Slice(EEClassTypeInfo.Fields[nameof(Data.EEClass.MethodTable)].Offset), canonMTPtr, arch.IsLittleEndian, arch.Is64Bit ? 8 : 4); + return builder.AddHeapFragment(eeClassFragment); } +#endif - private static void MetadataContractHelper(TargetConfig targetConfig, Action testCase) + // a delegate for adding more heap fragments to the context builder + private delegate MockMemorySpace.Builder ConfigureContextBuilder(MockTarget.Architecture arch, MockMemorySpace.Builder builder); + + private static void MetadataContractHelper(MockTarget.Architecture arch, ConfigureContextBuilder configure, Action testCase) { + TargetTestHelpers targetTestHelpers = new(arch); string metadataTypesJson = TargetTestHelpers.MakeTypesJson(MetadataTypes); string metadataGlobalsJson = TargetTestHelpers.MakeGlobalsJson(MetadataGlobals); byte[] json = Encoding.UTF8.GetBytes($$""" @@ -79,45 +92,47 @@ private static void MetadataContractHelper(TargetConfig targetConfig, Action descriptor = stackalloc byte[TargetTestHelpers.ContractDescriptor.Size(targetConfig.Is64Bit)]; - TargetTestHelpers.ContractDescriptor.Fill(descriptor, targetConfig.IsLittleEndian, targetConfig.Is64Bit, json.Length, MetadataGlobals.Length); + Span descriptor = stackalloc byte[targetTestHelpers.ContractDescriptorSize]; + targetTestHelpers.ContractDescriptorFill(descriptor, json.Length, MetadataGlobals.Length); - int pointerSize = targetConfig.Is64Bit ? sizeof(ulong) : sizeof(uint); + int pointerSize = targetTestHelpers.PointerSize; Span pointerData = stackalloc byte[MetadataGlobals.Length * pointerSize]; for (int i = 0; i < MetadataGlobals.Length; i++) { var (_, value, _) = MetadataGlobals[i]; - TargetTestHelpers.WritePointer(pointerData.Slice(i * pointerSize), value, targetConfig.IsLittleEndian, pointerSize); + targetTestHelpers.WritePointer(pointerData.Slice(i * pointerSize), value); } fixed (byte* jsonPtr = json) { - TargetTestHelpers.ContextBuilder builder = new(); + MockMemorySpace.Builder builder = new(); builder = builder.SetDescriptor(descriptor) .SetJson(json) - .SetPointerData(pointerData) - .AddHeapFragments(FreeObjectMethodTableHeapFragments(targetConfig)); + .SetPointerData(pointerData); + + builder = AddFreeObjectMethodTable(targetTestHelpers, builder); + + if (configure != null) + { + builder = configure(arch, builder); + } - using TargetTestHelpers.ReadContext context = builder.Create(); + using MockMemorySpace.ReadContext context = builder.Create(); - bool success = TargetTestHelpers.TryCreateTarget(&context, out Target? target); + bool success = MockMemorySpace.TryCreateTarget(&context, out Target? target); Assert.True(success); - testCase(targetConfig, target); + testCase(arch, target); } GC.KeepAlive(json); } [Theory] - [InlineData(true, true)] - [InlineData(true, false)] - [InlineData(false, true)] - [InlineData(false, false)] - public void HasMetadataContract(bool isLittleEndian, bool is64Bit) + [ClassData(typeof(MockTarget.StdArch))] + public void HasMetadataContract(MockTarget.Architecture arch) { - TargetConfig targetConfig = new TargetConfig { IsLittleEndian = isLittleEndian, Is64Bit = is64Bit }; - MetadataContractHelper(targetConfig, static (arch, target) => + MetadataContractHelper(arch, default, static (arch, target) => { Contracts.IMetadata metadataContract = target.Contracts.Metadata; Assert.NotNull(metadataContract); @@ -126,4 +141,29 @@ public void HasMetadataContract(bool isLittleEndian, bool is64Bit) Assert.True(metadataContract.IsFreeObjectMethodTable(handle)); }); } + + [Theory] + [ClassData(typeof(MockTarget.StdArch))] + public void ValidateSystemObjectMethodTable(MockTarget.Architecture arch) + { + const ulong SystemObjectMethodTableAddress = 0x00000000_7c000010; + const ulong SystemObjectEEClassAddress = 0x00000000_7c0000d0; + TargetPointer systemObjectMethodTablePtr = new TargetPointer(SystemObjectMethodTableAddress); + TargetPointer systemObjectEEClassPtr = new TargetPointer(SystemObjectEEClassAddress); + MetadataContractHelper(arch, + (arch, builder) => + { +#if false + builder = AddEEClass(arch, builder, systemObjectEEClassPtr, systemObjectMethodTablePtr, default, default); + builder = AddMethodTable(builder, systemObjectMethodTablePtr, default); +#endif + return builder; + }, + static (arch, target) => + { + Contracts.IMetadata metadataContract = target.Contracts.Metadata; + Assert.NotNull(metadataContract); + + }); + } } diff --git a/src/native/managed/cdacreader/tests/MockMemorySpace.cs b/src/native/managed/cdacreader/tests/MockMemorySpace.cs new file mode 100644 index 00000000000000..af67840699b0d6 --- /dev/null +++ b/src/native/managed/cdacreader/tests/MockMemorySpace.cs @@ -0,0 +1,261 @@ +// 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.Buffers.Binary; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Text; +using Xunit; + +namespace Microsoft.Diagnostics.DataContractReader.UnitTests; + +/// +/// Helper for creating a mock memory space for testing. +/// +/// +/// Use MockMemorySpace.CreateContext to create a mostly empty context for reading from the target. +/// Use MockMemorySpace.ContextBuilder to create a context with additional MockMemorySpace.HeapFragment data. +/// +/// +/// All the spans should be stackalloc or pinned while the context is being used. +/// +internal unsafe static class MockMemorySpace +{ + internal const ulong ContractDescriptorAddr = 0xaaaaaaaa; + internal const uint JsonDescriptorAddr = 0xdddddddd; + internal const uint ContractPointerDataAddr = 0xeeeeeeee; + + + internal struct HeapFragment + { + public ulong Address; + public byte[] Data; + public string? Name; + } + + /// + /// Helper to build a context (virtual memory space) for reading from a target. + /// + /// + /// All the spans should be stackalloc or pinned while the context is being used. + /// + internal unsafe ref struct Builder + { + private bool _created = false; + private byte* _descriptor = null; + private int _descriptorLength = 0; + private byte* _json = null; + private int _jsonLength = 0; + private byte* _pointerData = null; + private int _pointerDataLength = 0; + private List _heapFragments = new(); + + public Builder() + { + + } + + public Builder SetDescriptor(scoped ReadOnlySpan descriptor) + { + if (_created) + throw new InvalidOperationException("Context already created"); + _descriptor = (byte*)Unsafe.AsPointer(ref MemoryMarshal.GetReference(descriptor)); + _descriptorLength = descriptor.Length; + return this; + } + + public Builder SetJson(scoped ReadOnlySpan json) + { + if (_created) + throw new InvalidOperationException("Context already created"); + _json = (byte*)Unsafe.AsPointer(ref MemoryMarshal.GetReference(json)); + _jsonLength = json.Length; + return this; + } + + public Builder SetPointerData(scoped ReadOnlySpan pointerData) + { + if (_created) + throw new InvalidOperationException("Context already created"); + if (pointerData.Length >= 0) + { + _pointerData = (byte*)Unsafe.AsPointer(ref MemoryMarshal.GetReference(pointerData)); + _pointerDataLength = pointerData.Length; + } + return this; + } + + public Builder AddHeapFragment(HeapFragment fragment) + { + if (_created) + throw new InvalidOperationException("Context already created"); + if (fragment.Data is null || fragment.Data.Length == 0) + throw new InvalidOperationException($"Fragment '{fragment.Name}' data is empty"); + if (!FragmentFits(fragment)) + throw new InvalidOperationException($"Fragment '{fragment.Name}' does not fit in the address space"); + _heapFragments.Add(fragment); + return this; + } + + public Builder AddHeapFragments(IEnumerable fragments) + { + foreach (var f in fragments) + { + // add fragments one at a time to check for overlaps + AddHeapFragment(f); + } + return this; + } + + public ReadContext Create() + { + if (_created) + throw new InvalidOperationException("Context already created"); + GCHandle fragmentReaderHandle = default; ; + if (_heapFragments.Count > 0) + { + fragmentReaderHandle = GCHandle.Alloc(new HeapFragmentReader(_heapFragments)); + } + ReadContext context = new ReadContext + { + ContractDescriptor = _descriptor, + ContractDescriptorLength = _descriptorLength, + JsonDescriptor = _json, + JsonDescriptorLength = _jsonLength, + PointerData = _pointerData, + PointerDataLength = _pointerDataLength, + HeapFragmentReader = GCHandle.ToIntPtr(fragmentReaderHandle) + }; + _created = true; + return context; + } + + private bool FragmentFits(HeapFragment f) + { + foreach (var fragment in _heapFragments) + { + // f and fragment overlap if either: + // 1. f starts before fragment starts and ends after fragment starts + // 2. f starts before fragment ends + if ((f.Address <= fragment.Address && f.Address + (ulong)f.Data.Length > fragment.Address) || + (f.Address >= fragment.Address && f.Address < fragment.Address + (ulong)fragment.Data.Length)) + { + return false; + } + + } + return true; + } + } + + // Note: all the spans should be stackalloc or pinned. + public static ReadContext CreateContext(ReadOnlySpan descriptor, ReadOnlySpan json, ReadOnlySpan pointerData = default) + { + Builder builder = new Builder() + .SetJson(json) + .SetDescriptor(descriptor) + .SetPointerData(pointerData); + return builder.Create(); + } + + public static bool TryCreateTarget(ReadContext* context, out Target? target) + { + return Target.TryCreate(ContractDescriptorAddr, &ReadFromTarget, context, out target); + } + + [UnmanagedCallersOnly] + private static int ReadFromTarget(ulong address, byte* buffer, uint length, void* context) + { + ReadContext* readContext = (ReadContext*)context; + var span = new Span(buffer, (int)length); + + // Populate the span with the requested portion of the contract descriptor + if (address >= ContractDescriptorAddr && address <= ContractDescriptorAddr + (ulong)readContext->ContractDescriptorLength - length) + { + ulong offset = address - ContractDescriptorAddr; + new ReadOnlySpan(readContext->ContractDescriptor + offset, (int)length).CopyTo(span); + return 0; + } + + // Populate the span with the JSON descriptor - this assumes the product will read it all at once. + if (address == JsonDescriptorAddr) + { + new ReadOnlySpan(readContext->JsonDescriptor, readContext->JsonDescriptorLength).CopyTo(span); + return 0; + } + + // Populate the span with the requested portion of the pointer data + if (address >= ContractPointerDataAddr && address <= ContractPointerDataAddr + (ulong)readContext->PointerDataLength - length) + { + ulong offset = address - ContractPointerDataAddr; + new ReadOnlySpan(readContext->PointerData + offset, (int)length).CopyTo(span); + return 0; + } + + HeapFragmentReader? heapFragmentReader = GCHandle.FromIntPtr(readContext->HeapFragmentReader).Target as HeapFragmentReader; + if (heapFragmentReader is not null) + { + return heapFragmentReader.ReadFragment(address, span); + } + + return -1; + } + + // Used by ReadFromTarget to return the appropriate bytes + internal ref struct ReadContext : IDisposable + { + public byte* ContractDescriptor; + public int ContractDescriptorLength; + + public byte* JsonDescriptor; + public int JsonDescriptorLength; + + public byte* PointerData; + public int PointerDataLength; + + public IntPtr HeapFragmentReader; + + public void Dispose() + { + if (HeapFragmentReader != IntPtr.Zero) + { + GCHandle.FromIntPtr(HeapFragmentReader).Free(); + HeapFragmentReader = IntPtr.Zero; + } + } + } + + private class HeapFragmentReader + { + private readonly IReadOnlyList _fragments; + public HeapFragmentReader(IReadOnlyList fragments) + { + _fragments = fragments; + } + + public int ReadFragment(ulong address, Span buffer) + { + foreach (var fragment in _fragments) + { + if (address >= fragment.Address && address < fragment.Address + (ulong)fragment.Data.Length) + { + int offset = (int)(address - fragment.Address); + int availableLength = fragment.Data.Length - offset; + if (availableLength >= buffer.Length) + { + fragment.Data.AsSpan(offset, buffer.Length).CopyTo(buffer); + return 0; + } + else + { + throw new InvalidOperationException($"Not enough data in fragment at {fragment.Address:X} ('{fragment.Name}') to read {buffer.Length} bytes at {address:X} (only {availableLength} bytes available)"); + } + } + } + return -1; + } + } +} diff --git a/src/native/managed/cdacreader/tests/MockTarget.cs b/src/native/managed/cdacreader/tests/MockTarget.cs new file mode 100644 index 00000000000000..ced73c5c262723 --- /dev/null +++ b/src/native/managed/cdacreader/tests/MockTarget.cs @@ -0,0 +1,47 @@ +// 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.Buffers.Binary; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Text; +using Xunit; + +namespace Microsoft.Diagnostics.DataContractReader.UnitTests; + +public class MockTarget +{ + public struct Architecture + { + public bool IsLittleEndian { get; init; } + public bool Is64Bit { get; init; } + } + + /// + /// Xunit enumeration of standard test architectures + /// + /// + /// [Theory] + /// [ClassData(typeof(MockTarget.StdArch))] + /// public void TestMethod(MockTarget.Architecture arch) + /// { + /// ... + /// } + /// + public class StdArch : IEnumerable + { + public IEnumerator GetEnumerator() + { + yield return [new Architecture { IsLittleEndian = true, Is64Bit = true }]; + yield return [new Architecture { IsLittleEndian = true, Is64Bit = false }]; + yield return [new Architecture { IsLittleEndian = false, Is64Bit = true }]; + yield return [new Architecture { IsLittleEndian = false, Is64Bit = false }]; + } + + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() => GetEnumerator(); + } + +} diff --git a/src/native/managed/cdacreader/tests/TargetTestHelpers.cs b/src/native/managed/cdacreader/tests/TargetTestHelpers.cs index dd9bf78d56b57c..2c81d1c51e3ef3 100644 --- a/src/native/managed/cdacreader/tests/TargetTestHelpers.cs +++ b/src/native/managed/cdacreader/tests/TargetTestHelpers.cs @@ -13,137 +13,37 @@ namespace Microsoft.Diagnostics.DataContractReader.UnitTests; internal unsafe class TargetTestHelpers { - private const ulong ContractDescriptorAddr = 0xaaaaaaaa; - private const uint JsonDescriptorAddr = 0xdddddddd; - private const uint PointerDataAddr = 0xeeeeeeee; + public MockTarget.Architecture Arch { get; init; } - internal struct HeapFragment + public TargetTestHelpers(MockTarget.Architecture arch) { - public ulong Address; - public byte[] Data; - public string? Name; + Arch = arch; } - /// - /// Helper to build a context for reading from a target. - /// - /// - /// All the spans should be stackalloc or pinned while the context is being used. - /// - internal unsafe ref struct ContextBuilder - { - private bool _created = false; - private byte* _descriptor = null; - private int _descriptorLength = 0; - private byte* _json = null; - private int _jsonLength = 0; - private byte* _pointerData = null; - private int _pointerDataLength = 0; - private List _heapFragments = new(); - - public ContextBuilder() - { - - } - - public ContextBuilder SetDescriptor(scoped ReadOnlySpan descriptor) - { - if (_created) - throw new InvalidOperationException("Context already created"); - _descriptor = (byte*)Unsafe.AsPointer(ref MemoryMarshal.GetReference(descriptor)); - _descriptorLength = descriptor.Length; - return this; - } - - public ContextBuilder SetJson(scoped ReadOnlySpan json) - { - if (_created) - throw new InvalidOperationException("Context already created"); - _json = (byte*)Unsafe.AsPointer(ref MemoryMarshal.GetReference(json)); - _jsonLength = json.Length; - return this; - } - - public ContextBuilder SetPointerData(scoped ReadOnlySpan pointerData) - { - if (_created) - throw new InvalidOperationException("Context already created"); - if (pointerData.Length >= 0) - { - _pointerData = (byte*)Unsafe.AsPointer(ref MemoryMarshal.GetReference(pointerData)); - _pointerDataLength = pointerData.Length; - } - return this; - } - - public ContextBuilder AddHeapFragment(HeapFragment fragment) - { - if (_created) - throw new InvalidOperationException("Context already created"); - _heapFragments.Add(fragment); - return this; - } + public int PointerSize => Arch.Is64Bit ? sizeof(ulong) : sizeof(uint); + public int ContractDescriptorSize => ContractDescriptor.Size(Arch.Is64Bit); - public ContextBuilder AddHeapFragments(IEnumerable fragments) - { - if (_created) - throw new InvalidOperationException("Context already created"); - _heapFragments.AddRange(fragments); - return this; - } - - public ReadContext Create() - { - if (_created) - throw new InvalidOperationException("Context already created"); - GCHandle fragmentReaderHandle = default; ; - if (_heapFragments.Count > 0) - { - fragmentReaderHandle = GCHandle.Alloc(new HeapFragmentReader(_heapFragments)); - } - ReadContext context = new ReadContext - { - ContractDescriptor = _descriptor, - ContractDescriptorLength = _descriptorLength, - JsonDescriptor = _json, - JsonDescriptorLength = _jsonLength, - PointerData = _pointerData, - PointerDataLength = _pointerDataLength, - HeapFragmentReader = GCHandle.ToIntPtr(fragmentReaderHandle) - }; - _created = true; - return context; - } - } - // Note: all the spans should be stackalloc or pinned. - public static ReadContext CreateContext(ReadOnlySpan descriptor, ReadOnlySpan json, ReadOnlySpan pointerData = default) - { - ContextBuilder builder = new ContextBuilder() - .SetJson(json) - .SetDescriptor(descriptor) - .SetPointerData(pointerData); - return builder.Create(); - } + #region Contract and data descriptor creation - public static bool TryCreateTarget(ReadContext* context, out Target? target) + public void ContractDescriptorFill(Span dest, int jsonDescriptorSize, int pointerDataCount) { - return Target.TryCreate(ContractDescriptorAddr, &ReadFromTarget, context, out target); + ContractDescriptor.Fill(dest, Arch, jsonDescriptorSize, pointerDataCount); } internal static class ContractDescriptor { public static int Size(bool is64Bit) => is64Bit ? sizeof(ContractDescriptor64) : sizeof(ContractDescriptor32); - public static void Fill(Span dest, bool isLittleEndian, bool is64Bit, int jsonDescriptorSize, int pointerDataCount) + public static void Fill(Span dest, MockTarget.Architecture arch, int jsonDescriptorSize, int pointerDataCount) { - if (is64Bit) + if (arch.Is64Bit) { - ContractDescriptor64.Fill(dest, isLittleEndian, jsonDescriptorSize, pointerDataCount); + ContractDescriptor64.Fill(dest, arch.IsLittleEndian, jsonDescriptorSize, pointerDataCount); } else { - ContractDescriptor32.Fill(dest, isLittleEndian, jsonDescriptorSize, pointerDataCount); + ContractDescriptor32.Fill(dest, arch.IsLittleEndian, jsonDescriptorSize, pointerDataCount); } } @@ -152,10 +52,10 @@ private struct ContractDescriptor32 public ulong Magic = BitConverter.ToUInt64("DNCCDAC\0"u8); public uint Flags = 0x2 /*32-bit*/ | 0x1; public uint DescriptorSize; - public uint Descriptor = JsonDescriptorAddr; + public uint Descriptor = MockMemorySpace.JsonDescriptorAddr; public uint PointerDataCount; public uint Pad0 = 0; - public uint PointerData = PointerDataAddr; + public uint PointerData = MockMemorySpace.ContractPointerDataAddr; public ContractDescriptor32() { } @@ -189,10 +89,10 @@ private struct ContractDescriptor64 public ulong Magic = BitConverter.ToUInt64("DNCCDAC\0"u8); public uint Flags = 0x1; public uint DescriptorSize; - public ulong Descriptor = JsonDescriptorAddr; + public ulong Descriptor = MockMemorySpace.JsonDescriptorAddr; public uint PointerDataCount; public uint Pad0 = 0; - public ulong PointerData = PointerDataAddr; + public ulong PointerData = MockMemorySpace.ContractPointerDataAddr; public ContractDescriptor64() { } @@ -222,99 +122,9 @@ private void ReverseEndianness() } } - [UnmanagedCallersOnly] - private static int ReadFromTarget(ulong address, byte* buffer, uint length, void* context) - { - ReadContext* readContext = (ReadContext*)context; - var span = new Span(buffer, (int)length); - - // Populate the span with the requested portion of the contract descriptor - if (address >= ContractDescriptorAddr && address <= ContractDescriptorAddr + (ulong)readContext->ContractDescriptorLength - length) - { - ulong offset = address - ContractDescriptorAddr; - new ReadOnlySpan(readContext->ContractDescriptor + offset, (int)length).CopyTo(span); - return 0; - } - - // Populate the span with the JSON descriptor - this assumes the product will read it all at once. - if (address == JsonDescriptorAddr) - { - new ReadOnlySpan(readContext->JsonDescriptor, readContext->JsonDescriptorLength).CopyTo(span); - return 0; - } - - // Populate the span with the requested portion of the pointer data - if (address >= PointerDataAddr && address <= PointerDataAddr + (ulong)readContext->PointerDataLength - length) - { - ulong offset = address - PointerDataAddr; - new ReadOnlySpan(readContext->PointerData + offset, (int)length).CopyTo(span); - return 0; - } - - HeapFragmentReader? heapFragmentReader = GCHandle.FromIntPtr(readContext->HeapFragmentReader).Target as HeapFragmentReader; - if (heapFragmentReader is not null) - { - return heapFragmentReader.ReadFragment(address, span); - } - - return -1; - } - - // Used by ReadFromTarget to return the appropriate bytes - internal ref struct ReadContext : IDisposable - { - public byte* ContractDescriptor; - public int ContractDescriptorLength; - - public byte* JsonDescriptor; - public int JsonDescriptorLength; - - public byte* PointerData; - public int PointerDataLength; - - public IntPtr HeapFragmentReader; - - public void Dispose() - { - if (HeapFragmentReader != IntPtr.Zero) - { - GCHandle.FromIntPtr(HeapFragmentReader).Free(); - HeapFragmentReader = IntPtr.Zero; - } - } - } - - private class HeapFragmentReader - { - private readonly IReadOnlyList _fragments; - public HeapFragmentReader(IReadOnlyList fragments) - { - _fragments = fragments; - } - - public int ReadFragment(ulong address, Span buffer) - { - foreach (var fragment in _fragments) - { - if (address >= fragment.Address && address < fragment.Address + (ulong)fragment.Data.Length) - { - int offset = (int)(address - fragment.Address); - int availableLength = fragment.Data.Length - offset; - if (availableLength >= buffer.Length) - { - fragment.Data.AsSpan(offset, buffer.Length).CopyTo(buffer); - return 0; - } - else - { - throw new InvalidOperationException($"Not enough data in fragment at {fragment.Address:X} ('{fragment.Name}') to read {buffer.Length} bytes at {address:X} (only {availableLength} bytes available)"); - } - } - } - return -1; - } - } + #endregion Contract and data descriptor creation + #region Data descriptor json formatting private static string GetTypeJson(string name, Target.TypeInfo info) { string ret = string.Empty; @@ -333,11 +143,18 @@ public static string MakeGlobalsJson(IEnumerable<(string Name, ulong Value, stri return string.Join(',', globals.Select(i => $"\"{i.Name}\": {(i.Type is null ? i.Value.ToString() : $"[{i.Value}, \"{i.Type}\"]")}")); } - internal static void WritePointer(Span dest, ulong value, bool isLittleEndian, int pointerSize) + #endregion Data descriptor json formatting + + + + + #region Mock memory initialization + + internal void WritePointer(Span dest, ulong value) { - if (pointerSize == sizeof(ulong)) + if (Arch.Is64Bit) { - if (isLittleEndian) + if (Arch.IsLittleEndian) { BinaryPrimitives.WriteUInt64LittleEndian(dest, value); } @@ -346,9 +163,9 @@ internal static void WritePointer(Span dest, ulong value, bool isLittleEnd BinaryPrimitives.WriteUInt64BigEndian(dest, value); } } - else if (pointerSize == sizeof(uint)) + else { - if (isLittleEndian) + if (Arch.IsLittleEndian) { BinaryPrimitives.WriteUInt32LittleEndian(dest, (uint)value); } @@ -359,7 +176,7 @@ internal static void WritePointer(Span dest, ulong value, bool isLittleEnd } } - internal static int SizeOfPrimitive(DataType type, bool is64Bit) + internal int SizeOfPrimitive(DataType type) { return type switch { @@ -367,21 +184,22 @@ internal static int SizeOfPrimitive(DataType type, bool is64Bit) DataType.uint16 or DataType.int16 => sizeof(ushort), DataType.uint32 or DataType.int32 => sizeof(uint), DataType.uint64 or DataType.int64 => sizeof(ulong), - DataType.pointer or DataType.nint or DataType.nuint => is64Bit ? sizeof(ulong) : sizeof(uint), + DataType.pointer or DataType.nint or DataType.nuint => PointerSize, _ => throw new InvalidOperationException($"Not a primitive: {type}"), }; } - internal static int SizeOfTypeInfo(bool is64Bit, Target.TypeInfo info) + internal int SizeOfTypeInfo(Target.TypeInfo info) { int size = 0; foreach (var (_, field) in info.Fields) { - size = Math.Max(size, field.Offset + SizeOfPrimitive(field.Type, is64Bit)); + size = Math.Max(size, field.Offset + SizeOfPrimitive(field.Type)); } return size; } + #endregion Mock memory initialization } diff --git a/src/native/managed/cdacreader/tests/TargetTests.cs b/src/native/managed/cdacreader/tests/TargetTests.cs index 15f42929b94627..f4afc076ac2ade 100644 --- a/src/native/managed/cdacreader/tests/TargetTests.cs +++ b/src/native/managed/cdacreader/tests/TargetTests.cs @@ -38,12 +38,10 @@ private static readonly (DataType Type, Target.TypeInfo Info)[] TestTypes = ]; [Theory] - [InlineData(true, true)] - [InlineData(true, false)] - [InlineData(false, true)] - [InlineData(false, false)] - public void GetTypeInfo(bool isLittleEndian, bool is64Bit) + [ClassData(typeof(MockTarget.StdArch))] + public void GetTypeInfo(MockTarget.Architecture arch) { + TargetTestHelpers targetTestHelpers = new(arch); string typesJson = TargetTestHelpers.MakeTypesJson(TestTypes); byte[] json = Encoding.UTF8.GetBytes($$""" { @@ -54,13 +52,13 @@ public void GetTypeInfo(bool isLittleEndian, bool is64Bit) "globals": {} } """); - Span descriptor = stackalloc byte[TargetTestHelpers.ContractDescriptor.Size(is64Bit)]; - TargetTestHelpers.ContractDescriptor.Fill(descriptor, isLittleEndian, is64Bit, json.Length, 0); + Span descriptor = stackalloc byte[targetTestHelpers.ContractDescriptorSize]; + targetTestHelpers.ContractDescriptorFill(descriptor, json.Length, 0); fixed (byte* jsonPtr = json) { - using TargetTestHelpers.ReadContext context = TargetTestHelpers.CreateContext(descriptor, json); + using MockMemorySpace.ReadContext context = MockMemorySpace.CreateContext(descriptor, json); - bool success = TargetTestHelpers.TryCreateTarget(&context, out Target? target); + bool success = MockMemorySpace.TryCreateTarget(&context, out Target? target); Assert.True(success); foreach ((DataType type, Target.TypeInfo info) in TestTypes) @@ -98,12 +96,10 @@ private static readonly (string Name, ulong Value, string? Type)[] TestGlobals = ]; [Theory] - [InlineData(true, true)] - [InlineData(true, false)] - [InlineData(false, true)] - [InlineData(false, false)] - public void ReadGlobalValue(bool isLittleEndian, bool is64Bit) + [ClassData(typeof(MockTarget.StdArch))] + public void ReadGlobalValue(MockTarget.Architecture arch) { + TargetTestHelpers targetTestHelpers = new(arch); string globalsJson = TargetTestHelpers.MakeGlobalsJson(TestGlobals); byte[] json = Encoding.UTF8.GetBytes($$""" { @@ -114,13 +110,13 @@ public void ReadGlobalValue(bool isLittleEndian, bool is64Bit) "globals": { {{globalsJson}} } } """); - Span descriptor = stackalloc byte[TargetTestHelpers.ContractDescriptor.Size(is64Bit)]; - TargetTestHelpers.ContractDescriptor.Fill(descriptor, isLittleEndian, is64Bit, json.Length, 0); + Span descriptor = stackalloc byte[targetTestHelpers.ContractDescriptorSize]; + targetTestHelpers.ContractDescriptorFill(descriptor, json.Length, 0); fixed (byte* jsonPtr = json) { - using TargetTestHelpers.ReadContext context = TargetTestHelpers.CreateContext(descriptor, json); + using MockMemorySpace.ReadContext context = MockMemorySpace.CreateContext(descriptor, json); - bool success = TargetTestHelpers.TryCreateTarget(&context, out Target? target); + bool success = MockMemorySpace.TryCreateTarget(&context, out Target? target); Assert.True(success); ValidateGlobals(target, TestGlobals); @@ -128,18 +124,16 @@ public void ReadGlobalValue(bool isLittleEndian, bool is64Bit) } [Theory] - [InlineData(true, true)] - [InlineData(true, false)] - [InlineData(false, true)] - [InlineData(false, false)] - public void ReadIndirectGlobalValue(bool isLittleEndian, bool is64Bit) + [ClassData(typeof(MockTarget.StdArch))] + public void ReadIndirectGlobalValue(MockTarget.Architecture arch) { - int pointerSize = is64Bit ? sizeof(ulong) : sizeof(uint); + TargetTestHelpers targetTestHelpers = new(arch); + int pointerSize = targetTestHelpers.PointerSize; Span pointerData = stackalloc byte[TestGlobals.Length * pointerSize]; for (int i = 0; i < TestGlobals.Length; i++) { var (_, value, _) = TestGlobals[i]; - TargetTestHelpers.WritePointer(pointerData.Slice(i * pointerSize), value, isLittleEndian, pointerSize); + targetTestHelpers.WritePointer(pointerData.Slice(i * pointerSize), value); } string globalsJson = string.Join(',', TestGlobals.Select((g, i) => $"\"{g.Name}\": {(g.Type is null ? $"[{i}]" : $"[[{i}], \"{g.Type}\"]")}")); @@ -152,17 +146,17 @@ public void ReadIndirectGlobalValue(bool isLittleEndian, bool is64Bit) "globals": { {{globalsJson}} } } """); - Span descriptor = stackalloc byte[TargetTestHelpers.ContractDescriptor.Size(is64Bit)]; - TargetTestHelpers.ContractDescriptor.Fill(descriptor, isLittleEndian, is64Bit, json.Length, pointerData.Length / pointerSize); + Span descriptor = stackalloc byte[targetTestHelpers.ContractDescriptorSize]; + targetTestHelpers.ContractDescriptorFill(descriptor, json.Length, pointerData.Length / pointerSize); fixed (byte* jsonPtr = json) { - using TargetTestHelpers.ReadContext context = TargetTestHelpers.CreateContext(descriptor, json, pointerData); + using MockMemorySpace.ReadContext context = MockMemorySpace.CreateContext(descriptor, json, pointerData); - bool success = TargetTestHelpers.TryCreateTarget(&context, out Target? target); + bool success = MockMemorySpace.TryCreateTarget(&context, out Target? target); Assert.True(success); // Indirect values are pointer-sized, so max 32-bits for a 32-bit target - var expected = is64Bit + var expected = arch.Is64Bit ? TestGlobals : TestGlobals.Select(g => (g.Name, g.Value & 0xffffffff, g.Type)).ToArray(); From f9bce4cdf841522c84d8a3c86c93e8d2326bd71c Mon Sep 17 00:00:00 2001 From: Aleksey Kliger Date: Tue, 2 Jul 2024 12:47:02 -0400 Subject: [PATCH 50/62] validate that a mock system object is a valid method table --- .../cdacreader/tests/MethodTableTests.cs | 38 +++++++++++-------- .../cdacreader/tests/MockMemorySpace.cs | 6 +-- .../managed/cdacreader/tests/MockTarget.cs | 6 --- .../cdacreader/tests/TargetTestHelpers.cs | 3 -- .../managed/cdacreader/tests/TargetTests.cs | 3 -- 5 files changed, 23 insertions(+), 33 deletions(-) diff --git a/src/native/managed/cdacreader/tests/MethodTableTests.cs b/src/native/managed/cdacreader/tests/MethodTableTests.cs index 51f4584a985dd2..69474218bea16e 100644 --- a/src/native/managed/cdacreader/tests/MethodTableTests.cs +++ b/src/native/managed/cdacreader/tests/MethodTableTests.cs @@ -2,11 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; -using System.Buffers.Binary; -using System.Collections.Generic; -using System.Linq; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; using System.Text; using Microsoft.Diagnostics.DataContractReader.Data; using Xunit; @@ -63,15 +58,24 @@ private static MockMemorySpace.Builder AddFreeObjectMethodTable(TargetTestHelper ]); } -#if false - private static TargetTestHelpers.ContextBuilder AddEEClass(MockTarget.Architecture arch, TargetTestHelpers.ContextBuilder builder, TargetPointer eeClassPtr, TargetPointer canonMTPtr, uint attr, ushort numMethods) + private static MockMemorySpace.Builder AddEEClass(TargetTestHelpers targetTestHelpers, MockMemorySpace.Builder builder, TargetPointer eeClassPtr, string name, TargetPointer canonMTPtr, uint attr, ushort numMethods) { - TargetTestHelpers.HeapFragment eeClassFragment = new() { Name = "EEClass", Address = eeClassPtr, Data = new byte[TargetTestHelpers.SizeOfTypeInfo(arch.Is64Bit, EEClassTypeInfo)] }; + MockMemorySpace.HeapFragment eeClassFragment = new() { Name = $"EEClass '{name}'", Address = eeClassPtr, Data = new byte[targetTestHelpers.SizeOfTypeInfo(EEClassTypeInfo)] }; Span dest = eeClassFragment.Data; - TargetTestHelpers.WritePointer(dest.Slice(EEClassTypeInfo.Fields[nameof(Data.EEClass.MethodTable)].Offset), canonMTPtr, arch.IsLittleEndian, arch.Is64Bit ? 8 : 4); + targetTestHelpers.WritePointer(dest.Slice(EEClassTypeInfo.Fields[nameof(Data.EEClass.MethodTable)].Offset), canonMTPtr); + // TODO: fill in the rest of the fields return builder.AddHeapFragment(eeClassFragment); + + } + + private static MockMemorySpace.Builder AddMethodTable(TargetTestHelpers targetTestHelpers, MockMemorySpace.Builder builder, TargetPointer methodTablePtr, string name, TargetPointer eeClassOrCanonMT, uint mtflags, uint mtflags2, uint baseSize) + { + MockMemorySpace.HeapFragment methodTableFragment = new() { Name = $"MethodTable '{name}'", Address = methodTablePtr, Data = new byte[targetTestHelpers.SizeOfTypeInfo(MethodTableTypeInfo)] }; + Span dest = methodTableFragment.Data; + targetTestHelpers.WritePointer(dest.Slice(MethodTableTypeInfo.Fields[nameof(Data.MethodTable.EEClassOrCanonMT)].Offset), eeClassOrCanonMT); + // TODO fill in the rest of the fields + return builder.AddHeapFragment(methodTableFragment); } -#endif // a delegate for adding more heap fragments to the context builder private delegate MockMemorySpace.Builder ConfigureContextBuilder(MockTarget.Architecture arch, MockMemorySpace.Builder builder); @@ -153,17 +157,19 @@ public void ValidateSystemObjectMethodTable(MockTarget.Architecture arch) MetadataContractHelper(arch, (arch, builder) => { -#if false - builder = AddEEClass(arch, builder, systemObjectEEClassPtr, systemObjectMethodTablePtr, default, default); - builder = AddMethodTable(builder, systemObjectMethodTablePtr, default); -#endif + TargetTestHelpers targetTestHelpers = new(arch); + builder = AddEEClass(targetTestHelpers, builder, systemObjectEEClassPtr, "System.Object", systemObjectMethodTablePtr, attr: default, numMethods: default); + builder = AddMethodTable(targetTestHelpers, builder, systemObjectMethodTablePtr, "System.Object", systemObjectEEClassPtr, mtflags: default, mtflags2: default, baseSize: default); return builder; }, - static (arch, target) => + (arch, target) => { + TargetTestHelpers targetTestHelpers = new(arch); Contracts.IMetadata metadataContract = target.Contracts.Metadata; Assert.NotNull(metadataContract); - + Contracts.MethodTableHandle systemObjectMethodTableHandle = metadataContract.GetMethodTableHandle(systemObjectMethodTablePtr); + Assert.Equal(systemObjectMethodTablePtr.Value, systemObjectMethodTableHandle.Address.Value); + Assert.False(metadataContract.IsFreeObjectMethodTable(systemObjectMethodTableHandle)); }); } } diff --git a/src/native/managed/cdacreader/tests/MockMemorySpace.cs b/src/native/managed/cdacreader/tests/MockMemorySpace.cs index af67840699b0d6..c74771a7f5b5c3 100644 --- a/src/native/managed/cdacreader/tests/MockMemorySpace.cs +++ b/src/native/managed/cdacreader/tests/MockMemorySpace.cs @@ -2,13 +2,9 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; -using System.Buffers.Binary; using System.Collections.Generic; -using System.Linq; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -using System.Text; -using Xunit; namespace Microsoft.Diagnostics.DataContractReader.UnitTests; @@ -37,7 +33,7 @@ internal struct HeapFragment } /// - /// Helper to build a context (virtual memory space) for reading from a target. + /// Helper to populate a virtual memory space for reading from a target. /// /// /// All the spans should be stackalloc or pinned while the context is being used. diff --git a/src/native/managed/cdacreader/tests/MockTarget.cs b/src/native/managed/cdacreader/tests/MockTarget.cs index ced73c5c262723..5ef528139b7d57 100644 --- a/src/native/managed/cdacreader/tests/MockTarget.cs +++ b/src/native/managed/cdacreader/tests/MockTarget.cs @@ -1,13 +1,7 @@ // 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.Buffers.Binary; using System.Collections.Generic; -using System.Linq; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using System.Text; using Xunit; namespace Microsoft.Diagnostics.DataContractReader.UnitTests; diff --git a/src/native/managed/cdacreader/tests/TargetTestHelpers.cs b/src/native/managed/cdacreader/tests/TargetTestHelpers.cs index 2c81d1c51e3ef3..6897eea952f38e 100644 --- a/src/native/managed/cdacreader/tests/TargetTestHelpers.cs +++ b/src/native/managed/cdacreader/tests/TargetTestHelpers.cs @@ -5,10 +5,7 @@ using System.Buffers.Binary; using System.Collections.Generic; using System.Linq; -using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -using System.Text; -using Xunit; namespace Microsoft.Diagnostics.DataContractReader.UnitTests; internal unsafe class TargetTestHelpers diff --git a/src/native/managed/cdacreader/tests/TargetTests.cs b/src/native/managed/cdacreader/tests/TargetTests.cs index f4afc076ac2ade..56e36ec7dc2e20 100644 --- a/src/native/managed/cdacreader/tests/TargetTests.cs +++ b/src/native/managed/cdacreader/tests/TargetTests.cs @@ -2,11 +2,8 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; -using System.Buffers.Binary; -using System.Collections.Generic; using System.Linq; using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; using System.Text; using Xunit; From 3721992295363cc8e6b7b2f7e0c015b2a748f28f Mon Sep 17 00:00:00 2001 From: Aleksey Kliger Date: Tue, 2 Jul 2024 15:08:50 -0400 Subject: [PATCH 51/62] code review feedback and more tests: 1. rename AttrClass data descriptor field to CorTypeAttr 2. fixup HasComponentSize / RawGetComponentSize comments and code 3. update "system.object" mock methodtable with more field values 4. update "system.string" mock methodtable with more field values --- docs/design/datacontracts/Metadata.md | 22 +++-- .../debug/runtimeinfo/datadescriptor.h | 2 +- src/coreclr/vm/class.h | 2 +- .../Contracts/Metadata_1.MethodTableFlags.cs | 11 ++- .../cdacreader/src/Contracts/Metadata_1.cs | 13 +-- .../managed/cdacreader/src/Data/EEClass.cs | 4 +- .../cdacreader/tests/MethodTableTests.cs | 89 ++++++++++++++++--- .../cdacreader/tests/TargetTestHelpers.cs | 49 ++++++++++ 8 files changed, 157 insertions(+), 35 deletions(-) diff --git a/docs/design/datacontracts/Metadata.md b/docs/design/datacontracts/Metadata.md index 170a58da4aec2d..1c100663481753 100644 --- a/docs/design/datacontracts/Metadata.md +++ b/docs/design/datacontracts/Metadata.md @@ -53,6 +53,8 @@ The `MethodTable` inspection APIs are implemented in terms of the following flag ``` csharp internal partial struct Metadata_1 { + // The lower 16-bits of the MTFlags field are used for these flags, + // if WFLAGS_HIGH.HasComponentSize is unset [Flags] internal enum WFLAGS_LOW : uint { @@ -62,6 +64,7 @@ internal partial struct Metadata_1 StringArrayValues = GenericsMask_NonGeneric, } + // Upper bits of MTFlags [Flags] internal enum WFLAGS_HIGH : uint { @@ -69,8 +72,9 @@ internal partial struct Metadata_1 Category_Array = 0x00080000, Category_Array_Mask = 0x000C0000, Category_Interface = 0x000C0000, - ContainsPointers = 0x01000000, // Contains object references - HasComponentSize = 0x80000000, // This is set if component size is used for flags. + ContainsGCPointers = 0x01000000, + HasComponentSize = 0x80000000, // This is set if lower 16 bits is used for the component size, + // otherwise the lower bits are used for WFLAGS_LOW } [Flags] @@ -82,10 +86,14 @@ internal partial struct Metadata_1 // Encapsulates the MethodTable flags v1 uses internal struct MethodTableFlags { - public WFLAGS_LOW GetFlag(WFLAGS_LOW mask) { ... } - public WFLAGS_HIGH GetFlag(WFLAGS_HIGH mask) { ... } + public uint MTFlags { get; } + public uint MTFlags2 { get; } + public uint BaseSize { get; } - public WFLAGS2_ENUM GetFlag(WFLAGS2_ENUM mask) { ... } + public WFLAGS_LOW GetFlag(WFLAGS_LOW mask) { ... /* mask & lower 16 bits of MTFlags */ } + public WFLAGS_HIGH GetFlag(WFLAGS_HIGH mask) { ... /* mask & upper 16 bits of MTFlags */ } + + public WFLAGS2_ENUM GetFlag(WFLAGS2_ENUM mask) { ... /* mask & MTFlags2*/ } public bool IsInterface => GetFlag(WFLAGS_HIGH.Category_Mask) == WFLAGS_HIGH.Category_Interface; public bool IsString => HasComponentSize && !IsArray && RawGetComponentSize() == 2; @@ -94,7 +102,7 @@ internal partial struct Metadata_1 public bool IsArray => GetFlag(WFLAGS_HIGH.Category_Array_Mask) == WFLAGS_HIGH.Category_Array; public bool IsStringOrArray => HasComponentSize; - public ushort RawGetComponentSize() => (ushort)(MTFlags >> 16); + public ushort RawGetComponentSize() => (ushort)(MTFlags & 0x0000ffff); private bool TestFlagWithMask(WFLAGS_LOW mask, WFLAGS_LOW flag) { @@ -211,7 +219,7 @@ The contract additionally depends on the `EEClass` data descriptor. public ushort GetNumInterfaces(MethodTableHandle methodTableHandle) => _methodTables[methodTableHandle.Address].NumInterfaces; - public uint GetTypeDefTypeAttributes(MethodTableHandle methodTableHandle) => GetClassData(methodTableHandle).AttrClass; + public uint GetTypeDefTypeAttributes(MethodTableHandle methodTableHandle) => GetClassData(methodTableHandle).CorTypeAttr; public bool IsDynamicStatics(MethodTableHandle methodTableHandle) => _methodTables[methodTableHandle.Address].Flags.GetFlag(WFLAGS2_ENUM.DynamicStatics) != 0; ``` diff --git a/src/coreclr/debug/runtimeinfo/datadescriptor.h b/src/coreclr/debug/runtimeinfo/datadescriptor.h index 9231a3d7efdc55..0e0aa0c8a819d2 100644 --- a/src/coreclr/debug/runtimeinfo/datadescriptor.h +++ b/src/coreclr/debug/runtimeinfo/datadescriptor.h @@ -174,7 +174,7 @@ CDAC_TYPE_BEGIN(EEClass) CDAC_TYPE_INDETERMINATE(EEClass) CDAC_TYPE_FIELD(EEClass, /*pointer*/, MethodTable, cdac_offsets::MethodTable) CDAC_TYPE_FIELD(EEClass, /*uint16*/, NumMethods, cdac_offsets::NumMethods) -CDAC_TYPE_FIELD(EEClass, /*uint32*/, AttrClass, cdac_offsets::AttrClass) +CDAC_TYPE_FIELD(EEClass, /*uint32*/, CorTypeAttr, cdac_offsets::CorTypeAttr) CDAC_TYPE_END(EEClass) CDAC_TYPES_END() diff --git a/src/coreclr/vm/class.h b/src/coreclr/vm/class.h index 95144fb4c5ad06..74c66714555f36 100644 --- a/src/coreclr/vm/class.h +++ b/src/coreclr/vm/class.h @@ -1805,7 +1805,7 @@ template<> struct cdac_offsets { static constexpr size_t MethodTable = offsetof(EEClass, m_pMethodTable); static constexpr size_t NumMethods = offsetof(EEClass, m_NumMethods); - static constexpr size_t AttrClass = offsetof(EEClass, m_dwAttrClass); + static constexpr size_t CorTypeAttr = offsetof(EEClass, m_dwAttrClass); }; // -------------------------------------------------------------------------------------------- diff --git a/src/native/managed/cdacreader/src/Contracts/Metadata_1.MethodTableFlags.cs b/src/native/managed/cdacreader/src/Contracts/Metadata_1.MethodTableFlags.cs index fdd34178a0bf44..5d471d758f0757 100644 --- a/src/native/managed/cdacreader/src/Contracts/Metadata_1.MethodTableFlags.cs +++ b/src/native/managed/cdacreader/src/Contracts/Metadata_1.MethodTableFlags.cs @@ -7,6 +7,8 @@ namespace Microsoft.Diagnostics.DataContractReader.Contracts; internal partial struct Metadata_1 { + // The lower 16-bits of the MTFlags field are used for these flags, + // if WFLAGS_HIGH.HasComponentSize is unset [Flags] internal enum WFLAGS_LOW : uint { @@ -18,6 +20,7 @@ internal enum WFLAGS_LOW : uint 0, } + // Upper bits of MTFlags [Flags] internal enum WFLAGS_HIGH : uint { @@ -25,8 +28,9 @@ internal enum WFLAGS_HIGH : uint Category_Array = 0x00080000, Category_Array_Mask = 0x000C0000, Category_Interface = 0x000C0000, - ContainsGCPointers = 0x01000000, // Contains object references - HasComponentSize = 0x80000000, // This is set if component size is used for flags. + ContainsGCPointers = 0x01000000, + HasComponentSize = 0x80000000, // This is set if lower 16 bits is used for the component size, + // otherwise the lower bits are used for WFLAGS_LOW } [Flags] @@ -44,7 +48,6 @@ internal struct MethodTableFlags public uint BaseSize { get; init; } private const int MTFlags2TypeDefRidShift = 8; - private const int MTFlagsComponentSizeShift = 16; private WFLAGS_HIGH FlagsHigh => (WFLAGS_HIGH)MTFlags; private WFLAGS_LOW FlagsLow => (WFLAGS_LOW)MTFlags; public int GetTypeDefRid() => (int)(MTFlags2 >> MTFlags2TypeDefRidShift); @@ -61,7 +64,7 @@ internal struct MethodTableFlags public bool IsArray => GetFlag(WFLAGS_HIGH.Category_Array_Mask) == WFLAGS_HIGH.Category_Array; public bool IsStringOrArray => HasComponentSize; - public ushort RawGetComponentSize() => (ushort)(MTFlags >> MTFlagsComponentSizeShift); + public ushort RawGetComponentSize() => (ushort)(MTFlags & 0x0000ffff); private bool TestFlagWithMask(WFLAGS_LOW mask, WFLAGS_LOW flag) { diff --git a/src/native/managed/cdacreader/src/Contracts/Metadata_1.cs b/src/native/managed/cdacreader/src/Contracts/Metadata_1.cs index 61b3591e34ee97..eacf16d2ddef2b 100644 --- a/src/native/managed/cdacreader/src/Contracts/Metadata_1.cs +++ b/src/native/managed/cdacreader/src/Contracts/Metadata_1.cs @@ -113,7 +113,8 @@ internal partial struct Metadata_1 : IMetadata private readonly Target _target; private readonly TargetPointer _freeObjectMethodTablePointer; - // FIXME: we mutate this dictionary - copies of the Metadata_1 struct share this instance + // TODO(cdac): we mutate this dictionary - copies of the Metadata_1 struct share this instance. + // If we need to invalidate our view of memory, we shoudl clear this dictionary. private readonly Dictionary _methodTables = new(); [Flags] @@ -132,7 +133,7 @@ internal Metadata_1(Target target, TargetPointer freeObjectMethodTablePointer) internal TargetPointer FreeObjectMethodTablePointer => _freeObjectMethodTablePointer; - private NonValidatedMethodTable_1 GetUntrustedMethodTableData(TargetPointer methodTablePointer) + private NonValidatedMethodTable_1 GetNonValidatedMethodTableData(TargetPointer methodTablePointer) { return new NonValidatedMethodTable_1(_target, methodTablePointer); } @@ -159,7 +160,7 @@ public MethodTableHandle GetMethodTableHandle(TargetPointer methodTablePointer) } // Otherwse, get ready to validate - NonValidatedMethodTable_1 nonvalidatedMethodTable = GetUntrustedMethodTableData(methodTablePointer); + NonValidatedMethodTable_1 nonvalidatedMethodTable = GetNonValidatedMethodTableData(methodTablePointer); // if it's the free object method table, we trust it to be valid if (methodTablePointer == FreeObjectMethodTablePointer) @@ -225,7 +226,7 @@ private bool ValidateWithPossibleAV(NonValidatedMethodTable_1 methodTable) } if (methodTable.Flags.HasInstantiation || methodTable.Flags.IsArray) { - NonValidatedMethodTable_1 methodTableFromClass = GetUntrustedMethodTableData(methodTablePtrFromClass); + NonValidatedMethodTable_1 methodTableFromClass = GetNonValidatedMethodTableData(methodTablePtrFromClass); TargetPointer classFromMethodTable = GetClassWithPossibleAV(methodTableFromClass); return classFromMethodTable == eeClassPtr; } @@ -261,7 +262,7 @@ private TargetPointer GetClassWithPossibleAV(NonValidatedMethodTable_1 methodTab else { TargetPointer canonicalMethodTablePtr = methodTable.CanonMT; - NonValidatedMethodTable_1 umt = GetUntrustedMethodTableData(canonicalMethodTablePtr); + NonValidatedMethodTable_1 umt = GetNonValidatedMethodTableData(canonicalMethodTablePtr); return umt.EEClass; } } @@ -326,7 +327,7 @@ public uint GetTypeDefToken(MethodTableHandle methodTableHandle) public ushort GetNumInterfaces(MethodTableHandle methodTableHandle) => _methodTables[methodTableHandle.Address].NumInterfaces; - public uint GetTypeDefTypeAttributes(MethodTableHandle methodTableHandle) => GetClassData(methodTableHandle).AttrClass; + public uint GetTypeDefTypeAttributes(MethodTableHandle methodTableHandle) => GetClassData(methodTableHandle).CorTypeAttr; public bool IsDynamicStatics(MethodTableHandle methodTableHandle) => _methodTables[methodTableHandle.Address].Flags.GetFlag(WFLAGS2_ENUM.DynamicStatics) != 0; diff --git a/src/native/managed/cdacreader/src/Data/EEClass.cs b/src/native/managed/cdacreader/src/Data/EEClass.cs index eb3cf6e7acb4fd..e697b3f40756c8 100644 --- a/src/native/managed/cdacreader/src/Data/EEClass.cs +++ b/src/native/managed/cdacreader/src/Data/EEClass.cs @@ -14,10 +14,10 @@ public EEClass(Target target, TargetPointer address) MethodTable = target.ReadPointer(address + (ulong)type.Fields[nameof(MethodTable)].Offset); NumMethods = target.Read(address + (ulong)type.Fields[nameof(NumMethods)].Offset); - AttrClass = target.Read(address + (ulong)type.Fields[nameof(AttrClass)].Offset); + CorTypeAttr = target.Read(address + (ulong)type.Fields[nameof(CorTypeAttr)].Offset); } public TargetPointer MethodTable { get; init; } public ushort NumMethods { get; init; } - public uint AttrClass { get; init; } + public uint CorTypeAttr { get; init; } } diff --git a/src/native/managed/cdacreader/tests/MethodTableTests.cs b/src/native/managed/cdacreader/tests/MethodTableTests.cs index 69474218bea16e..6a1bc5ddd332c8 100644 --- a/src/native/managed/cdacreader/tests/MethodTableTests.cs +++ b/src/native/managed/cdacreader/tests/MethodTableTests.cs @@ -3,6 +3,7 @@ using System; using System.Text; +using Microsoft.Diagnostics.DataContractReader.Contracts; using Microsoft.Diagnostics.DataContractReader.Data; using Xunit; @@ -31,7 +32,7 @@ public unsafe class MethodTableTests { Fields = { { nameof (Data.EEClass.MethodTable), new () { Offset = 8, Type = DataType.pointer}}, - { nameof (Data.EEClass.AttrClass), new () { Offset = 16, Type = DataType.uint32}}, + { nameof (Data.EEClass.CorTypeAttr), new () { Offset = 16, Type = DataType.uint32}}, { nameof (Data.EEClass.NumMethods), new () { Offset = 20, Type = DataType.uint16}}, } }; @@ -63,24 +64,34 @@ private static MockMemorySpace.Builder AddEEClass(TargetTestHelpers targetTestHe MockMemorySpace.HeapFragment eeClassFragment = new() { Name = $"EEClass '{name}'", Address = eeClassPtr, Data = new byte[targetTestHelpers.SizeOfTypeInfo(EEClassTypeInfo)] }; Span dest = eeClassFragment.Data; targetTestHelpers.WritePointer(dest.Slice(EEClassTypeInfo.Fields[nameof(Data.EEClass.MethodTable)].Offset), canonMTPtr); - // TODO: fill in the rest of the fields + targetTestHelpers.Write(dest.Slice(EEClassTypeInfo.Fields[nameof(Data.EEClass.CorTypeAttr)].Offset), attr); + targetTestHelpers.Write(dest.Slice(EEClassTypeInfo.Fields[nameof(Data.EEClass.NumMethods)].Offset), numMethods); return builder.AddHeapFragment(eeClassFragment); } - private static MockMemorySpace.Builder AddMethodTable(TargetTestHelpers targetTestHelpers, MockMemorySpace.Builder builder, TargetPointer methodTablePtr, string name, TargetPointer eeClassOrCanonMT, uint mtflags, uint mtflags2, uint baseSize) + private static MockMemorySpace.Builder AddMethodTable(TargetTestHelpers targetTestHelpers, MockMemorySpace.Builder builder, TargetPointer methodTablePtr, string name, TargetPointer eeClassOrCanonMT, uint mtflags, uint mtflags2, uint baseSize, + TargetPointer module, TargetPointer parentMethodTable, ushort numInterfaces, ushort numVirtuals) { MockMemorySpace.HeapFragment methodTableFragment = new() { Name = $"MethodTable '{name}'", Address = methodTablePtr, Data = new byte[targetTestHelpers.SizeOfTypeInfo(MethodTableTypeInfo)] }; Span dest = methodTableFragment.Data; targetTestHelpers.WritePointer(dest.Slice(MethodTableTypeInfo.Fields[nameof(Data.MethodTable.EEClassOrCanonMT)].Offset), eeClassOrCanonMT); + targetTestHelpers.Write(dest.Slice(MethodTableTypeInfo.Fields[nameof(Data.MethodTable.MTFlags)].Offset), mtflags); + targetTestHelpers.Write(dest.Slice(MethodTableTypeInfo.Fields[nameof(Data.MethodTable.MTFlags2)].Offset), mtflags2); + targetTestHelpers.Write(dest.Slice(MethodTableTypeInfo.Fields[nameof(Data.MethodTable.BaseSize)].Offset), baseSize); + targetTestHelpers.WritePointer(dest.Slice(MethodTableTypeInfo.Fields[nameof(Data.MethodTable.Module)].Offset), module); + targetTestHelpers.WritePointer(dest.Slice(MethodTableTypeInfo.Fields[nameof(Data.MethodTable.ParentMethodTable)].Offset), parentMethodTable); + targetTestHelpers.Write(dest.Slice(MethodTableTypeInfo.Fields[nameof(Data.MethodTable.NumInterfaces)].Offset), numInterfaces); + targetTestHelpers.Write(dest.Slice(MethodTableTypeInfo.Fields[nameof(Data.MethodTable.NumVirtuals)].Offset), numVirtuals); + // TODO fill in the rest of the fields return builder.AddHeapFragment(methodTableFragment); } // a delegate for adding more heap fragments to the context builder - private delegate MockMemorySpace.Builder ConfigureContextBuilder(MockTarget.Architecture arch, MockMemorySpace.Builder builder); + private delegate MockMemorySpace.Builder ConfigureContextBuilder(MockMemorySpace.Builder builder); - private static void MetadataContractHelper(MockTarget.Architecture arch, ConfigureContextBuilder configure, Action testCase) + private static void MetadataContractHelper(MockTarget.Architecture arch, ConfigureContextBuilder configure, Action testCase) { TargetTestHelpers targetTestHelpers = new(arch); string metadataTypesJson = TargetTestHelpers.MakeTypesJson(MetadataTypes); @@ -119,7 +130,7 @@ private static void MetadataContractHelper(MockTarget.Architecture arch, Configu if (configure != null) { - builder = configure(arch, builder); + builder = configure(builder); } using MockMemorySpace.ReadContext context = builder.Create(); @@ -127,7 +138,7 @@ private static void MetadataContractHelper(MockTarget.Architecture arch, Configu bool success = MockMemorySpace.TryCreateTarget(&context, out Target? target); Assert.True(success); - testCase(arch, target); + testCase(target); } GC.KeepAlive(json); } @@ -136,7 +147,7 @@ private static void MetadataContractHelper(MockTarget.Architecture arch, Configu [ClassData(typeof(MockTarget.StdArch))] public void HasMetadataContract(MockTarget.Architecture arch) { - MetadataContractHelper(arch, default, static (arch, target) => + MetadataContractHelper(arch, default, (target) => { Contracts.IMetadata metadataContract = target.Contracts.Metadata; Assert.NotNull(metadataContract); @@ -146,6 +157,18 @@ public void HasMetadataContract(MockTarget.Architecture arch) }); } + private static MockMemorySpace.Builder AddSystemObject(TargetTestHelpers targetTestHelpers, MockMemorySpace.Builder builder, TargetPointer systemObjectMethodTablePtr, TargetPointer systemObjectEEClassPtr) + { + System.Reflection.TypeAttributes typeAttributes = System.Reflection.TypeAttributes.Public | System.Reflection.TypeAttributes.Class; + const int numMethods = 8; // System.Object has 8 methods + const int numVirtuals = 3; // System.Object has 3 virtual methods + builder = AddEEClass(targetTestHelpers, builder, systemObjectEEClassPtr, "System.Object", systemObjectMethodTablePtr, attr: (uint)typeAttributes, numMethods: numMethods); + builder = AddMethodTable(targetTestHelpers, builder, systemObjectMethodTablePtr, "System.Object", systemObjectEEClassPtr, + mtflags: default, mtflags2: default, baseSize: targetTestHelpers.ObjectBaseSize, + module: TargetPointer.Null, parentMethodTable: TargetPointer.Null, numInterfaces: 0, numVirtuals: numVirtuals); + return builder; + } + [Theory] [ClassData(typeof(MockTarget.StdArch))] public void ValidateSystemObjectMethodTable(MockTarget.Architecture arch) @@ -154,17 +177,15 @@ public void ValidateSystemObjectMethodTable(MockTarget.Architecture arch) const ulong SystemObjectEEClassAddress = 0x00000000_7c0000d0; TargetPointer systemObjectMethodTablePtr = new TargetPointer(SystemObjectMethodTableAddress); TargetPointer systemObjectEEClassPtr = new TargetPointer(SystemObjectEEClassAddress); + TargetTestHelpers targetTestHelpers = new(arch); MetadataContractHelper(arch, - (arch, builder) => + (builder) => { - TargetTestHelpers targetTestHelpers = new(arch); - builder = AddEEClass(targetTestHelpers, builder, systemObjectEEClassPtr, "System.Object", systemObjectMethodTablePtr, attr: default, numMethods: default); - builder = AddMethodTable(targetTestHelpers, builder, systemObjectMethodTablePtr, "System.Object", systemObjectEEClassPtr, mtflags: default, mtflags2: default, baseSize: default); + builder = AddSystemObject(targetTestHelpers, builder, systemObjectMethodTablePtr, systemObjectEEClassPtr); return builder; }, - (arch, target) => + (target) => { - TargetTestHelpers targetTestHelpers = new(arch); Contracts.IMetadata metadataContract = target.Contracts.Metadata; Assert.NotNull(metadataContract); Contracts.MethodTableHandle systemObjectMethodTableHandle = metadataContract.GetMethodTableHandle(systemObjectMethodTablePtr); @@ -172,4 +193,44 @@ public void ValidateSystemObjectMethodTable(MockTarget.Architecture arch) Assert.False(metadataContract.IsFreeObjectMethodTable(systemObjectMethodTableHandle)); }); } + + [Theory] + [ClassData(typeof(MockTarget.StdArch))] + public void ValidateSystemStringMethodTable(MockTarget.Architecture arch) + { + const ulong SystemObjectMethodTableAddress = 0x00000000_7c000010; + const ulong SystemObjectEEClassAddress = 0x00000000_7c0000d0; + TargetPointer systemObjectMethodTablePtr = new TargetPointer(SystemObjectMethodTableAddress); + TargetPointer systemObjectEEClassPtr = new TargetPointer(SystemObjectEEClassAddress); + + const ulong SystemStringMethodTableAddress = 0x00000000_7c002010; + const ulong SystemStringEEClassAddress = 0x00000000_7c0020d0; + TargetPointer systemStringMethodTablePtr = new TargetPointer(SystemStringMethodTableAddress); + TargetPointer systemStringEEClassPtr = new TargetPointer(SystemStringEEClassAddress); + TargetTestHelpers targetTestHelpers = new(arch); + MetadataContractHelper(arch, + (builder) => + { + builder = AddSystemObject(targetTestHelpers, builder, systemObjectMethodTablePtr, systemObjectEEClassPtr); + System.Reflection.TypeAttributes typeAttributes = System.Reflection.TypeAttributes.Public | System.Reflection.TypeAttributes.Class | System.Reflection.TypeAttributes.Sealed; + const int numMethods = 37; // Arbitrary. Not trying to exactly match the real System.String + const int numInterfaces = 8; // Arbitrary + const int numVirtuals = 3; // at least as many as System.Object + uint mtflags = (uint)Metadata_1.WFLAGS_HIGH.HasComponentSize | /*componentSize: */2; + builder = AddEEClass(targetTestHelpers, builder, systemStringEEClassPtr, "System.String", systemStringMethodTablePtr, attr: (uint)typeAttributes, numMethods: numMethods); + builder = AddMethodTable(targetTestHelpers, builder, systemStringMethodTablePtr, "System.String", systemStringEEClassPtr, + mtflags: mtflags, mtflags2: default, baseSize: targetTestHelpers.StringBaseSize, + module: TargetPointer.Null, parentMethodTable: systemObjectMethodTablePtr, numInterfaces: numInterfaces, numVirtuals: numVirtuals); + return builder; + }, + (target) => + { + Contracts.IMetadata metadataContract = target.Contracts.Metadata; + Assert.NotNull(metadataContract); + Contracts.MethodTableHandle systemStringMethodTableHandle = metadataContract.GetMethodTableHandle(systemStringMethodTablePtr); + Assert.Equal(systemStringMethodTablePtr.Value, systemStringMethodTableHandle.Address.Value); + Assert.False(metadataContract.IsFreeObjectMethodTable(systemStringMethodTableHandle)); + Assert.True(metadataContract.IsString(systemStringMethodTableHandle)); + }); + } } diff --git a/src/native/managed/cdacreader/tests/TargetTestHelpers.cs b/src/native/managed/cdacreader/tests/TargetTestHelpers.cs index 6897eea952f38e..df95785d828c91 100644 --- a/src/native/managed/cdacreader/tests/TargetTestHelpers.cs +++ b/src/native/managed/cdacreader/tests/TargetTestHelpers.cs @@ -147,6 +147,55 @@ public static string MakeGlobalsJson(IEnumerable<(string Name, ulong Value, stri #region Mock memory initialization + internal uint ObjHeaderSize => (uint)(Arch.Is64Bit ? 2 * sizeof(uint) /*alignpad + syncblock*/: sizeof(uint) /* syncblock */); + internal uint ObjectSize => (uint)PointerSize /* methtab */; + + internal uint ObjectBaseSize => ObjHeaderSize + ObjectSize; + + internal uint ArrayBaseSize => Arch.Is64Bit ? ObjectSize + sizeof(uint) /* numComponents */ + sizeof(uint) /* pad*/ : ObjectSize + sizeof(uint) /* numComponents */; + + internal uint ArrayBaseBaseSize => ObjHeaderSize + ArrayBaseSize; + + internal uint StringBaseSize => ObjectBaseSize + sizeof(uint) /* length */ + sizeof(char) /* nul terminator */; + + internal void Write(Span dest, byte b) => dest[0] = b; + internal void Write(Span dest, ushort u) + { + if (Arch.IsLittleEndian) + { + BinaryPrimitives.WriteUInt16LittleEndian(dest, u); + } + else + { + BinaryPrimitives.WriteUInt16BigEndian(dest, u); + } + } + + internal void Write(Span dest, uint u) + { + if (Arch.IsLittleEndian) + { + BinaryPrimitives.WriteUInt32LittleEndian(dest, u); + } + else + { + BinaryPrimitives.WriteUInt32BigEndian(dest, u); + } + } + + internal void Write(Span dest, ulong u) + { + if (Arch.IsLittleEndian) + { + BinaryPrimitives.WriteUInt64LittleEndian(dest, u); + } + else + { + BinaryPrimitives.WriteUInt64BigEndian(dest, u); + } + } + + internal void WritePointer(Span dest, ulong value) { if (Arch.Is64Bit) From 1af7c80b3c5a56b8a417abeb122d4314f95d5bf0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aleksey=20Kliger=20=28=CE=BBgeek=29?= Date: Tue, 2 Jul 2024 15:27:31 -0400 Subject: [PATCH 52/62] Update src/coreclr/gc/env/gcenv.object.h Co-authored-by: Elinor Fung --- src/coreclr/gc/env/gcenv.object.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/coreclr/gc/env/gcenv.object.h b/src/coreclr/gc/env/gcenv.object.h index e8e0a8185cb158..f515536f6a66f8 100644 --- a/src/coreclr/gc/env/gcenv.object.h +++ b/src/coreclr/gc/env/gcenv.object.h @@ -49,7 +49,7 @@ static_assert(sizeof(ObjHeader) == sizeof(uintptr_t), "this assumption is made b #define MTFlag_RequiresAlign8 0x00001000 // enum_flag_RequiresAlign8 #define MTFlag_Category_ValueType 0x00040000 // enum_flag_Category_ValueType #define MTFlag_Category_ValueType_Mask 0x000C0000 // enum_flag_Category_ValueType_Mask -#define MTFlag_ContainsGCPointers 0x01000000 // enum_flag_ContainsGCPointers +#define MTFlag_ContainsGCPointers 0x01000000 // enum_flag_ContainsGCPointers #define MTFlag_HasCriticalFinalizer 0x00000002 // enum_flag_HasCriticalFinalizer #define MTFlag_HasFinalizer 0x00100000 // enum_flag_HasFinalizer #define MTFlag_IsArray 0x00080000 // enum_flag_Category_Array From 993ae1d6febb0d6434d6caa7834c60752ff95803 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aleksey=20Kliger=20=28=CE=BBgeek=29?= Date: Tue, 2 Jul 2024 15:28:55 -0400 Subject: [PATCH 53/62] Update src/native/managed/cdacreader/src/Contracts/Metadata_1.MethodTableFlags.cs Co-authored-by: Elinor Fung --- .../cdacreader/src/Contracts/Metadata_1.MethodTableFlags.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/native/managed/cdacreader/src/Contracts/Metadata_1.MethodTableFlags.cs b/src/native/managed/cdacreader/src/Contracts/Metadata_1.MethodTableFlags.cs index 5d471d758f0757..70cc24c0a574c5 100644 --- a/src/native/managed/cdacreader/src/Contracts/Metadata_1.MethodTableFlags.cs +++ b/src/native/managed/cdacreader/src/Contracts/Metadata_1.MethodTableFlags.cs @@ -41,8 +41,6 @@ internal enum WFLAGS2_ENUM : uint internal struct MethodTableFlags { - - public uint MTFlags { get; init; } public uint MTFlags2 { get; init; } public uint BaseSize { get; init; } From f04d88051b29710b49ae5219e9c171df938e6d4d Mon Sep 17 00:00:00 2001 From: Aleksey Kliger Date: Tue, 2 Jul 2024 15:39:50 -0400 Subject: [PATCH 54/62] Address code review feedback --- .../Contracts/Metadata_1.MethodTableFlags.cs | 2 ++ .../cdacreader/src/Contracts/Metadata_1.cs | 31 ++++++++++--------- 2 files changed, 18 insertions(+), 15 deletions(-) diff --git a/src/native/managed/cdacreader/src/Contracts/Metadata_1.MethodTableFlags.cs b/src/native/managed/cdacreader/src/Contracts/Metadata_1.MethodTableFlags.cs index 70cc24c0a574c5..10675967105272 100644 --- a/src/native/managed/cdacreader/src/Contracts/Metadata_1.MethodTableFlags.cs +++ b/src/native/managed/cdacreader/src/Contracts/Metadata_1.MethodTableFlags.cs @@ -84,5 +84,7 @@ private bool TestFlagWithMask(WFLAGS2_ENUM mask, WFLAGS2_ENUM flag) public bool HasInstantiation => !TestFlagWithMask(WFLAGS_LOW.GenericsMask, WFLAGS_LOW.GenericsMask_NonGeneric); public bool ContainsGCPointers => GetFlag(WFLAGS_HIGH.ContainsGCPointers) != 0; + + public bool IsDynamicStatics => GetFlag(WFLAGS2_ENUM.DynamicStatics) != 0; } } diff --git a/src/native/managed/cdacreader/src/Contracts/Metadata_1.cs b/src/native/managed/cdacreader/src/Contracts/Metadata_1.cs index eacf16d2ddef2b..75a498a397b3a0 100644 --- a/src/native/managed/cdacreader/src/Contracts/Metadata_1.cs +++ b/src/native/managed/cdacreader/src/Contracts/Metadata_1.cs @@ -117,6 +117,8 @@ internal partial struct Metadata_1 : IMetadata // If we need to invalidate our view of memory, we shoudl clear this dictionary. private readonly Dictionary _methodTables = new(); + // Low order bit of EEClassOrCanonMT. + // See MethodTable::LowBits UNION_EECLASS / UNION_METHODABLE [Flags] internal enum EEClassOrCanonMTBits { @@ -159,10 +161,7 @@ public MethodTableHandle GetMethodTableHandle(TargetPointer methodTablePointer) return new MethodTableHandle(methodTablePointer); } - // Otherwse, get ready to validate - NonValidatedMethodTable_1 nonvalidatedMethodTable = GetNonValidatedMethodTableData(methodTablePointer); - - // if it's the free object method table, we trust it to be valid + // If it's the free object method table, we trust it to be valid if (methodTablePointer == FreeObjectMethodTablePointer) { Data.MethodTable freeObjectMethodTableData = _target.ProcessedData.GetOrAdd(methodTablePointer); @@ -170,6 +169,10 @@ public MethodTableHandle GetMethodTableHandle(TargetPointer methodTablePointer) _ = _methodTables.TryAdd(methodTablePointer, trustedMethodTable); return new MethodTableHandle(methodTablePointer); } + + // Otherwse, get ready to validate + NonValidatedMethodTable_1 nonvalidatedMethodTable = GetNonValidatedMethodTableData(methodTablePointer); + if (!ValidateMethodTablePointer(nonvalidatedMethodTable)) { throw new ArgumentException("Invalid method table pointer"); @@ -185,7 +188,7 @@ private bool ValidateMethodTablePointer(NonValidatedMethodTable_1 umt) { try { - if (!ValidateWithPossibleAV(umt)) + if (!ValidateThrowing(umt)) { return false; } @@ -196,13 +199,17 @@ private bool ValidateMethodTablePointer(NonValidatedMethodTable_1 umt) } catch (System.Exception) { - // FIXME: maybe don't swallow all exceptions? + // TODO(cdac): maybe don't swallow all exceptions? We could consider a richer contract that + // helps to track down what sort of memory corruption caused the validation to fail. + // TODO(cdac): we could also consider a more fine-grained exception type so we don't mask + // programmer mistakes in cdacreader. return false; } return true; } - private bool ValidateWithPossibleAV(NonValidatedMethodTable_1 methodTable) + // This portion of validation may throw if we are trying to read an invalid address in the target process + private bool ValidateThrowing(NonValidatedMethodTable_1 methodTable) { // For non-generic classes, we can rely on comparing // object->methodtable->class->methodtable @@ -298,13 +305,7 @@ private TargetPointer GetClassPointer(MethodTableHandle methodTableHandle) private Data.EEClass GetClassData(MethodTableHandle methodTableHandle) { TargetPointer clsPtr = GetClassPointer(methodTableHandle); - // Check if we cached it already - if (_target.ProcessedData.TryGet(clsPtr, out Data.EEClass? eeClassData)) - { - return eeClassData; - } - eeClassData = _target.ProcessedData.GetOrAdd(clsPtr); - return eeClassData; + return _target.ProcessedData.GetOrAdd(clsPtr); } public TargetPointer GetCanonicalMethodTable(MethodTableHandle methodTableHandle) => GetClassData(methodTableHandle).MethodTable; @@ -329,6 +330,6 @@ public uint GetTypeDefToken(MethodTableHandle methodTableHandle) public uint GetTypeDefTypeAttributes(MethodTableHandle methodTableHandle) => GetClassData(methodTableHandle).CorTypeAttr; - public bool IsDynamicStatics(MethodTableHandle methodTableHandle) => _methodTables[methodTableHandle.Address].Flags.GetFlag(WFLAGS2_ENUM.DynamicStatics) != 0; + public bool IsDynamicStatics(MethodTableHandle methodTableHandle) => _methodTables[methodTableHandle.Address].Flags.IsDynamicStatics; } From 76859d19fabd6deb79254c1c30880048212b0a3c Mon Sep 17 00:00:00 2001 From: Aleksey Kliger Date: Tue, 2 Jul 2024 16:03:25 -0400 Subject: [PATCH 55/62] move non-validated MethodTable handling to a separate class --- .../src/Contracts/Metadata_1.NonValidated.cs | 198 +++++++++++++++ .../cdacreader/src/Contracts/Metadata_1.cs | 238 +++--------------- 2 files changed, 230 insertions(+), 206 deletions(-) create mode 100644 src/native/managed/cdacreader/src/Contracts/Metadata_1.NonValidated.cs diff --git a/src/native/managed/cdacreader/src/Contracts/Metadata_1.NonValidated.cs b/src/native/managed/cdacreader/src/Contracts/Metadata_1.NonValidated.cs new file mode 100644 index 00000000000000..4a521b948e7d65 --- /dev/null +++ b/src/native/managed/cdacreader/src/Contracts/Metadata_1.NonValidated.cs @@ -0,0 +1,198 @@ +// 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 partial struct Metadata_1 : IMetadata +{ + // GC Heap corruption may create situations where a pointer value may point to garbage or even + // to an unmapped memory region. + // All types here have not been validated as actually representing a MethodTable, EEClass, etc. + // All checks are unsafe and may throw if we access an invalid address in target memory. + internal static class NonValidated + { + + // This doesn't need as many properties as MethodTable because we don't want to be operating on + // a NonValidatedMethodTable for too long + internal struct MethodTable + { + private readonly Target _target; + private readonly Target.TypeInfo _type; + internal TargetPointer Address { get; init; } + + private MethodTableFlags? _methodTableFlags; + + internal MethodTable(Target target, TargetPointer methodTablePointer) + { + _target = target; + _type = target.GetTypeInfo(DataType.MethodTable); + Address = methodTablePointer; + _methodTableFlags = null; + } + + private MethodTableFlags GetOrCreateFlags() + { + if (_methodTableFlags == null) + { + // note: may throw if the method table Address is corrupted + MethodTableFlags flags = new MethodTableFlags + { + MTFlags = _target.Read(Address + (ulong)_type.Fields[nameof(MethodTableFlags.MTFlags)].Offset), + MTFlags2 = _target.Read(Address + (ulong)_type.Fields[nameof(MethodTableFlags.MTFlags2)].Offset), + BaseSize = _target.Read(Address + (ulong)_type.Fields[nameof(MethodTableFlags.BaseSize)].Offset), + }; + _methodTableFlags = flags; + } + return _methodTableFlags.Value; + } + + internal MethodTableFlags Flags => GetOrCreateFlags(); + + internal TargetPointer EEClassOrCanonMT => _target.ReadPointer(Address + (ulong)_type.Fields[nameof(EEClassOrCanonMT)].Offset); + internal TargetPointer EEClass => GetEEClassOrCanonMTBits(EEClassOrCanonMT) == EEClassOrCanonMTBits.EEClass ? EEClassOrCanonMT : throw new InvalidOperationException("not an EEClass"); + internal TargetPointer CanonMT + { + get + { + if (GetEEClassOrCanonMTBits(EEClassOrCanonMT) == EEClassOrCanonMTBits.CanonMT) + { + return new TargetPointer((ulong)EEClassOrCanonMT & ~(ulong)EEClassOrCanonMTBits.Mask); + } + else + { + throw new InvalidOperationException("not a canonical method table"); + } + } + } + } + + internal struct EEClass + { + public readonly Target _target; + private readonly Target.TypeInfo _type; + + internal TargetPointer Address { get; init; } + + internal EEClass(Target target, TargetPointer eeClassPointer) + { + _target = target; + Address = eeClassPointer; + _type = target.GetTypeInfo(DataType.EEClass); + } + + internal TargetPointer MethodTable => _target.ReadPointer(Address + (ulong)_type.Fields[nameof(MethodTable)].Offset); + } + + internal static MethodTable GetMethodTableData(Target target, TargetPointer methodTablePointer) + { + return new MethodTable(target, methodTablePointer); + } + + internal static EEClass GetEEClassData(Target target, TargetPointer eeClassPointer) + { + return new EEClass(target, eeClassPointer); + } + + } + + /// + /// Validates that the given address is a valid MethodTable. + /// + /// + /// If the target process has memory corruption, we may see pointers that are not valid method tables. + /// We validate by looking at the MethodTable -> EEClass -> MethodTable relationship (which may throw if we access invalid memory). + /// And then we do some ad-hoc checks on the method table flags. + private bool ValidateMethodTablePointer(NonValidated.MethodTable umt) + { + try + { + if (!ValidateThrowing(umt)) + { + return false; + } + if (!ValidateMethodTableAdHoc(umt)) + { + return false; + } + } + catch (System.Exception) + { + // TODO(cdac): maybe don't swallow all exceptions? We could consider a richer contract that + // helps to track down what sort of memory corruption caused the validation to fail. + // TODO(cdac): we could also consider a more fine-grained exception type so we don't mask + // programmer mistakes in cdacreader. + return false; + } + return true; + } + + // This portion of validation may throw if we are trying to read an invalid address in the target process + private bool ValidateThrowing(NonValidated.MethodTable methodTable) + { + // For non-generic classes, we can rely on comparing + // object->methodtable->class->methodtable + // to + // object->methodtable + // + // However, for generic instantiation this does not work. There we must + // compare + // + // object->methodtable->class->methodtable->class + // to + // object->methodtable->class + TargetPointer eeClassPtr = GetClassThrowing(methodTable); + if (eeClassPtr != TargetPointer.Null) + { + NonValidated.EEClass eeClass = NonValidated.GetEEClassData(_target, eeClassPtr); + TargetPointer methodTablePtrFromClass = eeClass.MethodTable; + if (methodTable.Address == methodTablePtrFromClass) + { + return true; + } + if (methodTable.Flags.HasInstantiation || methodTable.Flags.IsArray) + { + NonValidated.MethodTable methodTableFromClass = NonValidated.GetMethodTableData(_target, methodTablePtrFromClass); + TargetPointer classFromMethodTable = GetClassThrowing(methodTableFromClass); + return classFromMethodTable == eeClassPtr; + } + } + return false; + } + + private bool ValidateMethodTableAdHoc(NonValidated.MethodTable methodTable) + { + // ad-hoc checks; add more here as needed + if (!methodTable.Flags.IsInterface && !methodTable.Flags.IsString) + { + if (methodTable.Flags.BaseSize == 0 || !_target.IsAlignedToPointerSize(methodTable.Flags.BaseSize)) + { + return false; + } + } + return true; + } + + internal static EEClassOrCanonMTBits GetEEClassOrCanonMTBits(TargetPointer eeClassOrCanonMTPtr) + { + return (EEClassOrCanonMTBits)(eeClassOrCanonMTPtr & (ulong)EEClassOrCanonMTBits.Mask); + } + private TargetPointer GetClassThrowing(NonValidated.MethodTable methodTable) + { + TargetPointer eeClassOrCanonMT = methodTable.EEClassOrCanonMT; + + if (GetEEClassOrCanonMTBits(eeClassOrCanonMT) == EEClassOrCanonMTBits.EEClass) + { + return methodTable.EEClass; + } + else + { + TargetPointer canonicalMethodTablePtr = methodTable.CanonMT; + NonValidated.MethodTable umt = NonValidated.GetMethodTableData(_target, canonicalMethodTablePtr); + return umt.EEClass; + } + } + + +} diff --git a/src/native/managed/cdacreader/src/Contracts/Metadata_1.cs b/src/native/managed/cdacreader/src/Contracts/Metadata_1.cs index 75a498a397b3a0..49802a50c06206 100644 --- a/src/native/managed/cdacreader/src/Contracts/Metadata_1.cs +++ b/src/native/managed/cdacreader/src/Contracts/Metadata_1.cs @@ -4,118 +4,44 @@ using System; using System.Collections.Generic; using System.Reflection.Metadata.Ecma335; -using Microsoft.Diagnostics.DataContractReader.Data; namespace Microsoft.Diagnostics.DataContractReader.Contracts; -// GC Heap corruption may create situations where a putative pointer to a MethodTable -// may point to garbage. So this struct represents a MethodTable that we don't necessarily -// trust to be valid. -// see Metadata_1.ValidateMethodTablePointer -// This doesn't need as many properties as MethodTable because we don't want to be operating on -// a NonValidatedMethodTable for too long -internal struct NonValidatedMethodTable_1 + + +internal partial struct Metadata_1 : IMetadata { private readonly Target _target; - private readonly Target.TypeInfo _type; - internal TargetPointer Address { get; init; } + private readonly TargetPointer _freeObjectMethodTablePointer; - private Metadata_1.MethodTableFlags? _methodTableFlags; + // TODO(cdac): we mutate this dictionary - copies of the Metadata_1 struct share this instance. + // If we need to invalidate our view of memory, we shoudl clear this dictionary. + private readonly Dictionary _methodTables = new(); - internal NonValidatedMethodTable_1(Target target, TargetPointer methodTablePointer) - { - _target = target; - _type = target.GetTypeInfo(DataType.MethodTable); - Address = methodTablePointer; - _methodTableFlags = null; - } - private Metadata_1.MethodTableFlags GetOrCreateFlags() + internal struct MethodTable { - if (_methodTableFlags == null) + internal MethodTableFlags Flags { get; } + internal ushort NumInterfaces { get; } + internal ushort NumVirtuals { get; } + internal TargetPointer ParentMethodTable { get; } + internal TargetPointer Module { get; } + internal TargetPointer EEClassOrCanonMT { get; } + internal MethodTable(Data.MethodTable data) { - // note: may throw if the method table Address is corrupted - Metadata_1.MethodTableFlags flags = new Metadata_1.MethodTableFlags + Flags = new MethodTableFlags { - MTFlags = _target.Read(Address + (ulong)_type.Fields[nameof(Metadata_1.MethodTableFlags.MTFlags)].Offset), - MTFlags2 = _target.Read(Address + (ulong)_type.Fields[nameof(Metadata_1.MethodTableFlags.MTFlags2)].Offset), - BaseSize = _target.Read(Address + (ulong)_type.Fields[nameof(Metadata_1.MethodTableFlags.BaseSize)].Offset), + MTFlags = data.MTFlags, + MTFlags2 = data.MTFlags2, + BaseSize = data.BaseSize, }; - _methodTableFlags = flags; - } - return _methodTableFlags.Value; - } - - internal Metadata_1.MethodTableFlags Flags => GetOrCreateFlags(); - - internal TargetPointer EEClassOrCanonMT => _target.ReadPointer(Address + (ulong)_type.Fields[nameof(EEClassOrCanonMT)].Offset); - internal TargetPointer EEClass => Metadata_1.GetEEClassOrCanonMTBits(EEClassOrCanonMT) == Metadata_1.EEClassOrCanonMTBits.EEClass ? EEClassOrCanonMT : throw new InvalidOperationException("not an EEClass"); - internal TargetPointer CanonMT - { - get - { - if (Metadata_1.GetEEClassOrCanonMTBits(EEClassOrCanonMT) == Metadata_1.EEClassOrCanonMTBits.CanonMT) - { - return new TargetPointer((ulong)EEClassOrCanonMT & ~(ulong)Metadata_1.EEClassOrCanonMTBits.Mask); - } - else - { - throw new InvalidOperationException("not a canonical method table"); - } + NumInterfaces = data.NumInterfaces; + NumVirtuals = data.NumVirtuals; + EEClassOrCanonMT = data.EEClassOrCanonMT; + Module = data.Module; + ParentMethodTable = data.ParentMethodTable; } } -} - -internal struct NonValidatedEEClass_1 -{ - public readonly Target _target; - private readonly Target.TypeInfo _type; - - internal TargetPointer Address { get; init; } - - internal NonValidatedEEClass_1(Target target, TargetPointer eeClassPointer) - { - _target = target; - Address = eeClassPointer; - _type = target.GetTypeInfo(DataType.EEClass); - } - - internal TargetPointer MethodTable => _target.ReadPointer(Address + (ulong)_type.Fields[nameof(MethodTable)].Offset); -} - - -internal struct MethodTable_1 -{ - internal Metadata_1.MethodTableFlags Flags { get; } - internal ushort NumInterfaces { get; } - internal ushort NumVirtuals { get; } - internal TargetPointer ParentMethodTable { get; } - internal TargetPointer Module { get; } - internal TargetPointer EEClassOrCanonMT { get; } - internal MethodTable_1(Data.MethodTable data) - { - Flags = new Metadata_1.MethodTableFlags - { - MTFlags = data.MTFlags, - MTFlags2 = data.MTFlags2, - BaseSize = data.BaseSize, - }; - NumInterfaces = data.NumInterfaces; - NumVirtuals = data.NumVirtuals; - EEClassOrCanonMT = data.EEClassOrCanonMT; - Module = data.Module; - ParentMethodTable = data.ParentMethodTable; - } -} - -internal partial struct Metadata_1 : IMetadata -{ - private readonly Target _target; - private readonly TargetPointer _freeObjectMethodTablePointer; - - // TODO(cdac): we mutate this dictionary - copies of the Metadata_1 struct share this instance. - // If we need to invalidate our view of memory, we shoudl clear this dictionary. - private readonly Dictionary _methodTables = new(); // Low order bit of EEClassOrCanonMT. // See MethodTable::LowBits UNION_EECLASS / UNION_METHODABLE @@ -135,15 +61,6 @@ internal Metadata_1(Target target, TargetPointer freeObjectMethodTablePointer) internal TargetPointer FreeObjectMethodTablePointer => _freeObjectMethodTablePointer; - private NonValidatedMethodTable_1 GetNonValidatedMethodTableData(TargetPointer methodTablePointer) - { - return new NonValidatedMethodTable_1(_target, methodTablePointer); - } - - private NonValidatedEEClass_1 GetUntrustedEEClassData(TargetPointer eeClassPointer) - { - return new NonValidatedEEClass_1(_target, eeClassPointer); - } public MethodTableHandle GetMethodTableHandle(TargetPointer methodTablePointer) { @@ -156,7 +73,7 @@ public MethodTableHandle GetMethodTableHandle(TargetPointer methodTablePointer) if (_target.ProcessedData.TryGet(methodTablePointer, out Data.MethodTable? methodTableData)) { // we already cached the data, we must have validated the address, create the representation struct for our use - MethodTable_1 trustedMethodTable = new MethodTable_1(methodTableData); + MethodTable trustedMethodTable = new MethodTable(methodTableData); _ = _methodTables.TryAdd(methodTablePointer, trustedMethodTable); return new MethodTableHandle(methodTablePointer); } @@ -165,13 +82,13 @@ public MethodTableHandle GetMethodTableHandle(TargetPointer methodTablePointer) if (methodTablePointer == FreeObjectMethodTablePointer) { Data.MethodTable freeObjectMethodTableData = _target.ProcessedData.GetOrAdd(methodTablePointer); - MethodTable_1 trustedMethodTable = new MethodTable_1(freeObjectMethodTableData); + MethodTable trustedMethodTable = new MethodTable(freeObjectMethodTableData); _ = _methodTables.TryAdd(methodTablePointer, trustedMethodTable); return new MethodTableHandle(methodTablePointer); } // Otherwse, get ready to validate - NonValidatedMethodTable_1 nonvalidatedMethodTable = GetNonValidatedMethodTableData(methodTablePointer); + NonValidated.MethodTable nonvalidatedMethodTable = NonValidated.GetMethodTableData(_target, methodTablePointer); if (!ValidateMethodTablePointer(nonvalidatedMethodTable)) { @@ -179,106 +96,15 @@ public MethodTableHandle GetMethodTableHandle(TargetPointer methodTablePointer) } // ok, we validated it, cache the data and add the MethodTable_1 struct to the dictionary Data.MethodTable trustedMethodTableData = _target.ProcessedData.GetOrAdd(methodTablePointer); - MethodTable_1 trustedMethodTableF = new MethodTable_1(trustedMethodTableData); + MethodTable trustedMethodTableF = new MethodTable(trustedMethodTableData); _ = _methodTables.TryAdd(methodTablePointer, trustedMethodTableF); return new MethodTableHandle(methodTablePointer); } - private bool ValidateMethodTablePointer(NonValidatedMethodTable_1 umt) - { - try - { - if (!ValidateThrowing(umt)) - { - return false; - } - if (!ValidateMethodTable(umt)) - { - return false; - } - } - catch (System.Exception) - { - // TODO(cdac): maybe don't swallow all exceptions? We could consider a richer contract that - // helps to track down what sort of memory corruption caused the validation to fail. - // TODO(cdac): we could also consider a more fine-grained exception type so we don't mask - // programmer mistakes in cdacreader. - return false; - } - return true; - } - - // This portion of validation may throw if we are trying to read an invalid address in the target process - private bool ValidateThrowing(NonValidatedMethodTable_1 methodTable) - { - // For non-generic classes, we can rely on comparing - // object->methodtable->class->methodtable - // to - // object->methodtable - // - // However, for generic instantiation this does not work. There we must - // compare - // - // object->methodtable->class->methodtable->class - // to - // object->methodtable->class - TargetPointer eeClassPtr = GetClassWithPossibleAV(methodTable); - if (eeClassPtr != TargetPointer.Null) - { - NonValidatedEEClass_1 eeClass = GetUntrustedEEClassData(eeClassPtr); - TargetPointer methodTablePtrFromClass = GetMethodTableWithPossibleAV(eeClass); - if (methodTable.Address == methodTablePtrFromClass) - { - return true; - } - if (methodTable.Flags.HasInstantiation || methodTable.Flags.IsArray) - { - NonValidatedMethodTable_1 methodTableFromClass = GetNonValidatedMethodTableData(methodTablePtrFromClass); - TargetPointer classFromMethodTable = GetClassWithPossibleAV(methodTableFromClass); - return classFromMethodTable == eeClassPtr; - } - } - return false; - } - - private bool ValidateMethodTable(NonValidatedMethodTable_1 methodTable) - { - // ad-hoc checks; add more here as needed - if (!methodTable.Flags.IsInterface && !methodTable.Flags.IsString) - { - if (methodTable.Flags.BaseSize == 0 || !_target.IsAlignedToPointerSize(methodTable.Flags.BaseSize)) - { - return false; - } - } - return true; - } - - internal static EEClassOrCanonMTBits GetEEClassOrCanonMTBits(TargetPointer eeClassOrCanonMTPtr) - { - return (EEClassOrCanonMTBits)(eeClassOrCanonMTPtr & (ulong)EEClassOrCanonMTBits.Mask); - } - private TargetPointer GetClassWithPossibleAV(NonValidatedMethodTable_1 methodTable) - { - TargetPointer eeClassOrCanonMT = methodTable.EEClassOrCanonMT; - - if (GetEEClassOrCanonMTBits(eeClassOrCanonMT) == EEClassOrCanonMTBits.EEClass) - { - return methodTable.EEClass; - } - else - { - TargetPointer canonicalMethodTablePtr = methodTable.CanonMT; - NonValidatedMethodTable_1 umt = GetNonValidatedMethodTableData(canonicalMethodTablePtr); - return umt.EEClass; - } - } - - private static TargetPointer GetMethodTableWithPossibleAV(NonValidatedEEClass_1 eeClass) => eeClass.MethodTable; public uint GetBaseSize(MethodTableHandle methodTableHandle) => _methodTables[methodTableHandle.Address].Flags.BaseSize; - private static uint GetComponentSize(MethodTable_1 methodTable) + private static uint GetComponentSize(MethodTable methodTable) { return methodTable.Flags.HasComponentSize ? methodTable.Flags.RawGetComponentSize() : 0u; } @@ -286,7 +112,7 @@ private static uint GetComponentSize(MethodTable_1 methodTable) private TargetPointer GetClassPointer(MethodTableHandle methodTableHandle) { - MethodTable_1 methodTable = _methodTables[methodTableHandle.Address]; + MethodTable methodTable = _methodTables[methodTableHandle.Address]; switch (GetEEClassOrCanonMTBits(methodTable.EEClassOrCanonMT)) { case EEClassOrCanonMTBits.EEClass: @@ -294,7 +120,7 @@ private TargetPointer GetClassPointer(MethodTableHandle methodTableHandle) case EEClassOrCanonMTBits.CanonMT: TargetPointer canonMTPtr = new TargetPointer((ulong)methodTable.EEClassOrCanonMT & ~(ulong)Metadata_1.EEClassOrCanonMTBits.Mask); MethodTableHandle canonMTHandle = GetMethodTableHandle(canonMTPtr); - MethodTable_1 canonMT = _methodTables[canonMTHandle.Address]; + MethodTable canonMT = _methodTables[canonMTHandle.Address]; return canonMT.EEClassOrCanonMT; // canonical method table EEClassOrCanonMT is always EEClass default: throw new InvalidOperationException(); @@ -320,7 +146,7 @@ private Data.EEClass GetClassData(MethodTableHandle methodTableHandle) public uint GetTypeDefToken(MethodTableHandle methodTableHandle) { - MethodTable_1 methodTable = _methodTables[methodTableHandle.Address]; + MethodTable methodTable = _methodTables[methodTableHandle.Address]; return (uint)(methodTable.Flags.GetTypeDefRid() | ((int)TableIndex.TypeDef << 24)); } From a12a407bb31efa2dcc7cc13875e743bf6101dade Mon Sep 17 00:00:00 2001 From: Aleksey Kliger Date: Tue, 2 Jul 2024 16:19:47 -0400 Subject: [PATCH 56/62] clear up ComponentSize contract spec and impl --- docs/design/datacontracts/Metadata.md | 23 +++++++++---------- .../Contracts/Metadata_1.MethodTableFlags.cs | 17 ++++++-------- .../cdacreader/src/Contracts/Metadata_1.cs | 6 +---- 3 files changed, 19 insertions(+), 27 deletions(-) diff --git a/docs/design/datacontracts/Metadata.md b/docs/design/datacontracts/Metadata.md index 1c100663481753..c5653347c0574d 100644 --- a/docs/design/datacontracts/Metadata.md +++ b/docs/design/datacontracts/Metadata.md @@ -94,15 +94,6 @@ internal partial struct Metadata_1 public WFLAGS_HIGH GetFlag(WFLAGS_HIGH mask) { ... /* mask & upper 16 bits of MTFlags */ } public WFLAGS2_ENUM GetFlag(WFLAGS2_ENUM mask) { ... /* mask & MTFlags2*/ } - public bool IsInterface => GetFlag(WFLAGS_HIGH.Category_Mask) == WFLAGS_HIGH.Category_Interface; - public bool IsString => HasComponentSize && !IsArray && RawGetComponentSize() == 2; - - public bool HasComponentSize => GetFlag(WFLAGS_HIGH.HasComponentSize) != 0; - - public bool IsArray => GetFlag(WFLAGS_HIGH.Category_Array_Mask) == WFLAGS_HIGH.Category_Array; - - public bool IsStringOrArray => HasComponentSize; - public ushort RawGetComponentSize() => (ushort)(MTFlags & 0x0000ffff); private bool TestFlagWithMask(WFLAGS_LOW mask, WFLAGS_LOW flag) { @@ -116,9 +107,17 @@ internal partial struct Metadata_1 } } - public bool HasInstantiation => !TestFlagWithMask(WFLAGS_LOW.GenericsMask, WFLAGS_LOW.GenericsMask_NonGeneric); + public ushort ComponentSizeBits => (ushort)(MTFlags & 0x0000ffff); // only meaningful if HasComponentSize is set + public bool HasComponentSize => GetFlag(WFLAGS_HIGH.HasComponentSize) != 0; + public bool IsInterface => GetFlag(WFLAGS_HIGH.Category_Mask) == WFLAGS_HIGH.Category_Interface; + public bool IsString => HasComponentSize && !IsArray && ComponentSizeBits == 2; + public bool IsArray => GetFlag(WFLAGS_HIGH.Category_Array_Mask) == WFLAGS_HIGH.Category_Array; + public bool IsStringOrArray => HasComponentSize; + public ushort ComponentSize => HasComponentSize ? ComponentSizeBits : (ushort)0; + public bool HasInstantiation => !TestFlagWithMask(WFLAGS_LOW.GenericsMask, WFLAGS_LOW.GenericsMask_NonGeneric); public bool ContainsGCPointers => GetFlag(WFLAGS_HIGH.ContainsGCPointers) != 0; + public bool IsDynamicStatics => GetFlag(WFLAGS2_ENUM.DynamicStatics) != 0; } [Flags] @@ -207,7 +206,7 @@ The contract additionally depends on the `EEClass` data descriptor. public bool IsFreeObjectMethodTable(MethodTableHandle methodTableHandle) => FreeObjectMethodTablePointer == methodTableHandle.Address; public bool IsString(MethodTableHandle methodTableHandle) => _methodTables[methodTableHandle.Address].Flags.IsString; - public bool ContainsPointers(MethodTableHandle methodTableHandle) => _methodTables[methodTableHandle.Address].Flags.ContainsPointers; + public bool ContainsGCPointers(MethodTableHandle methodTableHandle) => _methodTables[methodTableHandle.Address].Flags.ContainsGCPointers; public uint GetTypeDefToken(MethodTableHandle methodTableHandle) { @@ -221,5 +220,5 @@ The contract additionally depends on the `EEClass` data descriptor. public uint GetTypeDefTypeAttributes(MethodTableHandle methodTableHandle) => GetClassData(methodTableHandle).CorTypeAttr; - public bool IsDynamicStatics(MethodTableHandle methodTableHandle) => _methodTables[methodTableHandle.Address].Flags.GetFlag(WFLAGS2_ENUM.DynamicStatics) != 0; + public bool IsDynamicStatics(MethodTableHandle methodTableHandle) => _methodTables[methodTableHandle.Address].Flags.IsDynamicStatics; ``` diff --git a/src/native/managed/cdacreader/src/Contracts/Metadata_1.MethodTableFlags.cs b/src/native/managed/cdacreader/src/Contracts/Metadata_1.MethodTableFlags.cs index 10675967105272..376984155f2994 100644 --- a/src/native/managed/cdacreader/src/Contracts/Metadata_1.MethodTableFlags.cs +++ b/src/native/managed/cdacreader/src/Contracts/Metadata_1.MethodTableFlags.cs @@ -54,15 +54,8 @@ internal struct MethodTableFlags public WFLAGS_HIGH GetFlag(WFLAGS_HIGH mask) => FlagsHigh & mask; public WFLAGS2_ENUM GetFlag(WFLAGS2_ENUM mask) => (WFLAGS2_ENUM)MTFlags2 & mask; - public bool IsInterface => GetFlag(WFLAGS_HIGH.Category_Mask) == WFLAGS_HIGH.Category_Interface; - public bool IsString => HasComponentSize && !IsArray && RawGetComponentSize() == 2; - - public bool HasComponentSize => GetFlag(WFLAGS_HIGH.HasComponentSize) != 0; - public bool IsArray => GetFlag(WFLAGS_HIGH.Category_Array_Mask) == WFLAGS_HIGH.Category_Array; - - public bool IsStringOrArray => HasComponentSize; - public ushort RawGetComponentSize() => (ushort)(MTFlags & 0x0000ffff); + private ushort ComponentSizeBits => (ushort)(MTFlags & 0x0000ffff); // note: caller should check HasComponentSize private bool TestFlagWithMask(WFLAGS_LOW mask, WFLAGS_LOW flag) { @@ -81,10 +74,14 @@ private bool TestFlagWithMask(WFLAGS2_ENUM mask, WFLAGS2_ENUM flag) return ((WFLAGS2_ENUM)MTFlags2 & mask) == flag; } + public bool HasComponentSize => GetFlag(WFLAGS_HIGH.HasComponentSize) != 0; + public bool IsInterface => GetFlag(WFLAGS_HIGH.Category_Mask) == WFLAGS_HIGH.Category_Interface; + public bool IsString => HasComponentSize && !IsArray && ComponentSizeBits == 2; + public bool IsArray => GetFlag(WFLAGS_HIGH.Category_Array_Mask) == WFLAGS_HIGH.Category_Array; + public bool IsStringOrArray => HasComponentSize; + public ushort ComponentSize => HasComponentSize ? ComponentSizeBits : (ushort)0; public bool HasInstantiation => !TestFlagWithMask(WFLAGS_LOW.GenericsMask, WFLAGS_LOW.GenericsMask_NonGeneric); - public bool ContainsGCPointers => GetFlag(WFLAGS_HIGH.ContainsGCPointers) != 0; - public bool IsDynamicStatics => GetFlag(WFLAGS2_ENUM.DynamicStatics) != 0; } } diff --git a/src/native/managed/cdacreader/src/Contracts/Metadata_1.cs b/src/native/managed/cdacreader/src/Contracts/Metadata_1.cs index 49802a50c06206..25d0219c3863a7 100644 --- a/src/native/managed/cdacreader/src/Contracts/Metadata_1.cs +++ b/src/native/managed/cdacreader/src/Contracts/Metadata_1.cs @@ -104,11 +104,7 @@ public MethodTableHandle GetMethodTableHandle(TargetPointer methodTablePointer) public uint GetBaseSize(MethodTableHandle methodTableHandle) => _methodTables[methodTableHandle.Address].Flags.BaseSize; - private static uint GetComponentSize(MethodTable methodTable) - { - return methodTable.Flags.HasComponentSize ? methodTable.Flags.RawGetComponentSize() : 0u; - } - public uint GetComponentSize(MethodTableHandle methodTableHandle) => GetComponentSize(_methodTables[methodTableHandle.Address]); + public uint GetComponentSize(MethodTableHandle methodTableHandle) => _methodTables[methodTableHandle.Address].Flags.ComponentSize; private TargetPointer GetClassPointer(MethodTableHandle methodTableHandle) { From 9cf4c5a49f57df985213d718b333f69ccfebee72 Mon Sep 17 00:00:00 2001 From: Aleksey Kliger Date: Wed, 3 Jul 2024 10:19:13 -0400 Subject: [PATCH 57/62] rename Metadata -> RuntimeTypeSystem --- .../{Metadata.md => RuntimeTypeSystem.md} | 12 +++--- src/coreclr/debug/runtimeinfo/contracts.jsonc | 5 +-- .../cdacreader/src/Contracts/Registry.cs | 2 +- .../{Metadata.cs => RuntimeTypeSystem.cs} | 10 ++--- ...> RuntimeTypeSystem_1.MethodTableFlags.cs} | 2 +- ...cs => RuntimeTypeSystem_1.NonValidated.cs} | 2 +- .../{Metadata_1.cs => RuntimeTypeSystem_1.cs} | 6 +-- .../cdacreader/src/Legacy/SOSDacImpl.cs | 4 +- .../cdacreader/tests/MethodTableTests.cs | 37 +++++++++---------- 9 files changed, 39 insertions(+), 41 deletions(-) rename docs/design/datacontracts/{Metadata.md => RuntimeTypeSystem.md} (95%) rename src/native/managed/cdacreader/src/Contracts/{Metadata.cs => RuntimeTypeSystem.cs} (92%) rename src/native/managed/cdacreader/src/Contracts/{Metadata_1.MethodTableFlags.cs => RuntimeTypeSystem_1.MethodTableFlags.cs} (98%) rename src/native/managed/cdacreader/src/Contracts/{Metadata_1.NonValidated.cs => RuntimeTypeSystem_1.NonValidated.cs} (99%) rename src/native/managed/cdacreader/src/Contracts/{Metadata_1.cs => RuntimeTypeSystem_1.cs} (96%) diff --git a/docs/design/datacontracts/Metadata.md b/docs/design/datacontracts/RuntimeTypeSystem.md similarity index 95% rename from docs/design/datacontracts/Metadata.md rename to docs/design/datacontracts/RuntimeTypeSystem.md index c5653347c0574d..f8382ee4fdc599 100644 --- a/docs/design/datacontracts/Metadata.md +++ b/docs/design/datacontracts/RuntimeTypeSystem.md @@ -1,10 +1,10 @@ -# Contract Metadata +# Contract RuntimeTypeSystem -This contract is for exploring the properties of the metadata of values on the heap or on the stack in a .NET process. +This contract is for exploring the properties of the runtime types of values on the managed heap or on the stack in a .NET process. ## APIs of contract -A `MethodTable` is the runtime representation of the type information about a value. Given a `TargetPointer` address, the `Metadata` contract provides a `MethodTableHandle` for querying the `MethodTable`. +A `MethodTable` is the runtime representation of the type information about a value. Given a `TargetPointer` address, the `RuntimeTypeSystem` contract provides a `MethodTableHandle` for querying the `MethodTable`. ``` csharp struct MethodTableHandle @@ -51,7 +51,7 @@ struct MethodTableHandle The `MethodTable` inspection APIs are implemented in terms of the following flags on the runtime `MethodTable` structure: ``` csharp -internal partial struct Metadata_1 +internal partial struct RuntimeTypeSystem_1 { // The lower 16-bits of the MTFlags field are used for these flags, // if WFLAGS_HIGH.HasComponentSize is unset @@ -135,7 +135,7 @@ Internally the contract has a `MethodTable_1` struct that depends on the `Method ```csharp internal struct MethodTable_1 { - internal Metadata_1.MethodTableFlags Flags { get; } + internal RuntimeTypeSystem_1.MethodTableFlags Flags { get; } internal ushort NumInterfaces { get; } internal ushort NumVirtuals { get; } internal TargetPointer ParentMethodTable { get; } @@ -143,7 +143,7 @@ internal struct MethodTable_1 internal TargetPointer EEClassOrCanonMT { get; } internal MethodTable_1(Data.MethodTable data) { - Flags = new Metadata_1.MethodTableFlags + Flags = new RuntimeTypeSystem_1.MethodTableFlags { MTFlags = data.MTFlags, MTFlags2 = data.MTFlags2, diff --git a/src/coreclr/debug/runtimeinfo/contracts.jsonc b/src/coreclr/debug/runtimeinfo/contracts.jsonc index 36f802821580c5..44e7f914f9b075 100644 --- a/src/coreclr/debug/runtimeinfo/contracts.jsonc +++ b/src/coreclr/debug/runtimeinfo/contracts.jsonc @@ -10,7 +10,6 @@ // so to conditionally include contracts, put additional contracts in a separate file { "Exception": 1, - "Metadata": 1, - "Thread": 1, - "SOSBreakingChangeVersion": 1 // example contract: "runtime exports an SOS breaking change version global" + "RuntimeTypeSystem": 1, + "Thread": 1 } diff --git a/src/native/managed/cdacreader/src/Contracts/Registry.cs b/src/native/managed/cdacreader/src/Contracts/Registry.cs index 0e492c17ec18a0..9f7151adec432c 100644 --- a/src/native/managed/cdacreader/src/Contracts/Registry.cs +++ b/src/native/managed/cdacreader/src/Contracts/Registry.cs @@ -20,7 +20,7 @@ public Registry(Target target) public IException Exception => GetContract(); public IThread Thread => GetContract(); - public IMetadata Metadata => GetContract(); + public IRuntimeTypeSystem RuntimeTypeSystem => GetContract(); private T GetContract() where T : IContract { diff --git a/src/native/managed/cdacreader/src/Contracts/Metadata.cs b/src/native/managed/cdacreader/src/Contracts/RuntimeTypeSystem.cs similarity index 92% rename from src/native/managed/cdacreader/src/Contracts/Metadata.cs rename to src/native/managed/cdacreader/src/Contracts/RuntimeTypeSystem.cs index 03c70f5b4cc51b..8d37ca5441fcd3 100644 --- a/src/native/managed/cdacreader/src/Contracts/Metadata.cs +++ b/src/native/managed/cdacreader/src/Contracts/RuntimeTypeSystem.cs @@ -17,17 +17,17 @@ internal MethodTableHandle(TargetPointer address) internal TargetPointer Address { get; } } -internal interface IMetadata : IContract +internal interface IRuntimeTypeSystem : IContract { - static string IContract.Name => nameof(Metadata); + static string IContract.Name => nameof(RuntimeTypeSystem); static IContract IContract.Create(Target target, int version) { TargetPointer targetPointer = target.ReadGlobalPointer(Constants.Globals.FreeObjectMethodTable); TargetPointer freeObjectMethodTable = target.ReadPointer(targetPointer); return version switch { - 1 => new Metadata_1(target, freeObjectMethodTable), - _ => default(Metadata), + 1 => new RuntimeTypeSystem_1(target, freeObjectMethodTable), + _ => default(RuntimeTypeSystem), }; } @@ -61,7 +61,7 @@ static IContract IContract.Create(Target target, int version) #endregion MethodTable inspection APIs } -internal struct Metadata : IMetadata +internal struct RuntimeTypeSystem : IRuntimeTypeSystem { // Everything throws NotImplementedException } diff --git a/src/native/managed/cdacreader/src/Contracts/Metadata_1.MethodTableFlags.cs b/src/native/managed/cdacreader/src/Contracts/RuntimeTypeSystem_1.MethodTableFlags.cs similarity index 98% rename from src/native/managed/cdacreader/src/Contracts/Metadata_1.MethodTableFlags.cs rename to src/native/managed/cdacreader/src/Contracts/RuntimeTypeSystem_1.MethodTableFlags.cs index 376984155f2994..e6f98f0d8e2f08 100644 --- a/src/native/managed/cdacreader/src/Contracts/Metadata_1.MethodTableFlags.cs +++ b/src/native/managed/cdacreader/src/Contracts/RuntimeTypeSystem_1.MethodTableFlags.cs @@ -5,7 +5,7 @@ namespace Microsoft.Diagnostics.DataContractReader.Contracts; -internal partial struct Metadata_1 +internal partial struct RuntimeTypeSystem_1 { // The lower 16-bits of the MTFlags field are used for these flags, // if WFLAGS_HIGH.HasComponentSize is unset diff --git a/src/native/managed/cdacreader/src/Contracts/Metadata_1.NonValidated.cs b/src/native/managed/cdacreader/src/Contracts/RuntimeTypeSystem_1.NonValidated.cs similarity index 99% rename from src/native/managed/cdacreader/src/Contracts/Metadata_1.NonValidated.cs rename to src/native/managed/cdacreader/src/Contracts/RuntimeTypeSystem_1.NonValidated.cs index 4a521b948e7d65..ef392130e62316 100644 --- a/src/native/managed/cdacreader/src/Contracts/Metadata_1.NonValidated.cs +++ b/src/native/managed/cdacreader/src/Contracts/RuntimeTypeSystem_1.NonValidated.cs @@ -5,7 +5,7 @@ namespace Microsoft.Diagnostics.DataContractReader.Contracts; -internal partial struct Metadata_1 : IMetadata +internal partial struct RuntimeTypeSystem_1 : IRuntimeTypeSystem { // GC Heap corruption may create situations where a pointer value may point to garbage or even // to an unmapped memory region. diff --git a/src/native/managed/cdacreader/src/Contracts/Metadata_1.cs b/src/native/managed/cdacreader/src/Contracts/RuntimeTypeSystem_1.cs similarity index 96% rename from src/native/managed/cdacreader/src/Contracts/Metadata_1.cs rename to src/native/managed/cdacreader/src/Contracts/RuntimeTypeSystem_1.cs index 25d0219c3863a7..247050662ba9c6 100644 --- a/src/native/managed/cdacreader/src/Contracts/Metadata_1.cs +++ b/src/native/managed/cdacreader/src/Contracts/RuntimeTypeSystem_1.cs @@ -9,7 +9,7 @@ namespace Microsoft.Diagnostics.DataContractReader.Contracts; -internal partial struct Metadata_1 : IMetadata +internal partial struct RuntimeTypeSystem_1 : IRuntimeTypeSystem { private readonly Target _target; private readonly TargetPointer _freeObjectMethodTablePointer; @@ -53,7 +53,7 @@ internal enum EEClassOrCanonMTBits Mask = 1, } - internal Metadata_1(Target target, TargetPointer freeObjectMethodTablePointer) + internal RuntimeTypeSystem_1(Target target, TargetPointer freeObjectMethodTablePointer) { _target = target; _freeObjectMethodTablePointer = freeObjectMethodTablePointer; @@ -114,7 +114,7 @@ private TargetPointer GetClassPointer(MethodTableHandle methodTableHandle) case EEClassOrCanonMTBits.EEClass: return methodTable.EEClassOrCanonMT; case EEClassOrCanonMTBits.CanonMT: - TargetPointer canonMTPtr = new TargetPointer((ulong)methodTable.EEClassOrCanonMT & ~(ulong)Metadata_1.EEClassOrCanonMTBits.Mask); + TargetPointer canonMTPtr = new TargetPointer((ulong)methodTable.EEClassOrCanonMT & ~(ulong)RuntimeTypeSystem_1.EEClassOrCanonMTBits.Mask); MethodTableHandle canonMTHandle = GetMethodTableHandle(canonMTPtr); MethodTable canonMT = _methodTables[canonMTHandle.Address]; return canonMT.EEClassOrCanonMT; // canonical method table EEClassOrCanonMT is always EEClass diff --git a/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs b/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs index f58bfcdabf4493..240d98bf7d2952 100644 --- a/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs +++ b/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs @@ -89,7 +89,7 @@ public unsafe int GetMethodTableData(ulong mt, DacpMethodTableData* data) try { - Contracts.IMetadata contract = _target.Contracts.Metadata; + Contracts.IRuntimeTypeSystem contract = _target.Contracts.RuntimeTypeSystem; Contracts.MethodTableHandle methodTable = contract.GetMethodTableHandle(mt); DacpMethodTableData result = default; @@ -137,7 +137,7 @@ public unsafe int GetMethodTableForEEClass(ulong eeClassReallyCanonMT, ulong* va try { - Contracts.IMetadata contract = _target.Contracts.Metadata; + Contracts.IRuntimeTypeSystem contract = _target.Contracts.RuntimeTypeSystem; Contracts.MethodTableHandle methodTableHandle = contract.GetMethodTableHandle(eeClassReallyCanonMT); *value = methodTableHandle.Address; return HResults.S_OK; diff --git a/src/native/managed/cdacreader/tests/MethodTableTests.cs b/src/native/managed/cdacreader/tests/MethodTableTests.cs index 6a1bc5ddd332c8..c4538c9317f2af 100644 --- a/src/native/managed/cdacreader/tests/MethodTableTests.cs +++ b/src/native/managed/cdacreader/tests/MethodTableTests.cs @@ -4,7 +4,6 @@ using System; using System.Text; using Microsoft.Diagnostics.DataContractReader.Contracts; -using Microsoft.Diagnostics.DataContractReader.Data; using Xunit; namespace Microsoft.Diagnostics.DataContractReader.UnitTests; @@ -37,14 +36,14 @@ public unsafe class MethodTableTests } }; - private static readonly (DataType Type, Target.TypeInfo Info)[] MetadataTypes = + private static readonly (DataType Type, Target.TypeInfo Info)[] RTSTypes = [ (DataType.MethodTable, MethodTableTypeInfo), (DataType.EEClass, EEClassTypeInfo), ]; - private static readonly (string Name, ulong Value, string? Type)[] MetadataGlobals = + private static readonly (string Name, ulong Value, string? Type)[] RTSGlobals = [ (nameof(Constants.Globals.FreeObjectMethodTable), TestFreeObjectMethodTableGlobalAddress, null), ]; @@ -91,30 +90,30 @@ private static MockMemorySpace.Builder AddMethodTable(TargetTestHelpers targetTe // a delegate for adding more heap fragments to the context builder private delegate MockMemorySpace.Builder ConfigureContextBuilder(MockMemorySpace.Builder builder); - private static void MetadataContractHelper(MockTarget.Architecture arch, ConfigureContextBuilder configure, Action testCase) + private static void RTSContractHelper(MockTarget.Architecture arch, ConfigureContextBuilder configure, Action testCase) { TargetTestHelpers targetTestHelpers = new(arch); - string metadataTypesJson = TargetTestHelpers.MakeTypesJson(MetadataTypes); - string metadataGlobalsJson = TargetTestHelpers.MakeGlobalsJson(MetadataGlobals); + string metadataTypesJson = TargetTestHelpers.MakeTypesJson(RTSTypes); + string metadataGlobalsJson = TargetTestHelpers.MakeGlobalsJson(RTSGlobals); byte[] json = Encoding.UTF8.GetBytes($$""" { "version": 0, "baseline": "empty", "contracts": { - "Metadata": 1 + "{{nameof(Contracts.RuntimeTypeSystem)}}": 1 }, "types": { {{metadataTypesJson}} }, "globals": { {{metadataGlobalsJson}} } } """); Span descriptor = stackalloc byte[targetTestHelpers.ContractDescriptorSize]; - targetTestHelpers.ContractDescriptorFill(descriptor, json.Length, MetadataGlobals.Length); + targetTestHelpers.ContractDescriptorFill(descriptor, json.Length, RTSGlobals.Length); int pointerSize = targetTestHelpers.PointerSize; - Span pointerData = stackalloc byte[MetadataGlobals.Length * pointerSize]; - for (int i = 0; i < MetadataGlobals.Length; i++) + Span pointerData = stackalloc byte[RTSGlobals.Length * pointerSize]; + for (int i = 0; i < RTSGlobals.Length; i++) { - var (_, value, _) = MetadataGlobals[i]; + var (_, value, _) = RTSGlobals[i]; targetTestHelpers.WritePointer(pointerData.Slice(i * pointerSize), value); } @@ -145,11 +144,11 @@ private static void MetadataContractHelper(MockTarget.Architecture arch, Configu [Theory] [ClassData(typeof(MockTarget.StdArch))] - public void HasMetadataContract(MockTarget.Architecture arch) + public void HasRuntimeTypeSystemContract(MockTarget.Architecture arch) { - MetadataContractHelper(arch, default, (target) => + RTSContractHelper(arch, default, (target) => { - Contracts.IMetadata metadataContract = target.Contracts.Metadata; + Contracts.IRuntimeTypeSystem metadataContract = target.Contracts.RuntimeTypeSystem; Assert.NotNull(metadataContract); Contracts.MethodTableHandle handle = metadataContract.GetMethodTableHandle(TestFreeObjectMethodTableAddress); Assert.NotEqual(TargetPointer.Null, handle.Address); @@ -178,7 +177,7 @@ public void ValidateSystemObjectMethodTable(MockTarget.Architecture arch) TargetPointer systemObjectMethodTablePtr = new TargetPointer(SystemObjectMethodTableAddress); TargetPointer systemObjectEEClassPtr = new TargetPointer(SystemObjectEEClassAddress); TargetTestHelpers targetTestHelpers = new(arch); - MetadataContractHelper(arch, + RTSContractHelper(arch, (builder) => { builder = AddSystemObject(targetTestHelpers, builder, systemObjectMethodTablePtr, systemObjectEEClassPtr); @@ -186,7 +185,7 @@ public void ValidateSystemObjectMethodTable(MockTarget.Architecture arch) }, (target) => { - Contracts.IMetadata metadataContract = target.Contracts.Metadata; + Contracts.IRuntimeTypeSystem metadataContract = target.Contracts.RuntimeTypeSystem; Assert.NotNull(metadataContract); Contracts.MethodTableHandle systemObjectMethodTableHandle = metadataContract.GetMethodTableHandle(systemObjectMethodTablePtr); Assert.Equal(systemObjectMethodTablePtr.Value, systemObjectMethodTableHandle.Address.Value); @@ -208,7 +207,7 @@ public void ValidateSystemStringMethodTable(MockTarget.Architecture arch) TargetPointer systemStringMethodTablePtr = new TargetPointer(SystemStringMethodTableAddress); TargetPointer systemStringEEClassPtr = new TargetPointer(SystemStringEEClassAddress); TargetTestHelpers targetTestHelpers = new(arch); - MetadataContractHelper(arch, + RTSContractHelper(arch, (builder) => { builder = AddSystemObject(targetTestHelpers, builder, systemObjectMethodTablePtr, systemObjectEEClassPtr); @@ -216,7 +215,7 @@ public void ValidateSystemStringMethodTable(MockTarget.Architecture arch) const int numMethods = 37; // Arbitrary. Not trying to exactly match the real System.String const int numInterfaces = 8; // Arbitrary const int numVirtuals = 3; // at least as many as System.Object - uint mtflags = (uint)Metadata_1.WFLAGS_HIGH.HasComponentSize | /*componentSize: */2; + uint mtflags = (uint)RuntimeTypeSystem_1.WFLAGS_HIGH.HasComponentSize | /*componentSize: */2; builder = AddEEClass(targetTestHelpers, builder, systemStringEEClassPtr, "System.String", systemStringMethodTablePtr, attr: (uint)typeAttributes, numMethods: numMethods); builder = AddMethodTable(targetTestHelpers, builder, systemStringMethodTablePtr, "System.String", systemStringEEClassPtr, mtflags: mtflags, mtflags2: default, baseSize: targetTestHelpers.StringBaseSize, @@ -225,7 +224,7 @@ public void ValidateSystemStringMethodTable(MockTarget.Architecture arch) }, (target) => { - Contracts.IMetadata metadataContract = target.Contracts.Metadata; + Contracts.IRuntimeTypeSystem metadataContract = target.Contracts.RuntimeTypeSystem; Assert.NotNull(metadataContract); Contracts.MethodTableHandle systemStringMethodTableHandle = metadataContract.GetMethodTableHandle(systemStringMethodTablePtr); Assert.Equal(systemStringMethodTablePtr.Value, systemStringMethodTableHandle.Address.Value); From 181484824f02323816dcdec0f1d1d83fa6106859 Mon Sep 17 00:00:00 2001 From: Aleksey Kliger Date: Wed, 3 Jul 2024 11:49:32 -0400 Subject: [PATCH 58/62] add validation failure test; change validation to throw InvalidOperationException --- .../src/Contracts/RuntimeTypeSystem_1.cs | 2 +- .../cdacreader/tests/MethodTableTests.cs | 29 +++++++++++++++++++ 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/src/native/managed/cdacreader/src/Contracts/RuntimeTypeSystem_1.cs b/src/native/managed/cdacreader/src/Contracts/RuntimeTypeSystem_1.cs index 247050662ba9c6..8534c12264233f 100644 --- a/src/native/managed/cdacreader/src/Contracts/RuntimeTypeSystem_1.cs +++ b/src/native/managed/cdacreader/src/Contracts/RuntimeTypeSystem_1.cs @@ -92,7 +92,7 @@ public MethodTableHandle GetMethodTableHandle(TargetPointer methodTablePointer) if (!ValidateMethodTablePointer(nonvalidatedMethodTable)) { - throw new ArgumentException("Invalid method table pointer"); + throw new InvalidOperationException("Invalid method table pointer"); } // ok, we validated it, cache the data and add the MethodTable_1 struct to the dictionary Data.MethodTable trustedMethodTableData = _target.ProcessedData.GetOrAdd(methodTablePointer); diff --git a/src/native/managed/cdacreader/tests/MethodTableTests.cs b/src/native/managed/cdacreader/tests/MethodTableTests.cs index c4538c9317f2af..e3f38a88553cb4 100644 --- a/src/native/managed/cdacreader/tests/MethodTableTests.cs +++ b/src/native/managed/cdacreader/tests/MethodTableTests.cs @@ -232,4 +232,33 @@ public void ValidateSystemStringMethodTable(MockTarget.Architecture arch) Assert.True(metadataContract.IsString(systemStringMethodTableHandle)); }); } + + [Theory] + [ClassData(typeof(MockTarget.StdArch))] + public void MethodTableEEClassInvalidThrows(MockTarget.Architecture arch) + { + TargetTestHelpers targetTestHelpers = new(arch); + const ulong SystemObjectMethodTableAddress = 0x00000000_7c000010; + const ulong SystemObjectEEClassAddress = 0x00000000_7c0000d0; + TargetPointer systemObjectMethodTablePtr = new TargetPointer(SystemObjectMethodTableAddress); + TargetPointer systemObjectEEClassPtr = new TargetPointer(SystemObjectEEClassAddress); + + const ulong badMethodTableAddress = 0x00000000_4a000100; // place a normal-looking MethodTable here + const ulong badMethodTableEEClassAddress = 0x00000010_afafafafa0; // bad address + TargetPointer badMethodTablePtr = new TargetPointer(badMethodTableAddress); + TargetPointer badMethodTableEEClassPtr = new TargetPointer(badMethodTableEEClassAddress); + RTSContractHelper(arch, + (builder) => + { + builder = AddSystemObject(targetTestHelpers, builder, systemObjectMethodTablePtr, systemObjectEEClassPtr); + builder = AddMethodTable(targetTestHelpers, builder, badMethodTablePtr, "Bad MethodTable", badMethodTableEEClassPtr, mtflags: default, mtflags2: default, baseSize: targetTestHelpers.ObjectBaseSize, module: TargetPointer.Null, parentMethodTable: systemObjectMethodTablePtr, numInterfaces: 0, numVirtuals: 3); + return builder; + }, + (target) => + { + Contracts.IRuntimeTypeSystem metadataContract = target.Contracts.RuntimeTypeSystem; + Assert.NotNull(metadataContract); + Assert.Throws(() => metadataContract.GetMethodTableHandle(badMethodTablePtr)); + }); + } } From a0989fabcdaed80af96b26b5d4276f18f5ba06a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aleksey=20Kliger=20=28=CE=BBgeek=29?= Date: Wed, 3 Jul 2024 15:10:18 -0400 Subject: [PATCH 59/62] Update src/native/managed/cdacreader/src/Contracts/RuntimeTypeSystem_1.cs Co-authored-by: Jan Kotas --- .../managed/cdacreader/src/Contracts/RuntimeTypeSystem_1.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/native/managed/cdacreader/src/Contracts/RuntimeTypeSystem_1.cs b/src/native/managed/cdacreader/src/Contracts/RuntimeTypeSystem_1.cs index 8534c12264233f..00a568b4224886 100644 --- a/src/native/managed/cdacreader/src/Contracts/RuntimeTypeSystem_1.cs +++ b/src/native/managed/cdacreader/src/Contracts/RuntimeTypeSystem_1.cs @@ -14,7 +14,7 @@ internal partial struct RuntimeTypeSystem_1 : IRuntimeTypeSystem private readonly Target _target; private readonly TargetPointer _freeObjectMethodTablePointer; - // TODO(cdac): we mutate this dictionary - copies of the Metadata_1 struct share this instance. + // TODO(cdac): we mutate this dictionary - copies of the RuntimeTypeSystem_1 struct share this instance. // If we need to invalidate our view of memory, we shoudl clear this dictionary. private readonly Dictionary _methodTables = new(); From 617bf620825ffe4b7e7f27ccbab325e4357c395e Mon Sep 17 00:00:00 2001 From: Aleksey Kliger Date: Wed, 3 Jul 2024 15:25:55 -0400 Subject: [PATCH 60/62] spellcheck --- .../managed/cdacreader/src/Contracts/RuntimeTypeSystem_1.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/native/managed/cdacreader/src/Contracts/RuntimeTypeSystem_1.cs b/src/native/managed/cdacreader/src/Contracts/RuntimeTypeSystem_1.cs index 8534c12264233f..b404d619c0dbe5 100644 --- a/src/native/managed/cdacreader/src/Contracts/RuntimeTypeSystem_1.cs +++ b/src/native/managed/cdacreader/src/Contracts/RuntimeTypeSystem_1.cs @@ -15,7 +15,7 @@ internal partial struct RuntimeTypeSystem_1 : IRuntimeTypeSystem private readonly TargetPointer _freeObjectMethodTablePointer; // TODO(cdac): we mutate this dictionary - copies of the Metadata_1 struct share this instance. - // If we need to invalidate our view of memory, we shoudl clear this dictionary. + // If we need to invalidate our view of memory, we should clear this dictionary. private readonly Dictionary _methodTables = new(); From 1ab4f0803adba9e16a97106cba82686889c06204 Mon Sep 17 00:00:00 2001 From: Aleksey Kliger Date: Wed, 3 Jul 2024 15:53:10 -0400 Subject: [PATCH 61/62] Add a generic instance test --- .../cdacreader/tests/MethodTableTests.cs | 54 +++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/src/native/managed/cdacreader/tests/MethodTableTests.cs b/src/native/managed/cdacreader/tests/MethodTableTests.cs index e3f38a88553cb4..1b28464b25f91f 100644 --- a/src/native/managed/cdacreader/tests/MethodTableTests.cs +++ b/src/native/managed/cdacreader/tests/MethodTableTests.cs @@ -261,4 +261,58 @@ public void MethodTableEEClassInvalidThrows(MockTarget.Architecture arch) Assert.Throws(() => metadataContract.GetMethodTableHandle(badMethodTablePtr)); }); } + + [Theory] + [ClassData(typeof(MockTarget.StdArch))] + public void MethodTableGenericInstValid(MockTarget.Architecture arch) + { + TargetTestHelpers targetTestHelpers = new(arch); + const ulong SystemObjectMethodTableAddress = 0x00000000_7c000010; + const ulong SystemObjectEEClassAddress = 0x00000000_7c0000d0; + TargetPointer systemObjectMethodTablePtr = new TargetPointer(SystemObjectMethodTableAddress); + TargetPointer systemObjectEEClassPtr = new TargetPointer(SystemObjectEEClassAddress); + + const ulong genericDefinitionMethodTableAddress = 0x00000000_5d004040; + const ulong genericDefinitionEEClassAddress = 0x00000000_5d0040c0; + TargetPointer genericDefinitionMethodTablePtr = new TargetPointer(genericDefinitionMethodTableAddress); + TargetPointer genericDefinitionEEClassPtr = new TargetPointer(genericDefinitionEEClassAddress); + + const ulong genericInstanceMethodTableAddress = 0x00000000_330000a0; + TargetPointer genericInstanceMethodTablePtr = new TargetPointer(genericInstanceMethodTableAddress); + + const int numMethods = 17; + + RTSContractHelper(arch, + (builder) => + { + builder = AddSystemObject(targetTestHelpers, builder, systemObjectMethodTablePtr, systemObjectEEClassPtr); + + System.Reflection.TypeAttributes typeAttributes = System.Reflection.TypeAttributes.Public | System.Reflection.TypeAttributes.Class; + const int numInterfaces = 0; + const int numVirtuals = 3; + const uint gtd_mtflags = 0x00000030; // TODO: GenericsMask_TypicalInst + builder = AddEEClass(targetTestHelpers, builder, genericDefinitionEEClassPtr, "EEClass GenericDefinition", genericDefinitionMethodTablePtr, attr: (uint)typeAttributes, numMethods: numMethods); + builder = AddMethodTable(targetTestHelpers, builder, genericDefinitionMethodTablePtr, "MethodTable GenericDefinition", genericDefinitionEEClassPtr, + mtflags: gtd_mtflags, mtflags2: default, baseSize: targetTestHelpers.ObjectBaseSize, + module: TargetPointer.Null, parentMethodTable: systemObjectMethodTablePtr, numInterfaces: numInterfaces, numVirtuals: numVirtuals); + + const uint ginst_mtflags = 0x00000010; // TODO: GenericsMask_GenericInst + TargetPointer ginstCanonMT = new TargetPointer(genericDefinitionMethodTablePtr.Value | (ulong)1); + builder = AddMethodTable(targetTestHelpers, builder, genericInstanceMethodTablePtr, "MethodTable GenericInstance", eeClassOrCanonMT: ginstCanonMT, + mtflags: ginst_mtflags, mtflags2: default, baseSize: targetTestHelpers.ObjectBaseSize, + module: TargetPointer.Null, parentMethodTable: genericDefinitionMethodTablePtr, numInterfaces: numInterfaces, numVirtuals: numVirtuals); + + return builder; + }, + (target) => + { + Contracts.IRuntimeTypeSystem metadataContract = target.Contracts.RuntimeTypeSystem; + Assert.NotNull(metadataContract); + Contracts.MethodTableHandle genericInstanceMethodTableHandle = metadataContract.GetMethodTableHandle(genericInstanceMethodTablePtr); + Assert.Equal(genericInstanceMethodTablePtr.Value, genericInstanceMethodTableHandle.Address.Value); + Assert.False(metadataContract.IsFreeObjectMethodTable(genericInstanceMethodTableHandle)); + Assert.False(metadataContract.IsString(genericInstanceMethodTableHandle)); + Assert.Equal(numMethods, metadataContract.GetNumMethods(genericInstanceMethodTableHandle)); + }); + } } From ee3a362b975d49b3ead6c075090d4cd6285f0c83 Mon Sep 17 00:00:00 2001 From: Aleksey Kliger Date: Wed, 3 Jul 2024 16:16:39 -0400 Subject: [PATCH 62/62] add array instance test --- .../cdacreader/tests/MethodTableTests.cs | 60 ++++++++++++++++++- 1 file changed, 59 insertions(+), 1 deletion(-) diff --git a/src/native/managed/cdacreader/tests/MethodTableTests.cs b/src/native/managed/cdacreader/tests/MethodTableTests.cs index 1b28464b25f91f..dd451fa075f6ae 100644 --- a/src/native/managed/cdacreader/tests/MethodTableTests.cs +++ b/src/native/managed/cdacreader/tests/MethodTableTests.cs @@ -264,7 +264,7 @@ public void MethodTableEEClassInvalidThrows(MockTarget.Architecture arch) [Theory] [ClassData(typeof(MockTarget.StdArch))] - public void MethodTableGenericInstValid(MockTarget.Architecture arch) + public void ValidateGenericInstMethodTable(MockTarget.Architecture arch) { TargetTestHelpers targetTestHelpers = new(arch); const ulong SystemObjectMethodTableAddress = 0x00000000_7c000010; @@ -315,4 +315,62 @@ public void MethodTableGenericInstValid(MockTarget.Architecture arch) Assert.Equal(numMethods, metadataContract.GetNumMethods(genericInstanceMethodTableHandle)); }); } + + [Theory] + [ClassData(typeof(MockTarget.StdArch))] + public void ValidateArrayInstMethodTable(MockTarget.Architecture arch) + { + TargetTestHelpers targetTestHelpers = new(arch); + const ulong SystemObjectMethodTableAddress = 0x00000000_7c000010; + const ulong SystemObjectEEClassAddress = 0x00000000_7c0000d0; + TargetPointer systemObjectMethodTablePtr = new TargetPointer(SystemObjectMethodTableAddress); + TargetPointer systemObjectEEClassPtr = new TargetPointer(SystemObjectEEClassAddress); + + const ulong SystemArrayMethodTableAddress = 0x00000000_7c00a010; + const ulong SystemArrayEEClassAddress = 0x00000000_7c00a0d0; + TargetPointer systemArrayMethodTablePtr = new TargetPointer(SystemArrayMethodTableAddress); + TargetPointer systemArrayEEClassPtr = new TargetPointer(SystemArrayEEClassAddress); + + const ulong arrayInstanceMethodTableAddress = 0x00000000_330000a0; + const ulong arrayInstanceEEClassAddress = 0x00000000_330001d0; + TargetPointer arrayInstanceMethodTablePtr = new TargetPointer(arrayInstanceMethodTableAddress); + TargetPointer arrayInstanceEEClassPtr = new TargetPointer(arrayInstanceEEClassAddress); + + const uint arrayInstanceComponentSize = 392; + + RTSContractHelper(arch, + (builder) => + { + builder = AddSystemObject(targetTestHelpers, builder, systemObjectMethodTablePtr, systemObjectEEClassPtr); + const ushort systemArrayNumInterfaces = 4; + const ushort systemArrayNumMethods = 37; // Arbitrary. Not trying to exactly match the real System.Array + const uint systemArrayCorTypeAttr = (uint)(System.Reflection.TypeAttributes.Public | System.Reflection.TypeAttributes.Class); + + builder = AddEEClass(targetTestHelpers, builder, systemArrayEEClassPtr, "EEClass System.Array", systemArrayMethodTablePtr, attr: systemArrayCorTypeAttr, numMethods: systemArrayNumMethods); + builder = AddMethodTable(targetTestHelpers, builder, systemArrayMethodTablePtr, "MethodTable System.Array", systemArrayEEClassPtr, + mtflags: default, mtflags2: default, baseSize: targetTestHelpers.ObjectBaseSize, + module: TargetPointer.Null, parentMethodTable: systemObjectMethodTablePtr, numInterfaces: systemArrayNumInterfaces, numVirtuals: 3); + + const uint arrayInst_mtflags = (uint)(RuntimeTypeSystem_1.WFLAGS_HIGH.HasComponentSize | RuntimeTypeSystem_1.WFLAGS_HIGH.Category_Array) | arrayInstanceComponentSize; + const uint arrayInstCorTypeAttr = (uint)(System.Reflection.TypeAttributes.Public | System.Reflection.TypeAttributes.Class | System.Reflection.TypeAttributes.Sealed); + + builder = AddEEClass(targetTestHelpers, builder, arrayInstanceEEClassPtr, "EEClass ArrayInstance", arrayInstanceMethodTablePtr, attr: arrayInstCorTypeAttr, numMethods: systemArrayNumMethods); + builder = AddMethodTable(targetTestHelpers, builder, arrayInstanceMethodTablePtr, "MethodTable ArrayInstance", arrayInstanceEEClassPtr, + mtflags: arrayInst_mtflags, mtflags2: default, baseSize: targetTestHelpers.ObjectBaseSize, + module: TargetPointer.Null, parentMethodTable: systemArrayMethodTablePtr, numInterfaces: systemArrayNumInterfaces, numVirtuals: 3); + + return builder; + }, + (target) => + { + Contracts.IRuntimeTypeSystem metadataContract = target.Contracts.RuntimeTypeSystem; + Assert.NotNull(metadataContract); + Contracts.MethodTableHandle arrayInstanceMethodTableHandle = metadataContract.GetMethodTableHandle(arrayInstanceMethodTablePtr); + Assert.Equal(arrayInstanceMethodTablePtr.Value, arrayInstanceMethodTableHandle.Address.Value); + Assert.False(metadataContract.IsFreeObjectMethodTable(arrayInstanceMethodTableHandle)); + Assert.False(metadataContract.IsString(arrayInstanceMethodTableHandle)); + Assert.Equal(arrayInstanceComponentSize, metadataContract.GetComponentSize(arrayInstanceMethodTableHandle)); + }); + + } }