diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeTypeSystemFactory.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeTypeSystemFactory.cs index 8590f3e11a7cf..1f1ab288793a5 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeTypeSystemFactory.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeTypeSystemFactory.cs @@ -14,7 +14,7 @@ IRuntimeTypeSystem IContractFactory.CreateContract(Target ta ulong methodDescAlignment = target.ReadGlobal(Constants.Globals.MethodDescAlignment); return version switch { - 1 => new RuntimeTypeSystem_1(target, freeObjectMethodTable, methodDescAlignment), + 1 => new RuntimeTypeSystem_1(target, new RuntimeTypeSystemHelpers.TypeValidation(target), freeObjectMethodTable, methodDescAlignment), _ => default(RuntimeTypeSystem), }; } diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeTypeSystem_1.MethodTableFlags.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeTypeSystem_1.MethodTableFlags.cs deleted file mode 100644 index 6e02d241b994c..0000000000000 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeTypeSystem_1.MethodTableFlags.cs +++ /dev/null @@ -1,95 +0,0 @@ -// 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 RuntimeTypeSystem_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 - { - GenericsMask = 0x00000030, - GenericsMask_NonGeneric = 0x00000000, // no instantiation - GenericsMask_TypicalInstantiation = 0x00000030, // the type instantiated at its formal parameters, e.g. List - - StringArrayValues = - GenericsMask_NonGeneric | - 0, - } - - // Upper bits of MTFlags - [Flags] - internal enum WFLAGS_HIGH : uint - { - Category_Mask = 0x000F0000, - Category_Array = 0x00080000, - Category_IfArrayThenSzArray = 0x00020000, - Category_Array_Mask = 0x000C0000, - Category_ElementType_Mask = 0x000E0000, - Category_ValueType = 0x00040000, - Category_Nullable = 0x00050000, - Category_PrimitiveValueType = 0x00060000, - Category_TruePrimitive = 0x00070000, - Category_Interface = 0x000C0000, - 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] - internal enum WFLAGS2_ENUM : uint - { - DynamicStatics = 0x0002, - } - - internal struct MethodTableFlags - { - public uint MTFlags { get; init; } - public uint MTFlags2 { get; init; } - public uint BaseSize { get; init; } - - private const int MTFlags2TypeDefRidShift = 8; - 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)MTFlags2 & mask; - - private ushort ComponentSizeBits => (ushort)(MTFlags & 0x0000ffff); // note: caller should check HasComponentSize - - private bool TestFlagWithMask(WFLAGS_LOW mask, WFLAGS_LOW flag) - { - if (IsStringOrArray) - { - return (WFLAGS_LOW.StringArrayValues & mask) == flag; - } - else - { - return (FlagsLow & mask) == flag; - } - } - - 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; - public bool IsGenericTypeDefinition => TestFlagWithMask(WFLAGS_LOW.GenericsMask, WFLAGS_LOW.GenericsMask_TypicalInstantiation); - } -} diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeTypeSystem_1.NonValidated.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeTypeSystem_1.NonValidated.cs index b319f728eaf91..958767b7a1730 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeTypeSystem_1.NonValidated.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeTypeSystem_1.NonValidated.cs @@ -15,77 +15,6 @@ internal partial struct RuntimeTypeSystem_1 : IRuntimeTypeSystem 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 struct MethodDesc { private readonly Target _target; @@ -106,113 +35,6 @@ internal MethodDesc(Target target, Data.MethodDesc desc, Data.MethodDescChunk ch internal bool HasNonVtableSlot => HasFlag(MethodDescFlags.HasNonVtableSlot); } - 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; - } } private TargetPointer GetMethodDescChunkPointerThrowing(TargetPointer methodDescPointer, Data.MethodDesc umd) diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeTypeSystem_1.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeTypeSystem_1.cs index 654651ce20d5e..32b08e80ac9a4 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeTypeSystem_1.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeTypeSystem_1.cs @@ -5,7 +5,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.Reflection.Metadata.Ecma335; -using Microsoft.Diagnostics.DataContractReader.Contracts.RuntimeTypeSystem_1_NS; +using Microsoft.Diagnostics.DataContractReader.RuntimeTypeSystemHelpers; using Microsoft.Diagnostics.DataContractReader.Data; namespace Microsoft.Diagnostics.DataContractReader.Contracts; @@ -15,6 +15,7 @@ internal partial struct RuntimeTypeSystem_1 : IRuntimeTypeSystem private readonly Target _target; private readonly TargetPointer _freeObjectMethodTablePointer; private readonly ulong _methodDescAlignment; + private readonly TypeValidation _typeValidation; // 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 should clear this dictionary. @@ -24,7 +25,7 @@ internal partial struct RuntimeTypeSystem_1 : IRuntimeTypeSystem internal struct MethodTable { - internal MethodTableFlags Flags { get; } + internal MethodTableFlags_1 Flags { get; } internal ushort NumInterfaces { get; } internal ushort NumVirtuals { get; } internal TargetPointer ParentMethodTable { get; } @@ -33,7 +34,7 @@ internal struct MethodTable internal TargetPointer PerInstInfo { get; } internal MethodTable(Data.MethodTable data) { - Flags = new MethodTableFlags + Flags = new MethodTableFlags_1 { MTFlags = data.MTFlags, MTFlags2 = data.MTFlags2, @@ -48,17 +49,8 @@ internal MethodTable(Data.MethodTable data) } // this MethodTable is a canonical MethodTable if its EEClassOrCanonMT is an EEClass - internal bool IsCanonMT => GetEEClassOrCanonMTBits(EEClassOrCanonMT) == EEClassOrCanonMTBits.EEClass; - } + internal bool IsCanonMT => MethodTableFlags_1.GetEEClassOrCanonMTBits(EEClassOrCanonMT) == MethodTableFlags_1.EEClassOrCanonMTBits.EEClass; - // Low order bit of EEClassOrCanonMT. - // See MethodTable::LowBits UNION_EECLASS / UNION_METHODABLE - [Flags] - internal enum EEClassOrCanonMTBits - { - EEClass = 0, - CanonMT = 1, - Mask = 1, } // Low order bits of TypeHandle address. @@ -222,11 +214,12 @@ private StoredSigMethodDesc(Target target, TargetPointer methodDescPointer) } } - internal RuntimeTypeSystem_1(Target target, TargetPointer freeObjectMethodTablePointer, ulong methodDescAlignment) + internal RuntimeTypeSystem_1(Target target, TypeValidation typeValidation, TargetPointer freeObjectMethodTablePointer, ulong methodDescAlignment) { _target = target; _freeObjectMethodTablePointer = freeObjectMethodTablePointer; _methodDescAlignment = methodDescAlignment; + _typeValidation = typeValidation; } internal TargetPointer FreeObjectMethodTablePointer => _freeObjectMethodTablePointer; @@ -276,9 +269,7 @@ public TypeHandle GetTypeHandle(TargetPointer typeHandlePointer) } // Otherwse, get ready to validate - NonValidated.MethodTable nonvalidatedMethodTable = NonValidated.GetMethodTableData(_target, methodTablePointer); - - if (!ValidateMethodTablePointer(nonvalidatedMethodTable)) + if (!_typeValidation.TryValidateMethodTablePointer(methodTablePointer)) { throw new InvalidOperationException("Invalid method table pointer"); } @@ -296,12 +287,12 @@ public TypeHandle GetTypeHandle(TargetPointer typeHandlePointer) private TargetPointer GetClassPointer(TypeHandle typeHandle) { MethodTable methodTable = _methodTables[typeHandle.Address]; - switch (GetEEClassOrCanonMTBits(methodTable.EEClassOrCanonMT)) + switch (MethodTableFlags_1.GetEEClassOrCanonMTBits(methodTable.EEClassOrCanonMT)) { - case EEClassOrCanonMTBits.EEClass: + case MethodTableFlags_1.EEClassOrCanonMTBits.EEClass: return methodTable.EEClassOrCanonMT; - case EEClassOrCanonMTBits.CanonMT: - TargetPointer canonMTPtr = new TargetPointer((ulong)methodTable.EEClassOrCanonMT & ~(ulong)RuntimeTypeSystem_1.EEClassOrCanonMTBits.Mask); + case MethodTableFlags_1.EEClassOrCanonMTBits.CanonMT: + TargetPointer canonMTPtr =MethodTableFlags_1.UntagEEClassOrCanonMT(methodTable.EEClassOrCanonMT); TypeHandle canonMTHandle = GetTypeHandle(canonMTPtr); MethodTable canonMT = _methodTables[canonMTHandle.Address]; return canonMT.EEClassOrCanonMT; // canonical method table EEClassOrCanonMT is always EEClass @@ -440,17 +431,17 @@ public CorElementType GetSignatureCorElementType(TypeHandle typeHandle) { MethodTable methodTable = _methodTables[typeHandle.Address]; - switch (methodTable.Flags.GetFlag(WFLAGS_HIGH.Category_Mask)) + switch (methodTable.Flags.GetFlag(MethodTableFlags_1.WFLAGS_HIGH.Category_Mask)) { - case WFLAGS_HIGH.Category_Array: + case MethodTableFlags_1.WFLAGS_HIGH.Category_Array: return CorElementType.Array; - case WFLAGS_HIGH.Category_Array | WFLAGS_HIGH.Category_IfArrayThenSzArray: + case MethodTableFlags_1.WFLAGS_HIGH.Category_Array | MethodTableFlags_1.WFLAGS_HIGH.Category_IfArrayThenSzArray: return CorElementType.SzArray; - case WFLAGS_HIGH.Category_ValueType: - case WFLAGS_HIGH.Category_Nullable: - case WFLAGS_HIGH.Category_PrimitiveValueType: + case MethodTableFlags_1.WFLAGS_HIGH.Category_ValueType: + case MethodTableFlags_1.WFLAGS_HIGH.Category_Nullable: + case MethodTableFlags_1.WFLAGS_HIGH.Category_PrimitiveValueType: return CorElementType.ValueType; - case WFLAGS_HIGH.Category_TruePrimitive: + case MethodTableFlags_1.WFLAGS_HIGH.Category_TruePrimitive: return (CorElementType)GetClassData(typeHandle).InternalCorElementType; default: return CorElementType.Class; @@ -472,14 +463,14 @@ public bool IsArray(TypeHandle typeHandle, out uint rank) { MethodTable methodTable = _methodTables[typeHandle.Address]; - switch (methodTable.Flags.GetFlag(WFLAGS_HIGH.Category_Mask)) + switch (methodTable.Flags.GetFlag(MethodTableFlags_1.WFLAGS_HIGH.Category_Mask)) { - case WFLAGS_HIGH.Category_Array: + case MethodTableFlags_1.WFLAGS_HIGH.Category_Array: TargetPointer clsPtr = GetClassPointer(typeHandle); rank = _target.ProcessedData.GetOrAdd(clsPtr).Rank; return true; - case WFLAGS_HIGH.Category_Array | WFLAGS_HIGH.Category_IfArrayThenSzArray: + case MethodTableFlags_1.WFLAGS_HIGH.Category_Array | MethodTableFlags_1.WFLAGS_HIGH.Category_IfArrayThenSzArray: rank = 1; return true; } diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeTypeSystem_1_Helpers.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/RuntimeTypeSystemHelpers/ExtensionMethods.cs similarity index 76% rename from src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeTypeSystem_1_Helpers.cs rename to src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/RuntimeTypeSystemHelpers/ExtensionMethods.cs index af1e23df461b9..2dd9a6dde7e94 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeTypeSystem_1_Helpers.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/RuntimeTypeSystemHelpers/ExtensionMethods.cs @@ -1,9 +1,11 @@ -// Licensed to the .NET Foundation under one or more agreements. +// 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.Contracts.RuntimeTypeSystem_1_NS; +using Microsoft.Diagnostics.DataContractReader.Contracts; -internal static class RuntimeTypeSystem_1_Helpers +namespace Microsoft.Diagnostics.DataContractReader.RuntimeTypeSystemHelpers; + +internal static class ExtensionMethods { public static bool IsTypeDesc(this TypeHandle type) { diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/RuntimeTypeSystemHelpers/MethodTableFlags_1.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/RuntimeTypeSystemHelpers/MethodTableFlags_1.cs new file mode 100644 index 0000000000000..0998d5c22c5d7 --- /dev/null +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/RuntimeTypeSystemHelpers/MethodTableFlags_1.cs @@ -0,0 +1,117 @@ +// 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.RuntimeTypeSystemHelpers; + +internal struct MethodTableFlags_1 +{ + // Low order bit of EEClassOrCanonMT. + // See MethodTable::LowBits UNION_EECLASS / UNION_METHODABLE + [Flags] + internal enum EEClassOrCanonMTBits + { + EEClass = 0, + CanonMT = 1, + Mask = 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 + { + GenericsMask = 0x00000030, + GenericsMask_NonGeneric = 0x00000000, // no instantiation + GenericsMask_TypicalInstantiation = 0x00000030, // the type instantiated at its formal parameters, e.g. List + + StringArrayValues = + GenericsMask_NonGeneric | + 0, + } + + // Upper bits of MTFlags + [Flags] + internal enum WFLAGS_HIGH : uint + { + Category_Mask = 0x000F0000, + Category_Array = 0x00080000, + Category_IfArrayThenSzArray = 0x00020000, + Category_Array_Mask = 0x000C0000, + Category_ElementType_Mask = 0x000E0000, + Category_ValueType = 0x00040000, + Category_Nullable = 0x00050000, + Category_PrimitiveValueType = 0x00060000, + Category_TruePrimitive = 0x00070000, + Category_Interface = 0x000C0000, + 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] + internal enum WFLAGS2_ENUM : uint + { + DynamicStatics = 0x0002, + } + + public uint MTFlags { get; init; } + public uint MTFlags2 { get; init; } + public uint BaseSize { get; init; } + + private const int MTFlags2TypeDefRidShift = 8; + 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)MTFlags2 & mask; + + private ushort ComponentSizeBits => (ushort)(MTFlags & 0x0000ffff); // note: caller should check HasComponentSize + + private bool TestFlagWithMask(WFLAGS_LOW mask, WFLAGS_LOW flag) + { + if (IsStringOrArray) + { + return (WFLAGS_LOW.StringArrayValues & mask) == flag; + } + else + { + return (FlagsLow & mask) == flag; + } + } + + 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; + public bool IsGenericTypeDefinition => TestFlagWithMask(WFLAGS_LOW.GenericsMask, WFLAGS_LOW.GenericsMask_TypicalInstantiation); + + internal static EEClassOrCanonMTBits GetEEClassOrCanonMTBits(TargetPointer eeClassOrCanonMTPtr) + { + return (EEClassOrCanonMTBits)(eeClassOrCanonMTPtr & (ulong)EEClassOrCanonMTBits.Mask); + } + + internal static TargetPointer UntagEEClassOrCanonMT(TargetPointer eeClassOrCanonMTPtr) + { + return eeClassOrCanonMTPtr & ~(ulong)EEClassOrCanonMTBits.Mask; + } + + internal static TargetPointer TagEEClassOrCanonMT(TargetPointer eeClassOrCanonMTPtr, EEClassOrCanonMTBits tag) + { + return (eeClassOrCanonMTPtr & ~(ulong)EEClassOrCanonMTBits.Mask) | (ulong)tag; + } + +} diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/RuntimeTypeSystemHelpers/TypeValidation.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/RuntimeTypeSystemHelpers/TypeValidation.cs new file mode 100644 index 0000000000000..25135dce7385b --- /dev/null +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/RuntimeTypeSystemHelpers/TypeValidation.cs @@ -0,0 +1,199 @@ +// 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.Contracts; +using Microsoft.Diagnostics.DataContractReader.Data; + +namespace Microsoft.Diagnostics.DataContractReader.RuntimeTypeSystemHelpers; + +internal class TypeValidation +{ + private readonly Target _target; + + internal TypeValidation(Target target) + { + _target = target; + } + + // 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 + { + private readonly Target _target; + private readonly Target.TypeInfo _type; + internal TargetPointer Address { get; init; } + + private MethodTableFlags_1? _methodTableFlags; + + internal NonValidatedMethodTable(Target target, TargetPointer methodTablePointer) + { + _target = target; + _type = target.GetTypeInfo(DataType.MethodTable); + Address = methodTablePointer; + _methodTableFlags = null; + } + + private MethodTableFlags_1 GetOrCreateFlags() + { + if (_methodTableFlags == null) + { + // note: may throw if the method table Address is corrupted + MethodTableFlags_1 flags = new MethodTableFlags_1 + { + MTFlags = _target.Read(Address + (ulong)_type.Fields[nameof(MethodTableFlags_1.MTFlags)].Offset), + MTFlags2 = _target.Read(Address + (ulong)_type.Fields[nameof(MethodTableFlags_1.MTFlags2)].Offset), + BaseSize = _target.Read(Address + (ulong)_type.Fields[nameof(MethodTableFlags_1.BaseSize)].Offset), + }; + _methodTableFlags = flags; + } + return _methodTableFlags.Value; + } + + internal MethodTableFlags_1 Flags => GetOrCreateFlags(); + + internal TargetPointer EEClassOrCanonMT => _target.ReadPointer(Address + (ulong)_type.Fields[nameof(EEClassOrCanonMT)].Offset); + internal TargetPointer EEClass => MethodTableFlags_1.GetEEClassOrCanonMTBits(EEClassOrCanonMT) == MethodTableFlags_1.EEClassOrCanonMTBits.EEClass ? EEClassOrCanonMT : throw new InvalidOperationException("not an EEClass"); + internal TargetPointer CanonMT + { + get + { + if (MethodTableFlags_1.GetEEClassOrCanonMTBits(EEClassOrCanonMT) == MethodTableFlags_1.EEClassOrCanonMTBits.CanonMT) + { + return MethodTableFlags_1.UntagEEClassOrCanonMT(EEClassOrCanonMT); + } + else + { + throw new InvalidOperationException("not a canonical method table"); + } + } + } + } + + internal struct NonValidatedEEClass + { + public readonly Target _target; + private readonly Target.TypeInfo _type; + + internal TargetPointer Address { get; init; } + + internal NonValidatedEEClass(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 NonValidatedMethodTable GetMethodTableData(Target target, TargetPointer methodTablePointer) + { + return new NonValidatedMethodTable(target, methodTablePointer); + } + + internal static NonValidatedEEClass GetEEClassData(Target target, TargetPointer eeClassPointer) + { + return new NonValidatedEEClass(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(NonValidatedMethodTable 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(NonValidatedMethodTable 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) + { + NonValidatedEEClass eeClass = GetEEClassData(_target, eeClassPtr); + TargetPointer methodTablePtrFromClass = eeClass.MethodTable; + if (methodTable.Address == methodTablePtrFromClass) + { + return true; + } + if (methodTable.Flags.HasInstantiation || methodTable.Flags.IsArray) + { + NonValidatedMethodTable methodTableFromClass = GetMethodTableData(_target, methodTablePtrFromClass); + TargetPointer classFromMethodTable = GetClassThrowing(methodTableFromClass); + return classFromMethodTable == eeClassPtr; + } + } + return false; + } + + private bool ValidateMethodTableAdHoc(NonValidatedMethodTable 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; + } + + private TargetPointer GetClassThrowing(NonValidatedMethodTable methodTable) + { + TargetPointer eeClassOrCanonMT = methodTable.EEClassOrCanonMT; + + if (MethodTableFlags_1.GetEEClassOrCanonMTBits(eeClassOrCanonMT) == MethodTableFlags_1.EEClassOrCanonMTBits.EEClass) + { + return methodTable.EEClass; + } + else + { + TargetPointer canonicalMethodTablePtr = methodTable.CanonMT; + NonValidatedMethodTable umt = GetMethodTableData(_target, canonicalMethodTablePtr); + return umt.EEClass; + } + } + + internal bool TryValidateMethodTablePointer(TargetPointer methodTablePointer) + { + NonValidatedMethodTable nonvalidatedMethodTable = GetMethodTableData(_target, methodTablePointer); + + return ValidateMethodTablePointer(nonvalidatedMethodTable); + } +} diff --git a/src/native/managed/cdacreader/tests/ExecutionManagerTestBuilder.cs b/src/native/managed/cdacreader/tests/ExecutionManagerTestBuilder.cs index 2438369a8265c..614d63d3f9740 100644 --- a/src/native/managed/cdacreader/tests/ExecutionManagerTestBuilder.cs +++ b/src/native/managed/cdacreader/tests/ExecutionManagerTestBuilder.cs @@ -371,7 +371,7 @@ internal readonly struct JittedCodeRange public JittedCodeRange AllocateJittedCodeRange(ulong codeRangeStart, uint codeRangeSize) { - MockMemorySpace.BumpAllocator allocator = Builder.CreateAllocator(codeRangeStart, codeRangeStart + codeRangeSize); + MockMemorySpace.BumpAllocator allocator = Builder.CreateAllocator(codeRangeStart, codeRangeStart + codeRangeSize, minAlign: 1); return new JittedCodeRange { Allocator = allocator }; } diff --git a/src/native/managed/cdacreader/tests/MethodTableTests.cs b/src/native/managed/cdacreader/tests/MethodTableTests.cs index d86c2dd2e8eb2..a2c7ec4c7ba31 100644 --- a/src/native/managed/cdacreader/tests/MethodTableTests.cs +++ b/src/native/managed/cdacreader/tests/MethodTableTests.cs @@ -2,35 +2,34 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; -using System.Text; +using System.Collections.Generic; using Microsoft.Diagnostics.DataContractReader.Contracts; +using Microsoft.Diagnostics.DataContractReader.RuntimeTypeSystemHelpers; using Xunit; namespace Microsoft.Diagnostics.DataContractReader.UnitTests; using MockRTS = MockDescriptors.RuntimeTypeSystem; -public unsafe class MethodTableTests +public class MethodTableTests { - // a delegate for adding more heap fragments to the context builder - private delegate MockMemorySpace.Builder ConfigureContextBuilder(MockMemorySpace.Builder builder); - - private static void RTSContractHelper(MockTarget.Architecture arch, ConfigureContextBuilder configure, Action testCase) + private static void RTSContractHelper(MockTarget.Architecture arch, Action configure, Action testCase) { TargetTestHelpers targetTestHelpers = new(arch); MockMemorySpace.Builder builder = new(targetTestHelpers); - builder = builder + MockRTS rtsBuilder = new (builder) { + // arbitrary address range + TypeSystemAllocator = builder.CreateAllocator(start: 0x00000000_33000000, end: 0x00000000_34000000), + }; + builder .SetContracts ([ nameof(Contracts.RuntimeTypeSystem) ]) - .SetTypes (MockRTS.Types) + .SetTypes (rtsBuilder.Types) .SetGlobals (MockRTS.Globals); - builder = MockRTS.AddGlobalPointers(targetTestHelpers, builder); + rtsBuilder.AddGlobalPointers(); - if (configure != null) - { - builder = configure(builder); - } + configure?.Invoke(rtsBuilder); bool success = builder.TryCreateTarget(out ContractDescriptorTarget? target); Assert.True(success); @@ -42,42 +41,46 @@ private static void RTSContractHelper(MockTarget.Architecture arch, ConfigureCon [ClassData(typeof(MockTarget.StdArch))] public void HasRuntimeTypeSystemContract(MockTarget.Architecture arch) { - RTSContractHelper(arch, default, (target) => + TargetPointer freeObjectMethodTableAddress = default; + RTSContractHelper(arch, + (builder) => + { + freeObjectMethodTableAddress = builder.FreeObjectMethodTableAddress; + }, + (target) => { Contracts.IRuntimeTypeSystem metadataContract = target.Contracts.RuntimeTypeSystem; Assert.NotNull(metadataContract); - Contracts.TypeHandle handle = metadataContract.GetTypeHandle(MockRTS.TestFreeObjectMethodTableAddress); + Contracts.TypeHandle handle = metadataContract.GetTypeHandle(freeObjectMethodTableAddress); Assert.NotEqual(TargetPointer.Null, handle.Address); Assert.True(metadataContract.IsFreeObjectMethodTable(handle)); }); } - private static MockMemorySpace.Builder AddSystemObject(TargetTestHelpers targetTestHelpers, MockMemorySpace.Builder builder, TargetPointer systemObjectMethodTablePtr, TargetPointer systemObjectEEClassPtr) + private (TargetPointer MethodTable, TargetPointer EEClass) AddSystemObjectMethodTable(MockRTS rtsBuilder) { + MockMemorySpace.Builder builder = rtsBuilder.Builder; + TargetTestHelpers targetTestHelpers = builder.TargetTestHelpers; 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 = MockRTS.AddEEClass(targetTestHelpers, builder, systemObjectEEClassPtr, "System.Object", systemObjectMethodTablePtr, attr: (uint)typeAttributes, numMethods: numMethods, numNonVirtualSlots: 0); - builder = MockRTS.AddMethodTable(targetTestHelpers, builder, systemObjectMethodTablePtr, "System.Object", systemObjectEEClassPtr, + TargetPointer systemObjectEEClassPtr = rtsBuilder.AddEEClass("System.Object", attr: (uint)typeAttributes, numMethods: numMethods, numNonVirtualSlots: 0); + TargetPointer systemObjectMethodTablePtr = rtsBuilder.AddMethodTable("System.Object", mtflags: default, mtflags2: default, baseSize: targetTestHelpers.ObjectBaseSize, module: TargetPointer.Null, parentMethodTable: TargetPointer.Null, numInterfaces: 0, numVirtuals: numVirtuals); - return builder; + rtsBuilder.SetEEClassAndCanonMTRefs(systemObjectEEClassPtr, systemObjectMethodTablePtr); + return (MethodTable: systemObjectMethodTablePtr, EEClass: systemObjectEEClassPtr); } [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); - TargetTestHelpers targetTestHelpers = new(arch); + TargetPointer systemObjectMethodTablePtr = default; RTSContractHelper(arch, - (builder) => + (rtsBuilder) => { - builder = AddSystemObject(targetTestHelpers, builder, systemObjectMethodTablePtr, systemObjectEEClassPtr); - return builder; + systemObjectMethodTablePtr = AddSystemObjectMethodTable(rtsBuilder).MethodTable; }, (target) => { @@ -93,30 +96,23 @@ public void ValidateSystemObjectMethodTable(MockTarget.Architecture arch) [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); + TargetPointer systemStringMethodTablePtr = default; + TargetPointer systemStringEEClassPtr = default; RTSContractHelper(arch, - (builder) => + (rtsBuilder) => { - builder = AddSystemObject(targetTestHelpers, builder, systemObjectMethodTablePtr, systemObjectEEClassPtr); + TargetPointer systemObjectMethodTablePtr = AddSystemObjectMethodTable(rtsBuilder).MethodTable; + 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)RuntimeTypeSystem_1.WFLAGS_HIGH.HasComponentSize | /*componentSize: */2; - builder = MockRTS.AddEEClass(targetTestHelpers, builder, systemStringEEClassPtr, "System.String", systemStringMethodTablePtr, attr: (uint)typeAttributes, numMethods: numMethods, numNonVirtualSlots: 0); - builder = MockRTS.AddMethodTable(targetTestHelpers, builder, systemStringMethodTablePtr, "System.String", systemStringEEClassPtr, - mtflags: mtflags, mtflags2: default, baseSize: targetTestHelpers.StringBaseSize, + uint mtflags = (uint)MethodTableFlags_1.WFLAGS_HIGH.HasComponentSize | /*componentSize: */2; + systemStringEEClassPtr = rtsBuilder.AddEEClass("System.String", attr: (uint)typeAttributes, numMethods: numMethods, numNonVirtualSlots: 0); + systemStringMethodTablePtr = rtsBuilder.AddMethodTable("System.String", + mtflags: mtflags, mtflags2: default, baseSize: rtsBuilder.Builder.TargetTestHelpers.StringBaseSize, module: TargetPointer.Null, parentMethodTable: systemObjectMethodTablePtr, numInterfaces: numInterfaces, numVirtuals: numVirtuals); - return builder; + rtsBuilder.SetEEClassAndCanonMTRefs(systemStringEEClassPtr, systemStringMethodTablePtr); }, (target) => { @@ -133,22 +129,15 @@ public void ValidateSystemStringMethodTable(MockTarget.Architecture arch) [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); + TargetPointer badMethodTablePtr = default; + TargetPointer badMethodTableEEClassPtr = new TargetPointer(0x00000010_afafafafa0); // bad address RTSContractHelper(arch, - (builder) => + (rtsBuilder) => { - builder = AddSystemObject(targetTestHelpers, builder, systemObjectMethodTablePtr, systemObjectEEClassPtr); - builder = MockRTS.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; + TargetPointer systemObjectMethodTablePtr = AddSystemObjectMethodTable(rtsBuilder).MethodTable; + badMethodTablePtr = rtsBuilder.AddMethodTable("Bad MethodTable", mtflags: default, mtflags2: default, baseSize: rtsBuilder.Builder.TargetTestHelpers.ObjectBaseSize, module: TargetPointer.Null, parentMethodTable: systemObjectMethodTablePtr, numInterfaces: 0, numVirtuals: 3); + // make the method table point at a bad EEClass + rtsBuilder.SetMethodTableEEClassOrCanonMTRaw(badMethodTablePtr, badMethodTableEEClassPtr); }, (target) => { @@ -162,43 +151,33 @@ public void MethodTableEEClassInvalidThrows(MockTarget.Architecture arch) [ClassData(typeof(MockTarget.StdArch))] public void ValidateGenericInstMethodTable(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); + TargetPointer genericDefinitionMethodTablePtr = default; + TargetPointer genericInstanceMethodTablePtr = default; const int numMethods = 17; RTSContractHelper(arch, - (builder) => + (rtsBuilder) => { - builder = AddSystemObject(targetTestHelpers, builder, systemObjectMethodTablePtr, systemObjectEEClassPtr); + TargetTestHelpers targetTestHelpers = rtsBuilder.Builder.TargetTestHelpers; + TargetPointer systemObjectMethodTablePtr = AddSystemObjectMethodTable(rtsBuilder).MethodTable; 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 = MockRTS.AddEEClass(targetTestHelpers, builder, genericDefinitionEEClassPtr, "EEClass GenericDefinition", genericDefinitionMethodTablePtr, attr: (uint)typeAttributes, numMethods: numMethods, numNonVirtualSlots: 0); - builder = MockRTS.AddMethodTable(targetTestHelpers, builder, genericDefinitionMethodTablePtr, "MethodTable GenericDefinition", genericDefinitionEEClassPtr, + TargetPointer genericDefinitionEEClassPtr = rtsBuilder.AddEEClass("EEClass GenericDefinition", attr: (uint)typeAttributes, numMethods: numMethods, numNonVirtualSlots: 0); + genericDefinitionMethodTablePtr = rtsBuilder.AddMethodTable("MethodTable GenericDefinition", mtflags: gtd_mtflags, mtflags2: default, baseSize: targetTestHelpers.ObjectBaseSize, module: TargetPointer.Null, parentMethodTable: systemObjectMethodTablePtr, numInterfaces: numInterfaces, numVirtuals: numVirtuals); + rtsBuilder.SetEEClassAndCanonMTRefs(genericDefinitionEEClassPtr, genericDefinitionMethodTablePtr); const uint ginst_mtflags = 0x00000010; // TODO: GenericsMask_GenericInst - TargetPointer ginstCanonMT = new TargetPointer(genericDefinitionMethodTablePtr.Value | (ulong)1); - builder = MockRTS.AddMethodTable(targetTestHelpers, builder, genericInstanceMethodTablePtr, "MethodTable GenericInstance", eeClassOrCanonMT: ginstCanonMT, + genericInstanceMethodTablePtr = rtsBuilder.AddMethodTable("MethodTable GenericInstance", mtflags: ginst_mtflags, mtflags2: default, baseSize: targetTestHelpers.ObjectBaseSize, module: TargetPointer.Null, parentMethodTable: genericDefinitionMethodTablePtr, numInterfaces: numInterfaces, numVirtuals: numVirtuals); + rtsBuilder.SetMethodTableCanonMT(genericInstanceMethodTablePtr, genericDefinitionMethodTablePtr); - return builder; }, (target) => { @@ -216,46 +195,33 @@ public void ValidateGenericInstMethodTable(MockTarget.Architecture arch) [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); + TargetPointer arrayInstanceMethodTablePtr = default; const uint arrayInstanceComponentSize = 392; RTSContractHelper(arch, - (builder) => + (rtsBuilder) => { - builder = AddSystemObject(targetTestHelpers, builder, systemObjectMethodTablePtr, systemObjectEEClassPtr); + TargetTestHelpers targetTestHelpers = rtsBuilder.Builder.TargetTestHelpers; + TargetPointer systemObjectMethodTablePtr = AddSystemObjectMethodTable(rtsBuilder).MethodTable; 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 = MockRTS.AddEEClass(targetTestHelpers, builder, systemArrayEEClassPtr, "EEClass System.Array", systemArrayMethodTablePtr, attr: systemArrayCorTypeAttr, numMethods: systemArrayNumMethods, numNonVirtualSlots: 0); - builder = MockRTS.AddMethodTable(targetTestHelpers, builder, systemArrayMethodTablePtr, "MethodTable System.Array", systemArrayEEClassPtr, + TargetPointer systemArrayEEClassPtr = rtsBuilder.AddEEClass("EEClass System.Array", attr: systemArrayCorTypeAttr, numMethods: systemArrayNumMethods, numNonVirtualSlots: 0); + TargetPointer systemArrayMethodTablePtr = rtsBuilder.AddMethodTable("MethodTable System.Array", mtflags: default, mtflags2: default, baseSize: targetTestHelpers.ObjectBaseSize, module: TargetPointer.Null, parentMethodTable: systemObjectMethodTablePtr, numInterfaces: systemArrayNumInterfaces, numVirtuals: 3); + rtsBuilder.SetEEClassAndCanonMTRefs(systemArrayEEClassPtr, systemArrayMethodTablePtr); - const uint arrayInst_mtflags = (uint)(RuntimeTypeSystem_1.WFLAGS_HIGH.HasComponentSize | RuntimeTypeSystem_1.WFLAGS_HIGH.Category_Array) | arrayInstanceComponentSize; + const uint arrayInst_mtflags = (uint)(MethodTableFlags_1.WFLAGS_HIGH.HasComponentSize | MethodTableFlags_1.WFLAGS_HIGH.Category_Array) | arrayInstanceComponentSize; const uint arrayInstCorTypeAttr = (uint)(System.Reflection.TypeAttributes.Public | System.Reflection.TypeAttributes.Class | System.Reflection.TypeAttributes.Sealed); - builder = MockRTS.AddEEClass(targetTestHelpers, builder, arrayInstanceEEClassPtr, "EEClass ArrayInstance", arrayInstanceMethodTablePtr, attr: arrayInstCorTypeAttr, numMethods: systemArrayNumMethods, numNonVirtualSlots: 0); - builder = MockRTS.AddMethodTable(targetTestHelpers, builder, arrayInstanceMethodTablePtr, "MethodTable ArrayInstance", arrayInstanceEEClassPtr, + TargetPointer arrayInstanceEEClassPtr = rtsBuilder.AddEEClass("EEClass ArrayInstance", attr: arrayInstCorTypeAttr, numMethods: systemArrayNumMethods, numNonVirtualSlots: 0); + arrayInstanceMethodTablePtr = rtsBuilder.AddMethodTable("MethodTable ArrayInstance", mtflags: arrayInst_mtflags, mtflags2: default, baseSize: targetTestHelpers.ObjectBaseSize, module: TargetPointer.Null, parentMethodTable: systemArrayMethodTablePtr, numInterfaces: systemArrayNumInterfaces, numVirtuals: 3); - - return builder; + rtsBuilder.SetEEClassAndCanonMTRefs(arrayInstanceEEClassPtr, arrayInstanceMethodTablePtr); }, (target) => { diff --git a/src/native/managed/cdacreader/tests/MockDescriptors.cs b/src/native/managed/cdacreader/tests/MockDescriptors.cs index f9d393b552f7b..1dc2051338915 100644 --- a/src/native/managed/cdacreader/tests/MockDescriptors.cs +++ b/src/native/managed/cdacreader/tests/MockDescriptors.cs @@ -3,90 +3,73 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; -using System.Runtime.InteropServices; using System.Text; -using Microsoft.Diagnostics.DataContractReader.Contracts; +using System.Runtime.InteropServices; +using Microsoft.Diagnostics.DataContractReader.RuntimeTypeSystemHelpers; namespace Microsoft.Diagnostics.DataContractReader.UnitTests; internal class MockDescriptors { - private static readonly Target.TypeInfo MethodTableTypeInfo = new() + private static readonly (string Name, DataType Type)[] MethodTableFields = new[] { - Fields = new Dictionary { - { 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.PerInstInfo), new () { Offset = 56, Type = DataType.pointer}}, - } + (nameof(Data.MethodTable.MTFlags), DataType.uint32), + (nameof(Data.MethodTable.BaseSize), DataType.uint32), + (nameof(Data.MethodTable.MTFlags2), DataType.uint32), + (nameof(Data.MethodTable.EEClassOrCanonMT), DataType.nuint), + (nameof(Data.MethodTable.Module), DataType.pointer), + (nameof(Data.MethodTable.ParentMethodTable), DataType.pointer), + (nameof(Data.MethodTable.NumInterfaces), DataType.uint16), + (nameof(Data.MethodTable.NumVirtuals), DataType.uint16), + (nameof(Data.MethodTable.PerInstInfo), DataType.pointer), }; - private static readonly Target.TypeInfo EEClassTypeInfo = new Target.TypeInfo() + private static readonly (string Name, DataType Type)[] EEClassFields = new[] { - Fields = new Dictionary { - { nameof (Data.EEClass.MethodTable), new () { Offset = 8, Type = DataType.pointer}}, - { nameof (Data.EEClass.CorTypeAttr), new () { Offset = 16, Type = DataType.uint32}}, - { nameof (Data.EEClass.NumMethods), new () { Offset = 20, Type = DataType.uint16}}, - { nameof (Data.EEClass.InternalCorElementType), new () { Offset = 22, Type = DataType.uint8}}, - { nameof (Data.EEClass.NumNonVirtualSlots), new () { Offset = 24, Type = DataType.uint16}}, - } + (nameof(Data.EEClass.MethodTable), DataType.pointer), + (nameof(Data.EEClass.CorTypeAttr), DataType.uint32), + (nameof(Data.EEClass.NumMethods), DataType.uint16), + (nameof(Data.EEClass.InternalCorElementType), DataType.uint8), + (nameof(Data.EEClass.NumNonVirtualSlots), DataType.uint16), }; - private static readonly Target.TypeInfo ArrayClassTypeInfo = new Target.TypeInfo() + private static readonly (string Name, DataType Type)[] ArrayClassFields = new[] { - Fields = new Dictionary { - { nameof (Data.ArrayClass.Rank), new () { Offset = 0x70, Type = DataType.uint8}}, - } + (nameof(Data.ArrayClass.Rank), DataType.uint8), }; - private static readonly Target.TypeInfo ObjectTypeInfo = new() + private static readonly (string Name, DataType Type)[] ObjectFields = new[] { - Fields = new Dictionary { - { "m_pMethTab", new() { Offset = 0, Type = DataType.pointer} }, - } + ("m_pMethTab", DataType.pointer), }; - private static readonly Target.TypeInfo StringTypeInfo = new Target.TypeInfo() + private static readonly (string Name, DataType Type)[] StringFields = new[] { - Fields = new Dictionary { - { "m_StringLength", new() { Offset = 0x8, Type = DataType.uint32} }, - { "m_FirstChar", new() { Offset = 0xc, Type = DataType.uint16} }, - } + ("m_StringLength", DataType.uint32), + ("m_FirstChar", DataType.uint16), }; - private static readonly Target.TypeInfo ArrayTypeInfo = new Target.TypeInfo() + private static readonly (string Name, DataType Type)[] ArrayFields = new[] { - Fields = new Dictionary { - { "m_NumComponents", new() { Offset = 0x8, Type = DataType.uint32} }, - }, + ("m_NumComponents", DataType.uint32), }; - private static readonly Target.TypeInfo SyncTableEntryInfo = new Target.TypeInfo() + private static readonly (string Name, DataType Type)[] SyncTableEntryFields = new[] { - Fields = new Dictionary { - { nameof(Data.SyncTableEntry.SyncBlock), new() { Offset = 0, Type = DataType.pointer} }, - }, + (nameof(Data.SyncTableEntry.SyncBlock), DataType.pointer), }; - private static readonly Target.TypeInfo SyncBlockTypeInfo = new Target.TypeInfo() + private static readonly (string Name, DataType Type)[] SyncBlockFields = new[] { - Fields = new Dictionary { - { nameof(Data.SyncBlock.InteropInfo), new() { Offset = 0, Type = DataType.pointer} }, - }, + (nameof(Data.SyncBlock.InteropInfo), DataType.pointer), }; - private static readonly Target.TypeInfo InteropSyncBlockTypeInfo = new Target.TypeInfo() + private static readonly (string Name, DataType Type)[] InteropSyncBlockFields = new[] { - Fields = new Dictionary { - { nameof(Data.InteropSyncBlockInfo.RCW), new() { Offset = 0, Type = DataType.pointer} }, - { nameof(Data.InteropSyncBlockInfo.CCW), new() { Offset = 0x8, Type = DataType.pointer} }, - }, + (nameof(Data.InteropSyncBlockInfo.RCW), DataType.pointer), + (nameof(Data.InteropSyncBlockInfo.CCW), DataType.pointer), }; private static readonly (string, DataType)[] ModuleFields = @@ -138,17 +121,23 @@ private static readonly (string, DataType)[] ThreadStoreFields = (nameof(Data.ThreadStore.DeadCount), DataType.uint32), ]; - public static class RuntimeTypeSystem + public class RuntimeTypeSystem { internal const ulong TestFreeObjectMethodTableGlobalAddress = 0x00000000_7a0000a0; - internal const ulong TestFreeObjectMethodTableAddress = 0x00000000_7a0000a8; - internal static readonly Dictionary Types = new() + private Dictionary GetTypes() { - [DataType.MethodTable] = MethodTableTypeInfo, - [DataType.EEClass] = EEClassTypeInfo, - [DataType.ArrayClass] = ArrayClassTypeInfo, - }; + var targetTestHelpers = Builder.TargetTestHelpers; + Dictionary types = new (); + var layout = targetTestHelpers.LayoutFields(MethodTableFields); + types[DataType.MethodTable] = new Target.TypeInfo() { Fields = layout.Fields, Size = layout.Stride }; + var eeClassLayout = targetTestHelpers.LayoutFields(EEClassFields); + layout = eeClassLayout; + types[DataType.EEClass] = new Target.TypeInfo() { Fields = layout.Fields, Size = layout.Stride }; + layout = targetTestHelpers.ExtendLayout(ArrayClassFields, eeClassLayout); + types[DataType.ArrayClass] = new Target.TypeInfo() { Fields = layout.Fields, Size = layout.Stride }; + return types; + } internal static readonly (string Name, ulong Value, string? Type)[] Globals = [ @@ -156,86 +145,170 @@ internal static readonly (string Name, ulong Value, string? Type)[] Globals = (nameof(Constants.Globals.MethodDescAlignment), 8, nameof(DataType.uint64)), ]; - internal static MockMemorySpace.Builder AddGlobalPointers(TargetTestHelpers targetTestHelpers, MockMemorySpace.Builder builder) + internal readonly MockMemorySpace.Builder Builder; + + internal Dictionary Types { get; init; } + + internal MockMemorySpace.BumpAllocator TypeSystemAllocator { get; set; } + + internal TargetPointer FreeObjectMethodTableAddress { get; private set; } + + internal RuntimeTypeSystem(MockMemorySpace.Builder builder) + { + Builder = builder; + Types = GetTypes();; + } + + internal void AddGlobalPointers() { - return AddFreeObjectMethodTable(targetTestHelpers, builder); + AddFreeObjectMethodTable(); } - private static MockMemorySpace.Builder AddFreeObjectMethodTable(TargetTestHelpers targetTestHelpers, MockMemorySpace.Builder builder) + private void AddFreeObjectMethodTable() { + Target.TypeInfo methodTableTypeInfo = Types[DataType.MethodTable]; + MockMemorySpace.HeapFragment freeObjectMethodTableFragment = TypeSystemAllocator.Allocate(methodTableTypeInfo.Size.Value, "Free Object Method Table"); + Builder.AddHeapFragment(freeObjectMethodTableFragment); + FreeObjectMethodTableAddress = freeObjectMethodTableFragment.Address; + + TargetTestHelpers targetTestHelpers = Builder.TargetTestHelpers; 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 () { Name = "Free Object Method Table", Address = TestFreeObjectMethodTableAddress, Data = new byte[targetTestHelpers.SizeOfTypeInfo(MethodTableTypeInfo)] } - ]); + targetTestHelpers.WritePointer(globalAddr.Data, FreeObjectMethodTableAddress); + Builder.AddHeapFragment(globalAddr); + } + + // set the eeClass MethodTable pointer to the canonMT and the canonMT's EEClass pointer to the eeClass + internal void SetEEClassAndCanonMTRefs(TargetPointer eeClass, TargetPointer canonMT) + { + // make eeClass point at the canonMT + Target.TypeInfo eeClassTypeInfo = Types[DataType.EEClass]; + Span eeClassBytes = Builder.BorrowAddressRange(eeClass, (int)eeClassTypeInfo.Size.Value); + Builder.TargetTestHelpers.WritePointer(eeClassBytes.Slice(eeClassTypeInfo.Fields[nameof(Data.EEClass.MethodTable)].Offset, Builder.TargetTestHelpers.PointerSize), canonMT); + + // and make the canonMT point at the eeClass + SetMethodTableEEClassOrCanonMTRaw(canonMT, eeClass); } - internal static MockMemorySpace.Builder AddEEClass(TargetTestHelpers targetTestHelpers, MockMemorySpace.Builder builder, TargetPointer eeClassPtr, string name, TargetPointer canonMTPtr, uint attr, ushort numMethods, ushort numNonVirtualSlots) + + // for cases when a methodTable needs to point at a canonical method table + internal void SetMethodTableCanonMT(TargetPointer methodTable, TargetPointer canonMT) => SetMethodTableEEClassOrCanonMTRaw(methodTable, canonMT.Value | 1); + + // NOTE: don't use directly unless you want to write a bogus value into the canonMT field + internal void SetMethodTableEEClassOrCanonMTRaw(TargetPointer methodTable, TargetPointer eeClassOrCanonMT) { - MockMemorySpace.HeapFragment eeClassFragment = new() { Name = $"EEClass '{name}'", Address = eeClassPtr, Data = new byte[targetTestHelpers.SizeOfTypeInfo(EEClassTypeInfo)] }; + Target.TypeInfo methodTableTypeInfo = Types[DataType.MethodTable]; + Span methodTableBytes = Builder.BorrowAddressRange(methodTable, (int)methodTableTypeInfo.Size.Value); + Builder.TargetTestHelpers.WritePointer(methodTableBytes.Slice(methodTableTypeInfo.Fields[nameof(Data.MethodTable.EEClassOrCanonMT)].Offset, Builder.TargetTestHelpers.PointerSize), eeClassOrCanonMT); + } + + // call SetEEClassAndCanonMTRefs after the EEClass and the MethodTable have been added + internal TargetPointer AddEEClass(string name, uint attr, ushort numMethods, ushort numNonVirtualSlots) + { + Target.TypeInfo eeClassTypeInfo = Types[DataType.EEClass]; + MockMemorySpace.Builder builder = Builder; + TargetTestHelpers targetTestHelpers = builder.TargetTestHelpers; + + MockMemorySpace.HeapFragment eeClassFragment = TypeSystemAllocator.Allocate(eeClassTypeInfo.Size.Value, $"EEClass '{name}'"); Span dest = eeClassFragment.Data; - targetTestHelpers.WritePointer(dest.Slice(EEClassTypeInfo.Fields[nameof(Data.EEClass.MethodTable)].Offset), canonMTPtr); - targetTestHelpers.Write(dest.Slice(EEClassTypeInfo.Fields[nameof(Data.EEClass.CorTypeAttr)].Offset), attr); - targetTestHelpers.Write(dest.Slice(EEClassTypeInfo.Fields[nameof(Data.EEClass.NumMethods)].Offset), numMethods); - targetTestHelpers.Write(dest.Slice(EEClassTypeInfo.Fields[nameof(Data.EEClass.NumNonVirtualSlots)].Offset), numNonVirtualSlots); - return builder.AddHeapFragment(eeClassFragment); + targetTestHelpers.Write(dest.Slice(eeClassTypeInfo.Fields[nameof(Data.EEClass.CorTypeAttr)].Offset), attr); + targetTestHelpers.Write(dest.Slice(eeClassTypeInfo.Fields[nameof(Data.EEClass.NumMethods)].Offset), numMethods); + targetTestHelpers.Write(dest.Slice(eeClassTypeInfo.Fields[nameof(Data.EEClass.NumNonVirtualSlots)].Offset), numNonVirtualSlots); + builder.AddHeapFragment(eeClassFragment); + return eeClassFragment.Address; } - internal static MockMemorySpace.Builder AddArrayClass(TargetTestHelpers targetTestHelpers, MockMemorySpace.Builder builder, TargetPointer eeClassPtr, string name, TargetPointer canonMTPtr, uint attr, ushort numMethods, ushort numNonVirtualSlots, byte rank) + internal TargetPointer AddArrayClass(string name, uint attr, ushort numMethods, ushort numNonVirtualSlots, byte rank) { - int size = targetTestHelpers.SizeOfTypeInfo(EEClassTypeInfo) + targetTestHelpers.SizeOfTypeInfo(ArrayClassTypeInfo); - MockMemorySpace.HeapFragment eeClassFragment = new() { Name = $"ArrayClass '{name}'", Address = eeClassPtr, Data = new byte[size] }; + Dictionary types = Types; + MockMemorySpace.Builder builder = Builder; + TargetTestHelpers targetTestHelpers = builder.TargetTestHelpers; + Target.TypeInfo eeClassTypeInfo = types[DataType.EEClass]; + Target.TypeInfo arrayClassTypeInfo = types[DataType.ArrayClass]; + MockMemorySpace.HeapFragment eeClassFragment = TypeSystemAllocator.Allocate (arrayClassTypeInfo.Size.Value, $"ArrayClass '{name}'"); Span dest = eeClassFragment.Data; - targetTestHelpers.WritePointer(dest.Slice(EEClassTypeInfo.Fields[nameof(Data.EEClass.MethodTable)].Offset), canonMTPtr); - targetTestHelpers.Write(dest.Slice(EEClassTypeInfo.Fields[nameof(Data.EEClass.CorTypeAttr)].Offset), attr); - targetTestHelpers.Write(dest.Slice(EEClassTypeInfo.Fields[nameof(Data.EEClass.NumMethods)].Offset), numMethods); - targetTestHelpers.Write(dest.Slice(EEClassTypeInfo.Fields[nameof(Data.EEClass.NumNonVirtualSlots)].Offset), numNonVirtualSlots); - targetTestHelpers.Write(dest.Slice(ArrayClassTypeInfo.Fields[nameof(Data.ArrayClass.Rank)].Offset), rank); - return builder.AddHeapFragment(eeClassFragment); + targetTestHelpers.Write(dest.Slice(eeClassTypeInfo.Fields[nameof(Data.EEClass.CorTypeAttr)].Offset), attr); + targetTestHelpers.Write(dest.Slice(eeClassTypeInfo.Fields[nameof(Data.EEClass.NumMethods)].Offset), numMethods); + targetTestHelpers.Write(dest.Slice(eeClassTypeInfo.Fields[nameof(Data.EEClass.NumNonVirtualSlots)].Offset), numNonVirtualSlots); + targetTestHelpers.Write(dest.Slice(arrayClassTypeInfo.Fields[nameof(Data.ArrayClass.Rank)].Offset), rank); + builder.AddHeapFragment(eeClassFragment); + return eeClassFragment.Address; } - internal static MockMemorySpace.Builder AddMethodTable(TargetTestHelpers targetTestHelpers, MockMemorySpace.Builder builder, TargetPointer methodTablePtr, string name, TargetPointer eeClassOrCanonMT, uint mtflags, uint mtflags2, uint baseSize, + internal TargetPointer AddMethodTable(string name, 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)] }; + Target.TypeInfo methodTableTypeInfo = Types[DataType.MethodTable]; + MockMemorySpace.Builder builder = Builder; + TargetTestHelpers targetTestHelpers = builder.TargetTestHelpers; + MockMemorySpace.HeapFragment methodTableFragment = TypeSystemAllocator.Allocate(methodTableTypeInfo.Size.Value, $"MethodTable '{name}'"); 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); + 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); + builder.AddHeapFragment(methodTableFragment); + return methodTableFragment.Address; } } - public static class Object + public class Object { private const ulong TestStringMethodTableGlobalAddress = 0x00000000_100000a0; - private const ulong TestStringMethodTableAddress = 0x00000000_100000a8; + internal const ulong TestArrayBoundsZeroGlobalAddress = 0x00000000_100000b0; private const ulong TestSyncTableEntriesGlobalAddress = 0x00000000_100000c0; + // The sync table entries address range is manually managed in AddObjectWithSyncBlock private const ulong TestSyncTableEntriesAddress = 0x00000000_f0000000; internal const ulong TestObjectToMethodTableUnmask = 0x7; internal const ulong TestSyncBlockValueToObjectOffset = sizeof(uint); - internal static Dictionary Types(TargetTestHelpers helpers) => RuntimeTypeSystem.Types.Concat( - new Dictionary() + internal readonly RuntimeTypeSystem RTSBuilder; + internal MockMemorySpace.Builder Builder => RTSBuilder.Builder; + + internal MockMemorySpace.BumpAllocator ManagedObjectAllocator { get; set; } + + internal MockMemorySpace.BumpAllocator SyncBlockAllocator { get; private set; } + + internal TargetPointer TestStringMethodTableAddress { get; private set; } + + internal Dictionary Types { get; init; } + + internal Object(RuntimeTypeSystem rtsBuilder) { - [DataType.Object] = ObjectTypeInfo, - [DataType.String] = StringTypeInfo, - [DataType.Array] = ArrayTypeInfo with { Size = helpers.ArrayBaseSize }, - [DataType.SyncTableEntry] = SyncTableEntryInfo with { Size = (uint)helpers.SizeOfTypeInfo(SyncTableEntryInfo) }, - [DataType.SyncBlock] = SyncBlockTypeInfo, - [DataType.InteropSyncBlockInfo] = InteropSyncBlockTypeInfo, - }).ToDictionary(); + RTSBuilder = rtsBuilder; + + const ulong TestSyncBlocksAddress = 0x00000000_e0000000; + SyncBlockAllocator = Builder.CreateAllocator(start: TestSyncBlocksAddress, end: TestSyncBlocksAddress + 0x1000); + + Types = GetTypes(); + } + + private Dictionary GetTypes() + { + var helpers = Builder.TargetTestHelpers; + Dictionary types = RTSBuilder.Types; + var objectLayout = helpers.LayoutFields(ObjectFields); + types[DataType.Object] = new Target.TypeInfo() { Fields = objectLayout.Fields, Size = objectLayout.Stride }; + var layout = helpers.ExtendLayout(StringFields, objectLayout); + types[DataType.String] = new Target.TypeInfo() { Fields = layout.Fields, Size = layout.Stride }; + layout = helpers.ExtendLayout(ArrayFields, objectLayout); + types[DataType.Array] = new Target.TypeInfo() { Fields = layout.Fields, Size = layout.Stride }; + Debug.Assert(types[DataType.Array].Size == helpers.ArrayBaseSize); + layout = helpers.LayoutFields(SyncTableEntryFields); + types[DataType.SyncTableEntry] = new Target.TypeInfo() { Fields = layout.Fields, Size = layout.Stride }; + layout = helpers.LayoutFields(SyncBlockFields); + types[DataType.SyncBlock] = new Target.TypeInfo() { Fields = layout.Fields, Size = layout.Stride }; + layout = helpers.LayoutFields(InteropSyncBlockFields); + types[DataType.InteropSyncBlockInfo] = new Target.TypeInfo() { Fields = layout.Fields, Size = layout.Stride }; + return types; + } internal static (string Name, ulong Value, string? Type)[] Globals(TargetTestHelpers helpers) => RuntimeTypeSystem.Globals.Concat( [ @@ -247,132 +320,158 @@ internal static (string Name, ulong Value, string? Type)[] Globals(TargetTestHel (nameof(Constants.Globals.SyncBlockValueToObjectOffset), TestSyncBlockValueToObjectOffset, "uint16"), ]).ToArray(); - internal static MockMemorySpace.Builder AddGlobalPointers(TargetTestHelpers targetTestHelpers, MockMemorySpace.Builder builder) + internal void AddGlobalPointers() { - builder = RuntimeTypeSystem.AddGlobalPointers(targetTestHelpers, builder); - builder = AddStringMethodTablePointer(targetTestHelpers, builder); - builder = AddSyncTableEntriesPointer(targetTestHelpers, builder); - return builder; + RTSBuilder.AddGlobalPointers(); + AddStringMethodTablePointer(); + AddSyncTableEntriesPointer(); } - private static MockMemorySpace.Builder AddStringMethodTablePointer(TargetTestHelpers targetTestHelpers, MockMemorySpace.Builder builder) + private void AddStringMethodTablePointer() { + MockMemorySpace.Builder builder = Builder; + TargetTestHelpers targetTestHelpers = builder.TargetTestHelpers; + MockMemorySpace.HeapFragment stringMethodTableFragment = RTSBuilder.TypeSystemAllocator.Allocate((ulong)targetTestHelpers.PointerSize /*HACK*/, "String Method Table (fake)"); + TestStringMethodTableAddress = stringMethodTableFragment.Address; MockMemorySpace.HeapFragment fragment = new() { Name = "Address of String Method Table", Address = TestStringMethodTableGlobalAddress, Data = new byte[targetTestHelpers.PointerSize] }; - targetTestHelpers.WritePointer(fragment.Data, TestStringMethodTableAddress); - return builder.AddHeapFragments([ - fragment, - new () { Name = "String Method Table", Address = TestStringMethodTableAddress, Data = new byte[targetTestHelpers.PointerSize] } - ]); + targetTestHelpers.WritePointer(fragment.Data, stringMethodTableFragment.Address); + builder.AddHeapFragment(fragment); } - private static MockMemorySpace.Builder AddSyncTableEntriesPointer(TargetTestHelpers targetTestHelpers, MockMemorySpace.Builder builder) + private void AddSyncTableEntriesPointer() { + MockMemorySpace.Builder builder = Builder; + TargetTestHelpers targetTestHelpers = builder.TargetTestHelpers; MockMemorySpace.HeapFragment fragment = new() { Name = "Address of Sync Table Entries", Address = TestSyncTableEntriesGlobalAddress, Data = new byte[targetTestHelpers.PointerSize] }; targetTestHelpers.WritePointer(fragment.Data, TestSyncTableEntriesAddress); - return builder.AddHeapFragment(fragment); + builder.AddHeapFragment(fragment); } - internal static MockMemorySpace.Builder AddObject(TargetTestHelpers targetTestHelpers, MockMemorySpace.Builder builder, TargetPointer address, TargetPointer methodTable) + internal TargetPointer AddObject(TargetPointer methodTable, uint prefixSize =0) { - MockMemorySpace.HeapFragment fragment = new() { Name = $"Object : MT = '{methodTable}'", Address = address, Data = new byte[targetTestHelpers.SizeOfTypeInfo(ObjectTypeInfo)] }; - Span dest = fragment.Data; - targetTestHelpers.WritePointer(dest.Slice(ObjectTypeInfo.Fields["m_pMethTab"].Offset), methodTable); - return builder.AddHeapFragment(fragment); + MockMemorySpace.Builder builder = Builder; + TargetTestHelpers targetTestHelpers = builder.TargetTestHelpers; + Target.TypeInfo objectTypeInfo = Types[DataType.Object]; + uint totalSize = objectTypeInfo.Size.Value + prefixSize; + MockMemorySpace.HeapFragment fragment = ManagedObjectAllocator.Allocate(totalSize, $"Object : MT = '{methodTable}'"); + + Span dest = fragment.Data.AsSpan((int)prefixSize); + targetTestHelpers.WritePointer(dest.Slice(objectTypeInfo.Fields["m_pMethTab"].Offset), methodTable); + builder.AddHeapFragment(fragment); + return fragment.Address + prefixSize; // return pointer to the object, not the prefix; } - internal static MockMemorySpace.Builder AddObjectWithSyncBlock(TargetTestHelpers targetTestHelpers, MockMemorySpace.Builder builder, TargetPointer address, TargetPointer methodTable, uint syncBlockIndex, TargetPointer rcw, TargetPointer ccw) + internal TargetPointer AddObjectWithSyncBlock(TargetPointer methodTable, uint syncBlockIndex, TargetPointer rcw, TargetPointer ccw) { + MockMemorySpace.Builder builder = Builder; + TargetTestHelpers targetTestHelpers = builder.TargetTestHelpers; const uint IsSyncBlockIndexBits = 0x08000000; const uint SyncBlockIndexMask = (1 << 26) - 1; if ((syncBlockIndex & SyncBlockIndexMask) != syncBlockIndex) throw new ArgumentOutOfRangeException(nameof(syncBlockIndex), "Invalid sync block index"); - builder = AddObject(targetTestHelpers, builder, address, methodTable); + TargetPointer address = AddObject(methodTable, prefixSize: (uint)TestSyncBlockValueToObjectOffset); // Add the sync table value before the object uint syncTableValue = IsSyncBlockIndexBits | syncBlockIndex; TargetPointer syncTableValueAddr = address - TestSyncBlockValueToObjectOffset; - MockMemorySpace.HeapFragment fragment = new() { Name = $"Sync Table Value : index = {syncBlockIndex}", Address = syncTableValueAddr, Data = new byte[sizeof(uint)] }; - targetTestHelpers.Write(fragment.Data, syncTableValue); - builder = builder.AddHeapFragment(fragment); + Span syncTableValueDest = builder.BorrowAddressRange(syncTableValueAddr, sizeof(uint)); + targetTestHelpers.Write(syncTableValueDest, syncTableValue); // Add the actual sync block and associated data - return AddSyncBlock(targetTestHelpers, builder, syncBlockIndex, rcw, ccw); + AddSyncBlock(syncBlockIndex, rcw, ccw); + return address; } - private static MockMemorySpace.Builder AddSyncBlock(TargetTestHelpers targetTestHelpers, MockMemorySpace.Builder builder, uint index, TargetPointer rcw, TargetPointer ccw) + private void AddSyncBlock(uint index, TargetPointer rcw, TargetPointer ccw) { + Dictionary types = Types; + MockMemorySpace.Builder builder = Builder; + TargetTestHelpers targetTestHelpers = builder.TargetTestHelpers; // Tests write the sync blocks starting at TestSyncBlocksAddress - const ulong TestSyncBlocksAddress = 0x00000000_e0000000; - int syncBlockSize = targetTestHelpers.SizeOfTypeInfo(SyncBlockTypeInfo); - int interopSyncBlockInfoSize = targetTestHelpers.SizeOfTypeInfo(InteropSyncBlockTypeInfo); - ulong syncBlockAddr = TestSyncBlocksAddress + index * (ulong)(syncBlockSize + interopSyncBlockInfoSize); + Target.TypeInfo syncBlockTypeInfo = types[DataType.SyncBlock]; + Target.TypeInfo interopSyncBlockTypeInfo = types[DataType.InteropSyncBlockInfo]; + uint syncBlockSize = syncBlockTypeInfo.Size.Value;; + uint interopSyncBlockInfoSize = syncBlockSize + interopSyncBlockTypeInfo.Size.Value; + + + MockMemorySpace.HeapFragment syncBlock = SyncBlockAllocator.Allocate(interopSyncBlockInfoSize, $"Sync Block {index}"); + TargetPointer syncBlockAddr = syncBlock.Address; // Add the sync table entry - pointing at the sync block - uint syncTableEntrySize = (uint)targetTestHelpers.SizeOfTypeInfo(SyncTableEntryInfo); + Target.TypeInfo syncTableEntryInfo = types[DataType.SyncTableEntry]; + uint syncTableEntrySize = (uint)targetTestHelpers.SizeOfTypeInfo(syncTableEntryInfo); ulong syncTableEntryAddr = TestSyncTableEntriesAddress + index * syncTableEntrySize; MockMemorySpace.HeapFragment syncTableEntry = new() { Name = $"SyncTableEntries[{index}]", Address = syncTableEntryAddr, Data = new byte[syncTableEntrySize] }; Span syncTableEntryData = syncTableEntry.Data; - targetTestHelpers.WritePointer(syncTableEntryData.Slice(SyncTableEntryInfo.Fields[nameof(Data.SyncTableEntry.SyncBlock)].Offset), syncBlockAddr); + targetTestHelpers.WritePointer(syncTableEntryData.Slice(syncTableEntryInfo.Fields[nameof(Data.SyncTableEntry.SyncBlock)].Offset), syncBlockAddr); // Add the sync block - pointing at the interop sync block info - ulong interopInfoAddr = syncBlockAddr + (ulong)syncBlockSize; - MockMemorySpace.HeapFragment syncBlock = new() { Name = $"Sync Block", Address = syncBlockAddr, Data = new byte[syncBlockSize] }; + TargetPointer interopInfoAddr = syncBlockAddr + syncBlockSize; Span syncBlockData = syncBlock.Data; - targetTestHelpers.WritePointer(syncBlockData.Slice(SyncBlockTypeInfo.Fields[nameof(Data.SyncBlock.InteropInfo)].Offset), interopInfoAddr); + targetTestHelpers.WritePointer(syncBlockData.Slice(syncBlockTypeInfo.Fields[nameof(Data.SyncBlock.InteropInfo)].Offset), interopInfoAddr); // Add the interop sync block info - MockMemorySpace.HeapFragment interopInfo = new() { Name = $"Interop Sync Block Info", Address = interopInfoAddr, Data = new byte[interopSyncBlockInfoSize] }; - Span interopInfoData = interopInfo.Data; - targetTestHelpers.WritePointer(interopInfoData.Slice(InteropSyncBlockTypeInfo.Fields[nameof(Data.InteropSyncBlockInfo.RCW)].Offset), rcw); - targetTestHelpers.WritePointer(interopInfoData.Slice(InteropSyncBlockTypeInfo.Fields[nameof(Data.InteropSyncBlockInfo.CCW)].Offset), ccw); + Span interopInfoData = syncBlock.Data.AsSpan((int)syncBlockSize); + targetTestHelpers.WritePointer(interopInfoData.Slice(interopSyncBlockTypeInfo.Fields[nameof(Data.InteropSyncBlockInfo.RCW)].Offset), rcw); + targetTestHelpers.WritePointer(interopInfoData.Slice(interopSyncBlockTypeInfo.Fields[nameof(Data.InteropSyncBlockInfo.CCW)].Offset), ccw); - return builder.AddHeapFragments([syncTableEntry, syncBlock, interopInfo]); + builder.AddHeapFragments([syncTableEntry, syncBlock]); } - internal static MockMemorySpace.Builder AddStringObject(TargetTestHelpers targetTestHelpers, MockMemorySpace.Builder builder, TargetPointer address, string value) + internal TargetPointer AddStringObject(string value) { - int size = targetTestHelpers.SizeOfTypeInfo(ObjectTypeInfo) + targetTestHelpers.SizeOfTypeInfo(StringTypeInfo) + value.Length * sizeof(char); - MockMemorySpace.HeapFragment fragment = new() { Name = $"String = '{value}'", Address = address, Data = new byte[size] }; + MockMemorySpace.Builder builder = Builder; + Dictionary types = Types; + TargetTestHelpers targetTestHelpers = builder.TargetTestHelpers; + Target.TypeInfo objectTypeInfo = types[DataType.Object]; + Target.TypeInfo stringTypeInfo = types[DataType.String]; + int size = (int)stringTypeInfo.Size.Value + value.Length * sizeof(char); + MockMemorySpace.HeapFragment fragment = ManagedObjectAllocator.Allocate((uint)size, $"String = '{value}'"); Span dest = fragment.Data; - targetTestHelpers.WritePointer(dest.Slice(ObjectTypeInfo.Fields["m_pMethTab"].Offset), TestStringMethodTableAddress); - targetTestHelpers.Write(dest.Slice(StringTypeInfo.Fields["m_StringLength"].Offset), (uint)value.Length); - MemoryMarshal.Cast(value).CopyTo(dest.Slice(StringTypeInfo.Fields["m_FirstChar"].Offset)); - return builder.AddHeapFragment(fragment); + targetTestHelpers.WritePointer(dest.Slice(objectTypeInfo.Fields["m_pMethTab"].Offset), TestStringMethodTableAddress); + targetTestHelpers.Write(dest.Slice(stringTypeInfo.Fields["m_StringLength"].Offset), (uint)value.Length); + MemoryMarshal.Cast(value).CopyTo(dest.Slice(stringTypeInfo.Fields["m_FirstChar"].Offset)); + builder.AddHeapFragment(fragment); + return fragment.Address; } - internal static MockMemorySpace.Builder AddArrayObject(TargetTestHelpers targetTestHelpers, MockMemorySpace.Builder builder, TargetPointer address, Array array) + internal TargetPointer AddArrayObject(Array array) { + MockMemorySpace.Builder builder = Builder; + Dictionary types = Types; + TargetTestHelpers targetTestHelpers = builder.TargetTestHelpers; bool isSingleDimensionZeroLowerBound = array.Rank == 1 && array.GetLowerBound(0) == 0; // Bounds are part of the array object for non-single dimension or non-zero lower bound arrays // << fields that are part of the array type info >> // int32_t bounds[rank]; // int32_t lowerBounds[rank]; - int size = targetTestHelpers.SizeOfTypeInfo(ObjectTypeInfo) + targetTestHelpers.SizeOfTypeInfo(ArrayTypeInfo); + Target.TypeInfo objectTypeInfo = types[DataType.Object]; + Target.TypeInfo arrayTypeInfo = types[DataType.Array]; + int size = (int)arrayTypeInfo.Size.Value; if (!isSingleDimensionZeroLowerBound) size += array.Rank * sizeof(int) * 2; - ulong methodTableAddress = (address.Value + (ulong)size + (TestObjectToMethodTableUnmask - 1)) & ~(TestObjectToMethodTableUnmask - 1); - ulong arrayClassAddress = methodTableAddress + (ulong)targetTestHelpers.SizeOfTypeInfo(RuntimeTypeSystem.Types[DataType.MethodTable]); - - uint flags = (uint)(RuntimeTypeSystem_1.WFLAGS_HIGH.HasComponentSize | RuntimeTypeSystem_1.WFLAGS_HIGH.Category_Array) | (uint)array.Length; + uint flags = (uint)(MethodTableFlags_1.WFLAGS_HIGH.HasComponentSize | MethodTableFlags_1.WFLAGS_HIGH.Category_Array) | (uint)array.Length; if (isSingleDimensionZeroLowerBound) - flags |= (uint)RuntimeTypeSystem_1.WFLAGS_HIGH.Category_IfArrayThenSzArray; + flags |= (uint)MethodTableFlags_1.WFLAGS_HIGH.Category_IfArrayThenSzArray; string name = string.Join(',', array); - builder = RuntimeTypeSystem.AddArrayClass(targetTestHelpers, builder, arrayClassAddress, name, methodTableAddress, + TargetPointer arrayClassAddress = RTSBuilder.AddArrayClass(name, attr: 0, numMethods: 0, numNonVirtualSlots: 0, rank: (byte)array.Rank); - builder = RuntimeTypeSystem.AddMethodTable(targetTestHelpers, builder, methodTableAddress, name, arrayClassAddress, + TargetPointer methodTableAddress = RTSBuilder.AddMethodTable(name, mtflags: flags, mtflags2: default, baseSize: targetTestHelpers.ArrayBaseBaseSize, module: TargetPointer.Null, parentMethodTable: TargetPointer.Null, numInterfaces: 0, numVirtuals: 0); + RTSBuilder.SetEEClassAndCanonMTRefs(arrayClassAddress, methodTableAddress); - MockMemorySpace.HeapFragment fragment = new() { Name = $"Array = '{string.Join(',', array)}'", Address = address, Data = new byte[size] }; + MockMemorySpace.HeapFragment fragment = ManagedObjectAllocator.Allocate((uint)size, $"Array = '{string.Join(',', array)}'"); Span dest = fragment.Data; - targetTestHelpers.WritePointer(dest.Slice(ObjectTypeInfo.Fields["m_pMethTab"].Offset), methodTableAddress); - targetTestHelpers.Write(dest.Slice(ArrayTypeInfo.Fields["m_NumComponents"].Offset), (uint)array.Length); - return builder.AddHeapFragment(fragment); + targetTestHelpers.WritePointer(dest.Slice(objectTypeInfo.Fields["m_pMethTab"].Offset), methodTableAddress); + targetTestHelpers.Write(dest.Slice(arrayTypeInfo.Fields["m_NumComponents"].Offset), (uint)array.Length); + builder.AddHeapFragment(fragment); + return fragment.Address; } } @@ -447,6 +546,7 @@ internal TargetPointer AddModule(string? path = null, string? fileName = null) public class Thread { + const bool UseFunclets = false; private const ulong DefaultAllocationRangeStart = 0x0003_0000; private const ulong DefaultAllocationRangeEnd = 0x0004_0000; @@ -503,7 +603,7 @@ public Thread(MockMemorySpace.Builder builder, (ulong Start, ulong End) allocati (nameof(Constants.Globals.ThreadStore), threadStoreGlobal.Address, null), (nameof(Constants.Globals.FinalizerThread), finalizerThreadGlobal.Address, null), (nameof(Constants.Globals.GCThread), gcThreadGlobal.Address, null), - (nameof(Constants.Globals.FeatureEHFunclets), 0, null), + (nameof(Constants.Globals.FeatureEHFunclets), UseFunclets ? 1 : 0, null), ]; } @@ -546,7 +646,10 @@ internal TargetPointer AddThread(uint id, TargetNUInt osId) { TargetTestHelpers helpers = _builder.TargetTestHelpers; Target.TypeInfo typeInfo = Types[DataType.Thread]; - MockMemorySpace.HeapFragment thread = _allocator.Allocate(typeInfo.Size.Value, "Thread"); + if (UseFunclets) + throw new NotImplementedException("todo for funclets: allocate the ExceptionInfo separately"); + ulong allocSize = typeInfo.Size.Value + (UseFunclets ? 0 : Types[DataType.ExceptionInfo].Size.Value); + MockMemorySpace.HeapFragment thread = _allocator.Allocate(allocSize, UseFunclets ? "Thread" : "Thread and ExceptionInfo"); Span data = thread.Data.AsSpan(); helpers.Write( data.Slice(typeInfo.Fields[nameof(Data.Thread.Id)].Offset), @@ -557,11 +660,12 @@ internal TargetPointer AddThread(uint id, TargetNUInt osId) _builder.AddHeapFragment(thread); // Add exception info for the thread - MockMemorySpace.HeapFragment exceptionInfo = _allocator.Allocate(Types[DataType.ExceptionInfo].Size.Value, "ExceptionInfo"); - _builder.AddHeapFragment(exceptionInfo); + // Add exception info for the thread + // TODO: [cdac] Handle when UseFunclets is true - see NotImplementedException thrown above + TargetPointer exceptionInfoAddress = thread.Address + Types[DataType.ExceptionInfo].Size.Value; helpers.WritePointer( data.Slice(typeInfo.Fields[nameof(Data.Thread.ExceptionTracker)].Offset), - exceptionInfo.Address); + exceptionInfoAddress); ulong threadLinkOffset = (ulong)typeInfo.Fields[nameof(Data.Thread.LinkNext)].Offset; if (_previousThread != TargetPointer.Null) diff --git a/src/native/managed/cdacreader/tests/MockMemorySpace.BumpAllocator.cs b/src/native/managed/cdacreader/tests/MockMemorySpace.BumpAllocator.cs index 5bca9dc508f95..17b052c09ca34 100644 --- a/src/native/managed/cdacreader/tests/MockMemorySpace.BumpAllocator.cs +++ b/src/native/managed/cdacreader/tests/MockMemorySpace.BumpAllocator.cs @@ -25,6 +25,8 @@ internal class BumpAllocator private readonly ulong _blockStart; private readonly ulong _blockEnd; // exclusive ulong _current; + + public int MinAlign { get; init; } = 16; // by default align to 16 bytes public BumpAllocator(ulong blockStart, ulong blockEnd) { _blockStart = blockStart; @@ -35,17 +37,25 @@ public BumpAllocator(ulong blockStart, ulong blockEnd) public ulong RangeStart => _blockStart; public ulong RangeEnd => _blockEnd; + private ulong AlignUp(ulong value) + { + return (value + (ulong)(MinAlign - 1)) & ~(ulong)(MinAlign - 1); + } + public bool TryAllocate(ulong size, string name, [NotNullWhen(true)] out HeapFragment? fragment) { - // FIXME: alignment - if (_current + size <= _blockEnd) + ulong current = AlignUp(_current); + Debug.Assert(current >= _current); + Debug.Assert((current % (ulong)MinAlign) == 0); + if (current + size <= _blockEnd) { fragment = new HeapFragment { - Address = _current, + Address = current, Data = new byte[size], Name = name, }; - _current += size; + current += size; + _current = current; return true; } fragment = null; diff --git a/src/native/managed/cdacreader/tests/MockMemorySpace.cs b/src/native/managed/cdacreader/tests/MockMemorySpace.cs index 0439d3cceced8..0c3c086f56603 100644 --- a/src/native/managed/cdacreader/tests/MockMemorySpace.cs +++ b/src/native/managed/cdacreader/tests/MockMemorySpace.cs @@ -265,11 +265,11 @@ public bool TryCreateTarget([NotNullWhen(true)] out ContractDescriptorTarget? ta } // Get an allocator for a range of addresses to simplify creating heap fragments - public BumpAllocator CreateAllocator(ulong start, ulong end) + public BumpAllocator CreateAllocator(ulong start, ulong end, int minAlign = 16) { if (_created) throw new InvalidOperationException("Context already created"); - BumpAllocator allocator = new BumpAllocator(start, end); + BumpAllocator allocator = new BumpAllocator(start, end) { MinAlign = minAlign }; foreach (var a in _allocators) { if (allocator.Overlaps(a)) diff --git a/src/native/managed/cdacreader/tests/ObjectTests.cs b/src/native/managed/cdacreader/tests/ObjectTests.cs index 8a3c05dd79ea1..ba2e36d6fd9c7 100644 --- a/src/native/managed/cdacreader/tests/ObjectTests.cs +++ b/src/native/managed/cdacreader/tests/ObjectTests.cs @@ -2,7 +2,8 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; -using System.Text; +using System.Collections.Generic; +using Microsoft.Diagnostics.DataContractReader.Contracts; using Xunit; namespace Microsoft.Diagnostics.DataContractReader.UnitTests; @@ -11,24 +12,28 @@ namespace Microsoft.Diagnostics.DataContractReader.UnitTests; public unsafe class ObjectTests { - private delegate MockMemorySpace.Builder ConfigureContextBuilder(MockMemorySpace.Builder builder); - private static void ObjectContractHelper(MockTarget.Architecture arch, ConfigureContextBuilder configure, Action testCase) + private static void ObjectContractHelper(MockTarget.Architecture arch, Action configure, Action testCase) { TargetTestHelpers targetTestHelpers = new(arch); MockMemorySpace.Builder builder = new(targetTestHelpers); + MockDescriptors.RuntimeTypeSystem rtsBuilder = new(builder) { + // arbtrary address range + TypeSystemAllocator = builder.CreateAllocator(start: 0x00000000_4a000000, end: 0x00000000_4b000000), + }; + MockObject objectBuilder = new(rtsBuilder) { + // arbtrary adress range + ManagedObjectAllocator = builder.CreateAllocator(start: 0x00000000_10000000, end: 0x00000000_20000000), + }; builder = builder .SetContracts([ nameof (Contracts.Object), nameof (Contracts.RuntimeTypeSystem) ]) .SetGlobals(MockObject.Globals(targetTestHelpers)) - .SetTypes(MockObject.Types(targetTestHelpers)); + .SetTypes(objectBuilder.Types); - builder = MockObject.AddGlobalPointers(targetTestHelpers, builder); + objectBuilder.AddGlobalPointers(); - if (configure != null) - { - builder = configure(builder); - } + configure?.Invoke(objectBuilder); bool success = builder.TryCreateTarget(out ContractDescriptorTarget? target); Assert.True(success); @@ -39,14 +44,12 @@ private static void ObjectContractHelper(MockTarget.Architecture arch, Configure [ClassData(typeof(MockTarget.StdArch))] public void UnmaskMethodTableAddress(MockTarget.Architecture arch) { - const ulong TestObjectAddress = 0x00000000_10000010; + TargetPointer TestObjectAddress = default; const ulong TestMethodTableAddress = 0x00000000_10000027; - TargetTestHelpers targetTestHelpers = new(arch); ObjectContractHelper(arch, - (builder) => + (objectBuilder) => { - builder = MockObject.AddObject(targetTestHelpers, builder, TestObjectAddress, TestMethodTableAddress); - return builder; + TestObjectAddress = objectBuilder.AddObject(TestMethodTableAddress); }, (target) => { @@ -61,14 +64,12 @@ public void UnmaskMethodTableAddress(MockTarget.Architecture arch) [ClassData(typeof(MockTarget.StdArch))] public void StringValue(MockTarget.Architecture arch) { - const ulong TestStringAddress = 0x00000000_10000010; + TargetPointer TestStringAddress = default; string expected = "test_string_value"; - TargetTestHelpers targetTestHelpers = new(arch); ObjectContractHelper(arch, - (builder) => + (objectBuilder) => { - builder = MockObject.AddStringObject(targetTestHelpers, builder, TestStringAddress, expected); - return builder; + TestStringAddress = objectBuilder.AddStringObject(expected); }, (target) => { @@ -83,21 +84,20 @@ public void StringValue(MockTarget.Architecture arch) [ClassData(typeof(MockTarget.StdArch))] public void ArrayData(MockTarget.Architecture arch) { - const ulong SingleDimensionArrayAddress = 0x00000000_20000010; - const ulong MultiDimensionArrayAddress = 0x00000000_30000010; - const ulong NonZeroLowerBoundArrayAddress = 0x00000000_40000010; + TargetPointer SingleDimensionArrayAddress = default; + TargetPointer MultiDimensionArrayAddress = default; + TargetPointer NonZeroLowerBoundArrayAddress = default; Array singleDimension = new int[10]; Array multiDimension = new int[1, 2, 3, 4]; Array nonZeroLowerBound = Array.CreateInstance(typeof(int), [10], [5]); TargetTestHelpers targetTestHelpers = new(arch); ObjectContractHelper(arch, - (builder) => + (objectBuilder) => { - builder = MockObject.AddArrayObject(targetTestHelpers, builder, SingleDimensionArrayAddress, singleDimension); - builder = MockObject.AddArrayObject(targetTestHelpers, builder, MultiDimensionArrayAddress, multiDimension); - builder = MockObject.AddArrayObject(targetTestHelpers, builder, NonZeroLowerBoundArrayAddress, nonZeroLowerBound); - return builder; + SingleDimensionArrayAddress = objectBuilder.AddArrayObject(singleDimension); + MultiDimensionArrayAddress = objectBuilder.AddArrayObject(multiDimension); + NonZeroLowerBoundArrayAddress = objectBuilder.AddArrayObject(nonZeroLowerBound); }, (target) => { @@ -107,7 +107,8 @@ public void ArrayData(MockTarget.Architecture arch) TargetPointer data = contract.GetArrayData(SingleDimensionArrayAddress, out uint count, out TargetPointer boundsStart, out TargetPointer lowerBounds); Assert.Equal(SingleDimensionArrayAddress + targetTestHelpers.ArrayBaseBaseSize - targetTestHelpers.ObjHeaderSize, data.Value); Assert.Equal((uint)singleDimension.Length, count); - Assert.Equal(SingleDimensionArrayAddress + (ulong)MockObject.Types(targetTestHelpers)[DataType.Array].Fields["m_NumComponents"].Offset, boundsStart.Value); + Target.TypeInfo arrayType = target.GetTypeInfo(DataType.Array); + Assert.Equal(SingleDimensionArrayAddress + (ulong)arrayType.Fields["m_NumComponents"].Offset, boundsStart.Value); Assert.Equal(MockObject.TestArrayBoundsZeroGlobalAddress, lowerBounds.Value); } { @@ -131,20 +132,18 @@ public void ArrayData(MockTarget.Architecture arch) [ClassData(typeof(MockTarget.StdArch))] public void ComData(MockTarget.Architecture arch) { - const ulong TestComObjectAddress = 0x00000000_10000010; - const ulong TestNonComObjectAddress = 0x00000000_10000020; + TargetPointer TestComObjectAddress = default; + TargetPointer TestNonComObjectAddress = default; TargetPointer expectedRCW = 0xaaaa; TargetPointer expectedCCW = 0xbbbb; - TargetTestHelpers targetTestHelpers = new(arch); ObjectContractHelper(arch, - (builder) => + (objectBuilder) => { uint syncBlockIndex = 0; - builder = MockObject.AddObjectWithSyncBlock(targetTestHelpers, builder, TestComObjectAddress, 0, syncBlockIndex++, expectedRCW, expectedCCW); - builder = MockObject.AddObjectWithSyncBlock(targetTestHelpers, builder, TestNonComObjectAddress, 0, syncBlockIndex++, TargetPointer.Null, TargetPointer.Null); - return builder; + TestComObjectAddress = objectBuilder.AddObjectWithSyncBlock(0, syncBlockIndex++, expectedRCW, expectedCCW); + TestNonComObjectAddress = objectBuilder.AddObjectWithSyncBlock(0, syncBlockIndex++, TargetPointer.Null, TargetPointer.Null); }, (target) => { diff --git a/src/native/managed/cdacreader/tests/PrecodeStubsTests.cs b/src/native/managed/cdacreader/tests/PrecodeStubsTests.cs index 1b0abf72fd5f0..296a2c0aae790 100644 --- a/src/native/managed/cdacreader/tests/PrecodeStubsTests.cs +++ b/src/native/managed/cdacreader/tests/PrecodeStubsTests.cs @@ -185,8 +185,8 @@ internal class PrecodeBuilder { } public PrecodeBuilder(AllocationRange allocationRange, MockMemorySpace.Builder builder, Dictionary? typeInfoCache = null) { Builder = builder; - PrecodeAllocator = new MockMemorySpace.BumpAllocator(allocationRange.PrecodeDescriptorStart, allocationRange.PrecodeDescriptorEnd); - StubDataPageAllocator = new MockMemorySpace.BumpAllocator(allocationRange.StubDataPageStart, allocationRange.StubDataPageEnd); + PrecodeAllocator = builder.CreateAllocator(allocationRange.PrecodeDescriptorStart, allocationRange.PrecodeDescriptorEnd); + StubDataPageAllocator = builder.CreateAllocator(allocationRange.StubDataPageStart, allocationRange.StubDataPageEnd); TypeInfoCache = typeInfoCache ?? CreateTypeInfoCache(Builder.TargetTestHelpers); } diff --git a/src/native/managed/cdacreader/tests/TargetTestHelpers.cs b/src/native/managed/cdacreader/tests/TargetTestHelpers.cs index 0c96b19a49f40..71844fff6f429 100644 --- a/src/native/managed/cdacreader/tests/TargetTestHelpers.cs +++ b/src/native/managed/cdacreader/tests/TargetTestHelpers.cs @@ -333,16 +333,22 @@ public readonly struct LayoutResult public readonly uint MaxAlign { get; init; } } + // Implements a simple layout algorithm that aligns fields to their size + // and aligns the structure to the largest field size. public LayoutResult LayoutFields((string Name, DataType Type)[] fields) => LayoutFields(FieldLayout.CIsh, fields); - // Implements a simple layout algorithm that aligns fields to their size - // and aligns the structure to the largest field size. - public LayoutResult LayoutFields(FieldLayout style, (string Name, DataType Type)[] fields) + // Layout the fields of a structure according to the specified layout style. + public LayoutResult LayoutFields(FieldLayout style, (string Name, DataType Type)[] fields) { - Dictionary fieldInfos = new (); - int maxAlign = 1; int offset = 0; + int maxAlign = 1; + return LayoutFieldsWorker(style, fields, ref offset, ref maxAlign); + } + + private LayoutResult LayoutFieldsWorker(FieldLayout style, (string Name, DataType Type)[] fields, ref int offset, ref int maxAlign) + { + Dictionary fieldInfos = new (); for (int i = 0; i < fields.Length; i++) { var (name, type) = fields[i]; @@ -360,6 +366,7 @@ public LayoutResult LayoutFields(FieldLayout style, (string Name, DataType Type) }; fieldInfos[name] = new Target.FieldInfo { Offset = offset, + Type = type, }; offset += size; } @@ -371,4 +378,14 @@ public LayoutResult LayoutFields(FieldLayout style, (string Name, DataType Type) return new LayoutResult() { Fields = fieldInfos, Stride = (uint)stride, MaxAlign = (uint)maxAlign}; } + // Extend the layout of a base class with additional fields. + public LayoutResult ExtendLayout((string Name, DataType Type)[] fields, LayoutResult baseClass) => ExtendLayout(FieldLayout.CIsh, fields, baseClass); + + public LayoutResult ExtendLayout(FieldLayout fieldLayout, (string Name, DataType Type)[] fields, LayoutResult baseClass) + { + int offset = (int)baseClass.Stride; + int maxAlign = (int)baseClass.MaxAlign; + return LayoutFieldsWorker(fieldLayout, fields, ref offset, ref maxAlign); + } + }