diff --git a/docs/design/datacontracts/ExecutionManager.md b/docs/design/datacontracts/ExecutionManager.md index ea032d3cec32cf..655feda2e2a964 100644 --- a/docs/design/datacontracts/ExecutionManager.md +++ b/docs/design/datacontracts/ExecutionManager.md @@ -28,30 +28,57 @@ struct CodeBlockHandle ## Version 1 The execution manager uses two data structures to map the entire target address space to native executable code. -The range section map is used to partition the address space into large chunks which point to range section fragments. Each chunk is relatively large. If there is any executable code in the chunk, the chunk will contain one or more range section fragments that cover subsets of the chunk. Conversely if a massive method is JITed a single range section fragment may span multiple adjacent chunks. +The [range section map](#rangesectionmap) is used to partition the address space into large chunks which point to range section fragments. Each chunk is relatively large. If there is any executable code in the chunk, the chunk will contain one or more range section fragments that cover subsets of the chunk. Conversely if a massive method is JITed a single range section fragment may span multiple adjacent chunks. -Within a range section fragment, a nibble map structure is used to map arbitrary IP addresses back to the start of the method (and to the code header which immediately preceeeds the entrypoint to the code). +Within a range section fragment, a [nibble map](#nibblemap) structure is used to map arbitrary IP addresses back to the start of the method (and to the code header which immediately preceeeds the entrypoint to the code). Data descriptors used: | Data Descriptor Name | Field | Meaning | | --- | --- | --- | -| RangeSectionMap | TopLevelData | pointer to the outermost RangeSection | -| RangeSectionFragment| ? | ? | -| RangeSection | ? | ? | -| RealCodeHeader | ? | ? | -| HeapList | ? | ? | - - +| `RangeSectionMap` | `TopLevelData` | Pointer to the outermost RangeSection | +| `RangeSectionFragment`| `RangeBegin` | Begin address of the fragment | +| `RangeSectionFragment`| `RangeEndOpen` | End address of the fragment | +| `RangeSectionFragment`| `RangeSection` | Pointer to the corresponding `RangeSection` | +| `RangeSectionFragment`| `Next` | Pointer to the next fragment | +| `RangeSection` | `RangeBegin` | Begin address of the range section | +| `RangeSection` | `RangeEndOpen` | End address of the range section | +| `RangeSection` | `NextForDelete` | Pointer to next range section for deletion | +| `RangeSection` | `JitManager` | Pointer to the JIT manager | +| `RangeSection` | `Flags` | Flags for the range section | +| `RangeSection` | `HeapList` | Pointer to the heap list | +| `RangeSection` | `R2RModule` | ReadyToRun module | +| `CodeHeapListNode` | `Next` | Next node | +| `CodeHeapListNode` | `StartAddress` | Start address of the used portion of the code heap | +| `CodeHeapListNode` | `EndAddress` | End address of the used portion of the code heap | +| `CodeHeapListNode` | `MapBase` | Start of the map - start address rounded down based on OS page size | +| `CodeHeapListNode` | `HeaderMap` | Bit array used to find the start of methods - relative to `MapBase` | +| `RealCodeHeader` | `MethodDesc` | Pointer to the corresponding `MethodDesc` | +| `Module` | `ReadyToRunInfo` | Pointer to the `ReadyToRunInfo` for the module | +| `ReadyToRunInfo` | `CompositeInfo` | Pointer to composite R2R info - or itself for non-composite | +| `ReadyToRunInfo` | `NumRuntimeFunctions` | Number of `RuntimeFunctions` | +| `ReadyToRunInfo` | `RuntimeFunctions` | Pointer to an array of `RuntimeFunctions` | +| `ReadyToRunInfo` | `DelayLoadMethodCallThunks` | Pointer to an `ImageDataDirectory` for the delay load method call thunks | +| `ReadyToRunInfo` | `EntryPointToMethodDescMap` | `HashMap` of entry point addresses to `MethodDesc` pointers | +| `ImageDataDirectory` | `VirtualAddress` | Virtual address of the image data directory | +| `ImageDataDirectory` | `Size` | Size of the data | +| `RuntimeFunction` | `BeginAddress` | Begin address of the function | +| `HashMap` | `Buckets` | Pointer to the buckets of a `HashMap` | +| `Bucket` | `Keys` | Array of keys of `HashMapSlotsPerBucket` length | +| `Bucket` | `Values` | Array of values of `HashMapSlotsPerBucket` length | Global variables used: | Global Name | Type | Purpose | | --- | --- | --- | -| ExecutionManagerCodeRangeMapAddress | TargetPointer | Pointer to the global RangeSectionMap -| StubCodeBlockLast | uint8 | Maximum sentinel code header value indentifying a stub code block +| `ExecutionManagerCodeRangeMapAddress` | TargetPointer | Pointer to the global RangeSectionMap | +| `StubCodeBlockLast` | uint8 | Maximum sentinel code header value indentifying a stub code block | +| `HashMapSlotsPerBucket` | uint32 | Number of slots in each bucket of a `HashMap` | +| `HashMapValueMask` | uint64 | Bitmask used when storing values in a `HashMap` | +| `FeatureEHFunclets` | uint8 | 1 if EH funclets are enabled, 0 otherwise | Contracts used: | Contract Name | | --- | +| `PlatformMetadata` | The bulk of the work is done by the `GetCodeBlockHandle` API that maps a code pointer to information about the containing jitted method. @@ -97,28 +124,78 @@ There are two `JitManager`s: the "EE JitManager" for jitted code and "R2R JitMan The EE JitManager `GetMethodInfo` implements the nibble map lookup, summarized below, followed by returning the `RealCodeHeader` data: ```csharp - bool GetMethodInfo(RangeSection rangeSection, TargetCodePointer jittedCodeAddress, [NotNullWhen(true)] out CodeBlock? info) +bool GetMethodInfo(RangeSection rangeSection, TargetCodePointer jittedCodeAddress, [NotNullWhen(true)] out CodeBlock? info) +{ + TargetPointer start = FindMethodCode(rangeSection, jittedCodeAddress); // nibble map lookup + if (start == TargetPointer.Null) { - TargetPointer start = FindMethodCode(rangeSection, jittedCodeAddress); // nibble map lookup - if (start == TargetPointer.Null) - { - return false; - } - TargetNUInt relativeOffset = jittedCodeAddress - start; - int codeHeaderOffset = Target.PointerSize; - TargetPointer codeHeaderIndirect = start - codeHeaderOffset; - if (RangeSection.IsStubCodeBlock(Target, codeHeaderIndirect)) - { - return false; - } - TargetPointer codeHeaderAddress = Target.ReadPointer(codeHeaderIndirect); - Data.RealCodeHeader realCodeHeader = Target.ProcessedData.GetOrAdd(codeHeaderAddress); - info = new CodeBlock(jittedCodeAddress, codeHeaderOffset, relativeOffset, realCodeHeader, rangeSection.Data!.JitManager); - return true; + return false; + } + TargetNUInt relativeOffset = jittedCodeAddress - start; + int codeHeaderOffset = Target.PointerSize; + TargetPointer codeHeaderIndirect = start - codeHeaderOffset; + if (RangeSection.IsStubCodeBlock(Target, codeHeaderIndirect)) + { + return false; + } + TargetPointer codeHeaderAddress = Target.ReadPointer(codeHeaderIndirect); + Data.RealCodeHeader realCodeHeader = Target.ProcessedData.GetOrAdd(codeHeaderAddress); + info = new CodeBlock(jittedCodeAddress, realCodeHeader.MethodDesc, relativeOffset, rangeSection.Data!.JitManager); + return true; +} +``` + +The R2R JitManager `GetMethodInfo` finds the runtime function corresponding to an address and maps its entry point pack to a method: + +```csharp +bool GetMethodInfo(RangeSection rangeSection, TargetCodePointer jittedCodeAddress, [NotNullWhen(true)] out CodeBlock? info) +{ + if (rangeSection.Data == null) + throw new ArgumentException(nameof(rangeSection)); + + info = default; + + TargetPointer r2rModule = Target.ReadPointer(/* range section address + RangeSection::R2RModule offset */); + TargetPointer r2rInfo = Target.ReadPointer(r2rModule + /* Module::ReadyToRunInfo offset */); + + // Check if address is in a thunk + if (IsStubCodeBlockThunk(rangeSection.Data, r2rInfo, jittedCodeAddress)) + return false; + + // Find the relative address that we are looking for + TargetCodePointer code = /* code pointer from jittedCodeAddress using PlatformMetadata.GetCodePointerFlags */ + TargetPointer imageBase = Target.ReadPointer(/* range section address + RangeSection::RangeBegin offset */); + TargetPointer relativeAddr = code - imageBase; + + TargetPointer runtimeFunctions = Target.ReadPointer(r2rInfo + /* ReadyToRunInfo::RuntimeFunctions offset */); + int index = // Iterate through runtimeFunctions and find index of function with relativeAddress + if (index < 0) + return false; + + bool featureEHFunclets = Target.ReadGlobal(Constants.Globals.FeatureEHFunclets) != 0; + if (featureEHFunclets) + { + // TODO: [cdac] Look up in hot/cold mapping lookup table and if the method is in the cold block, + // get the index of the associated hot block. } + + TargetPointer function = runtimeFunctions + (ulong)(index * /* size of RuntimeFunction */); + + TargetPointer startAddress = imageBase + Target.Read(function + /* RuntimeFunction::BeginAddress offset */); + TargetPointer entryPoint = /* code pointer from startAddress using PlatformMetadata.GetCodePointerFlags */ + + TargetPointer mapAddress = r2rInfo + /* ReadyToRunInfo::EntryPointToMethodDescMap offset */; + TargetPointer methodDesc = /* look up entryPoint in HashMap at mapAddress */; + + // TODO: [cdac] Handle method with cold code when computing relative offset + TargetNUInt relativeOffset = new TargetNUInt(code - startAddress); + + info = new CodeBlock(startAddress.Value, methodDesc, relativeOffset, rangeSection.Data!.JitManager); + return true; +} ``` -The `CodeBlock` encapsulates the `RealCodeHeader` data from the target runtime together with the start of the jitted method +The `CodeBlock` encapsulates the `MethodDesc` data from the target runtime together with the start of the jitted method ```csharp class CodeBlock @@ -126,17 +203,14 @@ class CodeBlock private readonly int _codeHeaderOffset; public TargetCodePointer StartAddress { get; } - // note: this is the address of the pointer to the "real code header", you need to - // dereference it to get the address of _codeHeaderData - public TargetPointer CodeHeaderAddress => StartAddress - _codeHeaderOffset; - private Data.RealCodeHeader _codeHeaderData; + public TargetPointer MethodDesc { get; } public TargetPointer JitManagerAddress { get; } public TargetNUInt RelativeOffset { get; } - public CodeBlock(TargetCodePointer startAddress, int codeHeaderOffset, TargetNUInt relativeOffset, Data.RealCodeHeader codeHeaderData, TargetPointer jitManagerAddress) + + public CodeBlock(TargetCodePointer startAddress, TargetPointer methodDesc, TargetNUInt relativeOffset, TargetPointer jitManagerAddress) { - _codeHeaderOffset = codeHeaderOffset; StartAddress = startAddress; - _codeHeaderData = codeHeaderData; + MethodDesc = methodDesc; RelativeOffset = relativeOffset; JitManagerAddress = jitManagerAddress; } @@ -151,13 +225,13 @@ The remaining contract APIs extract fields of the `CodeBlock`: ```csharp TargetPointer IExecutionManager.GetMethodDesc(CodeBlockHandle codeInfoHandle) { - /* find EECodeBlock info for codeInfoHandle.Address*/ + /* find CodeBlock info for codeInfoHandle.Address*/ return info.MethodDescAddress; } TargetCodePointer IExecutionManager.GetStartAddress(CodeBlockHandle codeInfoHandle) { - /* find EECodeBlock info for codeInfoHandle.Address*/ + /* find CodeBlock info for codeInfoHandle.Address*/ return info.StartAddress; } ``` diff --git a/src/coreclr/debug/runtimeinfo/datadescriptor.h b/src/coreclr/debug/runtimeinfo/datadescriptor.h index 1bc05369a38719..4568395df3cac1 100644 --- a/src/coreclr/debug/runtimeinfo/datadescriptor.h +++ b/src/coreclr/debug/runtimeinfo/datadescriptor.h @@ -227,6 +227,7 @@ CDAC_TYPE_FIELD(Module, /*pointer*/, ThunkHeap, cdac_data::ThunkHeap) CDAC_TYPE_FIELD(Module, /*pointer*/, DynamicMetadata, cdac_data::DynamicMetadata) CDAC_TYPE_FIELD(Module, /*pointer*/, Path, cdac_data::Path) CDAC_TYPE_FIELD(Module, /*pointer*/, FileName, cdac_data::FileName) +CDAC_TYPE_FIELD(Module, /*pointer*/, ReadyToRunInfo, cdac_data::ReadyToRunInfo) CDAC_TYPE_FIELD(Module, /*pointer*/, FieldDefToDescMap, cdac_data::FieldDefToDescMap) CDAC_TYPE_FIELD(Module, /*pointer*/, ManifestModuleReferencesMap, cdac_data::ManifestModuleReferencesMap) @@ -412,6 +413,37 @@ CDAC_TYPE_INDETERMINATE(FixupPrecodeData) CDAC_TYPE_FIELD(FixupPrecodeData, /*pointer*/, MethodDesc, offsetof(FixupPrecodeData, MethodDesc)) CDAC_TYPE_END(FixupPrecodeData) +CDAC_TYPE_BEGIN(ReadyToRunInfo) +CDAC_TYPE_INDETERMINATE(ReadyToRunInfo) +CDAC_TYPE_FIELD(ReadyToRunInfo, /*pointer*/, CompositeInfo, cdac_data::CompositeInfo) +CDAC_TYPE_FIELD(ReadyToRunInfo, /*uint32*/, NumRuntimeFunctions, cdac_data::NumRuntimeFunctions) +CDAC_TYPE_FIELD(ReadyToRunInfo, /*pointer*/, RuntimeFunctions, cdac_data::RuntimeFunctions) +CDAC_TYPE_FIELD(ReadyToRunInfo, /*pointer*/, DelayLoadMethodCallThunks, cdac_data::DelayLoadMethodCallThunks) +CDAC_TYPE_FIELD(ReadyToRunInfo, /*HashMap*/, EntryPointToMethodDescMap, cdac_data::EntryPointToMethodDescMap) +CDAC_TYPE_END(ReadyToRunInfo) + +CDAC_TYPE_BEGIN(ImageDataDirectory) +CDAC_TYPE_SIZE(sizeof(IMAGE_DATA_DIRECTORY)) +CDAC_TYPE_FIELD(ImageDataDirectory, /*uint32*/, VirtualAddress, offsetof(IMAGE_DATA_DIRECTORY, VirtualAddress)) +CDAC_TYPE_FIELD(ImageDataDirectory, /*uint32*/, Size, offsetof(IMAGE_DATA_DIRECTORY, Size)) +CDAC_TYPE_END(ImageDataDirectory) + +CDAC_TYPE_BEGIN(RuntimeFunction) +CDAC_TYPE_SIZE(sizeof(RUNTIME_FUNCTION)) +CDAC_TYPE_FIELD(RuntimeFunction, /*uint32*/, BeginAddress, offsetof(RUNTIME_FUNCTION, BeginAddress)) +CDAC_TYPE_END(RuntimeFunction) + +CDAC_TYPE_BEGIN(HashMap) +CDAC_TYPE_INDETERMINATE(HashMap) +CDAC_TYPE_FIELD(HashMap, /*pointer*/, Buckets, cdac_data::Buckets) +CDAC_TYPE_END(HashMap) + +CDAC_TYPE_BEGIN(Bucket) +CDAC_TYPE_SIZE(sizeof(Bucket)) +CDAC_TYPE_FIELD(Bucket, /*pointer*/, Keys, offsetof(Bucket, m_rgKeys)) +CDAC_TYPE_FIELD(Bucket, /*pointer*/, Values, offsetof(Bucket, m_rgValues)) +CDAC_TYPE_END(Bucket) + CDAC_TYPE_BEGIN(RangeSectionMap) CDAC_TYPE_INDETERMINATE(RangeSectionMap) CDAC_TYPE_FIELD(RangeSectionMap, /*pointer*/, TopLevelData, cdac_data::TopLevelData) @@ -501,6 +533,8 @@ CDAC_GLOBAL(ObjectToMethodTableUnmask, uint8, 1 | 1 << 1) #endif //TARGET_64BIT CDAC_GLOBAL(SOSBreakingChangeVersion, uint8, SOS_BREAKING_CHANGE_VERSION) CDAC_GLOBAL(DirectorySeparator, uint8, (uint8_t)DIRECTORY_SEPARATOR_CHAR_A) +CDAC_GLOBAL(HashMapSlotsPerBucket, uint32, SLOTS_PER_BUCKET) +CDAC_GLOBAL(HashMapValueMask, uint64, VALUE_MASK) CDAC_GLOBAL(MethodDescAlignment, uint64, MethodDesc::ALIGNMENT) CDAC_GLOBAL(ObjectHeaderSize, uint64, OBJHEADER_SIZE) CDAC_GLOBAL(SyncBlockValueToObjectOffset, uint16, OBJHEADER_SIZE - cdac_data::SyncBlockValue) diff --git a/src/coreclr/vm/ceeload.h b/src/coreclr/vm/ceeload.h index e712365bbbdf73..66ef93ee8988f2 100644 --- a/src/coreclr/vm/ceeload.h +++ b/src/coreclr/vm/ceeload.h @@ -1649,6 +1649,7 @@ struct cdac_data static constexpr size_t DynamicMetadata = offsetof(Module, m_pDynamicMetadata); static constexpr size_t Path = offsetof(Module, m_path); static constexpr size_t FileName = offsetof(Module, m_fileName); + static constexpr size_t ReadyToRunInfo = offsetof(Module, m_pReadyToRunInfo); // Lookup map pointers static constexpr size_t FieldDefToDescMap = offsetof(Module, m_FieldDefToDescMap); diff --git a/src/coreclr/vm/codeman.cpp b/src/coreclr/vm/codeman.cpp index eb774f28fab4b3..0769460955ae56 100644 --- a/src/coreclr/vm/codeman.cpp +++ b/src/coreclr/vm/codeman.cpp @@ -5888,8 +5888,6 @@ BOOL ReadyToRunJitManager::JitCodeToMethodInfo(RangeSection * pRangeSection, // Save the raw entry PTR_RUNTIME_FUNCTION RawFunctionEntry = pRuntimeFunctions + MethodIndex; - ULONG UMethodIndex = (ULONG)MethodIndex; - const int lookupIndex = HotColdMappingLookupTable::LookupMappingForMethod(pInfo, (ULONG)MethodIndex); if ((lookupIndex != -1) && ((lookupIndex & 1) == 1)) { diff --git a/src/coreclr/vm/codeman.h b/src/coreclr/vm/codeman.h index 3f024f9493c449..4be52c92ea89c3 100644 --- a/src/coreclr/vm/codeman.h +++ b/src/coreclr/vm/codeman.h @@ -23,10 +23,9 @@ Module Name: An IJitManager knows about which method bodies live in each RangeSection. It can handle methods of one given CodeType. It can map a method body to a MethodDesc. It knows where the GCInfo about the method lives. - Today, we have three IJitManagers viz. + Today, we have two IJitManagers: 1. EEJitManager for JITcompiled code generated by clrjit.dll - 2. NativeImageJitManager for ngenned code. - 3. ReadyToRunJitManager for version resiliant ReadyToRun code + 2. ReadyToRunJitManager for version resiliant ReadyToRun code An ICodeManager knows how to crack a specific format of GCInfo. There is a default format (handled by ExecutionManager::GetDefaultCodeManager()) @@ -73,7 +72,6 @@ class MethodDesc; class ICorJitCompiler; class IJitManager; class EEJitManager; -class NativeImageJitManager; class ReadyToRunJitManager; class ExecutionManager; class Thread; @@ -1657,7 +1655,6 @@ class HostCodeHeap; typedef VPTR(class HostCodeHeap) PTR_HostCodeHeap; typedef VPTR(class EEJitManager) PTR_EEJitManager; -typedef VPTR(class NativeImageJitManager) PTR_NativeImageJitManager; typedef VPTR(class ReadyToRunJitManager) PTR_ReadyToRunJitManager; struct JumpStubBlockHeader diff --git a/src/coreclr/vm/hash.cpp b/src/coreclr/vm/hash.cpp index 54791c5fff9ba4..c3bb80396e1177 100644 --- a/src/coreclr/vm/hash.cpp +++ b/src/coreclr/vm/hash.cpp @@ -34,8 +34,6 @@ const DWORD g_rgPrimes[] = { }; const SIZE_T g_rgNumPrimes = sizeof(g_rgPrimes) / sizeof(*g_rgPrimes); -const unsigned int SLOTS_PER_BUCKET = 4; - #ifndef DACCESS_COMPILE void *PtrHashMap::operator new(size_t size, LoaderHeap *pHeap) diff --git a/src/coreclr/vm/hash.h b/src/coreclr/vm/hash.h index 123993d6ff37ae..99e4dfd9ef7d31 100644 --- a/src/coreclr/vm/hash.h +++ b/src/coreclr/vm/hash.h @@ -46,6 +46,8 @@ typedef ULONG_PTR UPTR; class Bucket; class HashMap; +const unsigned int SLOTS_PER_BUCKET = 4; + //------------------------------------------------------- // class Bucket // used by hash table implementation @@ -54,8 +56,8 @@ typedef DPTR(class Bucket) PTR_Bucket; class Bucket { public: - UPTR m_rgKeys[4]; - UPTR m_rgValues[4]; + UPTR m_rgKeys[SLOTS_PER_BUCKET]; + UPTR m_rgValues[SLOTS_PER_BUCKET]; #define VALUE_MASK (sizeof(LPVOID) == 4 ? 0x7FFFFFFF : I64(0x7FFFFFFFFFFFFFFF)) @@ -532,6 +534,14 @@ class HashMap return m_cbInserts-m_cbDeletes; } + + friend struct ::cdac_data; +}; + +template<> +struct cdac_data +{ + static constexpr size_t Buckets = offsetof(HashMap, m_rgBuckets); }; //--------------------------------------------------------------------------------------- diff --git a/src/coreclr/vm/readytoruninfo.h b/src/coreclr/vm/readytoruninfo.h index a58bbb41b90095..2777d5fbc98e34 100644 --- a/src/coreclr/vm/readytoruninfo.h +++ b/src/coreclr/vm/readytoruninfo.h @@ -28,7 +28,7 @@ class ReadyToRunCoreInfo PTR_PEImageLayout m_pLayout; PTR_READYTORUN_CORE_HEADER m_pCoreHeader; Volatile m_fForbidLoadILBodyFixups; - + public: ReadyToRunCoreInfo(); ReadyToRunCoreInfo(PEImageLayout * pLayout, READYTORUN_CORE_HEADER * pCoreHeader); @@ -123,7 +123,7 @@ class ReadyToRunInfo PTR_READYTORUN_IMPORT_SECTION m_pImportSections; DWORD m_nImportSections; - bool m_readyToRunCodeDisabled; // Is + bool m_readyToRunCodeDisabled; NativeFormat::NativeReader m_nativeReader; NativeFormat::NativeArray m_methodDefEntryPoints; @@ -335,8 +335,20 @@ class ReadyToRunInfo PTR_MethodDesc GetMethodDescForEntryPointInNativeImage(PCODE entryPoint); void SetMethodDescForEntryPointInNativeImage(PCODE entryPoint, PTR_MethodDesc methodDesc); - + PTR_ReadyToRunCoreInfo GetComponentInfo() { return dac_cast(&m_component); } + + friend struct ::cdac_data; +}; + +template<> +struct cdac_data +{ + static constexpr size_t CompositeInfo = offsetof(ReadyToRunInfo, m_pCompositeInfo); + static constexpr size_t NumRuntimeFunctions = offsetof(ReadyToRunInfo, m_nRuntimeFunctions); + static constexpr size_t RuntimeFunctions = offsetof(ReadyToRunInfo, m_pRuntimeFunctions); + static constexpr size_t DelayLoadMethodCallThunks = offsetof(ReadyToRunInfo, m_pSectionDelayLoadMethodCallThunks); + static constexpr size_t EntryPointToMethodDescMap = offsetof(ReadyToRunInfo, m_entryPointToMethodDescMap); }; class DynamicHelpers diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/DataType.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/DataType.cs index 60ee363c81d250..71076275b7fee4 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/DataType.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/DataType.cs @@ -66,5 +66,10 @@ public enum DataType ILCodeVersioningState, NativeCodeVersionNode, ProfControlBlock, - ILCodeVersionNode + ILCodeVersionNode, + ReadyToRunInfo, + ImageDataDirectory, + RuntimeFunction, + HashMap, + Bucket, } diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/Properties/InternalsVisibleTo.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/Properties/InternalsVisibleTo.cs new file mode 100644 index 00000000000000..444c0839674889 --- /dev/null +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/Properties/InternalsVisibleTo.cs @@ -0,0 +1,7 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Runtime.CompilerServices; + +// Allows Moq framework to mock internal types (for example, contract interfaces) +[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7")] diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/CodePointerUtils.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/CodePointerUtils.cs new file mode 100644 index 00000000000000..b0b5d10f4ac742 --- /dev/null +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/CodePointerUtils.cs @@ -0,0 +1,45 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Diagnostics; +using Microsoft.Diagnostics.DataContractReader.Contracts; + +namespace Microsoft.Diagnostics.DataContractReader; + +internal static class CodePointerUtils +{ + private const uint Arm32ThumbBit = 1; + + internal static TargetCodePointer CodePointerFromAddress(TargetPointer address, Target target) + { + IPlatformMetadata metadata = target.Contracts.PlatformMetadata; + CodePointerFlags flags = metadata.GetCodePointerFlags(); + if (flags.HasFlag(CodePointerFlags.HasArm32ThumbBit)) + { + return new TargetCodePointer(address.Value | Arm32ThumbBit); + } + else if (flags.HasFlag(CodePointerFlags.HasArm64PtrAuth)) + { + throw new NotImplementedException($"{nameof(CodePointerFromAddress)}: ARM64 with pointer authentication"); + } + Debug.Assert(flags == default); + return new TargetCodePointer(address.Value); + } + + internal static TargetPointer AddressFromCodePointer(TargetCodePointer code, Target target) + { + IPlatformMetadata metadata = target.Contracts.PlatformMetadata; + CodePointerFlags flags = metadata.GetCodePointerFlags(); + if (flags.HasFlag(CodePointerFlags.HasArm32ThumbBit)) + { + return new TargetPointer(code.Value & ~Arm32ThumbBit); + } + else if (flags.HasFlag(CodePointerFlags.HasArm64PtrAuth)) + { + throw new NotImplementedException($"{nameof(AddressFromCodePointer)}: ARM64 with pointer authentication"); + } + Debug.Assert(flags == default); + return new TargetPointer(code.Value); + } +} diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Constants.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Constants.cs index 85d29e0eef271a..81913e30eaeff9 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Constants.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Constants.cs @@ -43,5 +43,8 @@ internal static class Globals internal const string StubCodeBlockLast = nameof(StubCodeBlockLast); internal const string PlatformMetadata = nameof(PlatformMetadata); internal const string ProfilerControlBlock = nameof(ProfilerControlBlock); + + internal const string HashMapSlotsPerBucket = nameof(HashMapSlotsPerBucket); + internal const string HashMapValueMask = nameof(HashMapValueMask); } } diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManagerBase.EEJitManager.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManagerBase.EEJitManager.cs index eda3c4e3fb2476..4067c714c7eea5 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManagerBase.EEJitManager.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManagerBase.EEJitManager.cs @@ -23,14 +23,15 @@ public override bool GetMethodInfo(RangeSection rangeSection, TargetCodePointer info = null; // EEJitManager::JitCodeToMethodInfo if (rangeSection.IsRangeList) - { return false; - } + + if (rangeSection.Data == null) + throw new ArgumentException(nameof(rangeSection)); + TargetPointer start = FindMethodCode(rangeSection, jittedCodeAddress); if (start == TargetPointer.Null) - { return false; - } + Debug.Assert(start.Value <= jittedCodeAddress.Value); TargetNUInt relativeOffset = new TargetNUInt(jittedCodeAddress.Value - start.Value); // See EEJitManager::GetCodeHeaderFromStartAddress in vm/codeman.h @@ -42,25 +43,21 @@ public override bool GetMethodInfo(RangeSection rangeSection, TargetCodePointer } TargetPointer codeHeaderAddress = Target.ReadPointer(codeHeaderIndirect); Data.RealCodeHeader realCodeHeader = Target.ProcessedData.GetOrAdd(codeHeaderAddress); - info = new CodeBlock(start.Value, codeHeaderOffset, relativeOffset, realCodeHeader, rangeSection.Data!.JitManager); + info = new CodeBlock(start.Value, realCodeHeader.MethodDesc, relativeOffset, rangeSection.Data!.JitManager); return true; } private TargetPointer FindMethodCode(RangeSection rangeSection, TargetCodePointer jittedCodeAddress) { // EEJitManager::FindMethodCode - if (rangeSection.Data == null) - { - throw new InvalidOperationException(); - } + Debug.Assert(rangeSection.Data != null); + if (!rangeSection.IsCodeHeap) - { throw new InvalidOperationException("RangeSection is not a code heap"); - } + TargetPointer heapListAddress = rangeSection.Data.HeapList; Data.CodeHeapListNode heapListNode = Target.ProcessedData.GetOrAdd(heapListAddress); return _nibbleMap.FindMethodCode(heapListNode, jittedCodeAddress); } - } } diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManagerBase.ReadyToRunJitManager.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManagerBase.ReadyToRunJitManager.cs new file mode 100644 index 00000000000000..68bc4c74933144 --- /dev/null +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManagerBase.ReadyToRunJitManager.cs @@ -0,0 +1,137 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using Microsoft.Diagnostics.DataContractReader.ExecutionManagerHelpers; + +namespace Microsoft.Diagnostics.DataContractReader.Contracts; + +internal partial class ExecutionManagerBase : IExecutionManager +{ + private class ReadyToRunJitManager : JitManager + { + private readonly uint _runtimeFunctionSize; + private readonly PtrHashMapLookup _lookup; + + public ReadyToRunJitManager(Target target) : base(target) + { + _runtimeFunctionSize = Target.GetTypeInfo(DataType.RuntimeFunction).Size!.Value; + _lookup = PtrHashMapLookup.Create(target); + } + + public override bool GetMethodInfo(RangeSection rangeSection, TargetCodePointer jittedCodeAddress, [NotNullWhen(true)] out CodeBlock? info) + { + // ReadyToRunJitManager::JitCodeToMethodInfo + if (rangeSection.Data == null) + throw new ArgumentException(nameof(rangeSection)); + + info = default; + Debug.Assert(rangeSection.Data.R2RModule != TargetPointer.Null); + + Data.Module r2rModule = Target.ProcessedData.GetOrAdd(rangeSection.Data.R2RModule); + Debug.Assert(r2rModule.ReadyToRunInfo != TargetPointer.Null); + Data.ReadyToRunInfo r2rInfo = Target.ProcessedData.GetOrAdd(r2rModule.ReadyToRunInfo); + + // Check if address is in a thunk + if (IsStubCodeBlockThunk(rangeSection.Data, r2rInfo, jittedCodeAddress)) + return false; + + // Find the relative address that we are looking for + TargetPointer addr = CodePointerUtils.AddressFromCodePointer(jittedCodeAddress, Target); + TargetPointer imageBase = rangeSection.Data.RangeBegin; + TargetPointer relativeAddr = addr - imageBase; + + int index = GetRuntimeFunctionIndexForAddress(r2rInfo, relativeAddr); + if (index < 0) + return false; + + bool featureEHFunclets = Target.ReadGlobal(Constants.Globals.FeatureEHFunclets) != 0; + if (featureEHFunclets) + { + // TODO: [cdac] Look up in hot/cold mapping lookup table and if the method is in the cold block, + // get the index of the associated hot block. + // HotColdMappingLookupTable::LookupMappingForMethod + // + // while GetMethodDescForEntryPoint for the begin address of function at index is null + // index-- + } + + TargetPointer functionEntry = r2rInfo.RuntimeFunctions + (ulong)(index * _runtimeFunctionSize); + Data.RuntimeFunction function = Target.ProcessedData.GetOrAdd(functionEntry); + + // ReadyToRunInfo::GetMethodDescForEntryPointInNativeImage + TargetCodePointer startAddress = imageBase + function.BeginAddress; + TargetPointer entryPoint = CodePointerUtils.AddressFromCodePointer(startAddress, Target); + + TargetPointer methodDesc = _lookup.GetValue(r2rInfo.EntryPointToMethodDescMap, entryPoint); + Debug.Assert(methodDesc != TargetPointer.Null); + + // TODO: [cdac] Handle method with cold code when computing relative offset + // ReadyToRunJitManager::JitTokenToMethodRegionInfo + TargetNUInt relativeOffset = new TargetNUInt(addr - startAddress); + + info = new CodeBlock(startAddress.Value, methodDesc, relativeOffset, rangeSection.Data!.JitManager); + return true; + } + + private bool IsStubCodeBlockThunk(Data.RangeSection rangeSection, Data.ReadyToRunInfo r2rInfo, TargetCodePointer jittedCodeAddress) + { + if (r2rInfo.DelayLoadMethodCallThunks == TargetPointer.Null) + return false; + + // Check if the address is in the region containing thunks for READYTORUN_HELPER_DelayLoad_MethodCall + Data.ImageDataDirectory thunksData = Target.ProcessedData.GetOrAdd(r2rInfo.DelayLoadMethodCallThunks); + ulong rva = jittedCodeAddress - rangeSection.RangeBegin; + return thunksData.VirtualAddress <= rva && rva < thunksData.VirtualAddress + thunksData.Size; + } + + private int GetRuntimeFunctionIndexForAddress(Data.ReadyToRunInfo r2rInfo, TargetPointer relativeAddress) + { + // NativeUnwindInfoLookupTable::LookupUnwindInfoForMethod + uint start = 0; + uint end = r2rInfo.NumRuntimeFunctions - 1; + relativeAddress = CodePointerUtils.CodePointerFromAddress(relativeAddress, Target).AsTargetPointer; + + // Entries are sorted. Binary search until we get to 10 or fewer items. + while (end - start > 10) + { + uint middle = start + (end - start) / 2; + Data.RuntimeFunction func = GetRuntimeFunction(r2rInfo, middle); + if (relativeAddress < func.BeginAddress) + { + end = middle - 1; + } + else + { + start = middle; + } + } + + // Find the runtime function that contains the address of interest + for (uint i = start; i <= end; ++i) + { + // Entries are terminated by a sentinel value of -1, so we can index one past the end safely. + // Read as a runtime function, its begin address is 0xffffffff (always > relative address). + // See RuntimeFunctionsTableNode.GetData in RuntimeFunctionsTableNode.cs + Data.RuntimeFunction nextFunc = GetRuntimeFunction(r2rInfo, i + 1); + if (relativeAddress >= nextFunc.BeginAddress) + continue; + + Data.RuntimeFunction func = GetRuntimeFunction(r2rInfo, i); + if (relativeAddress >= func.BeginAddress) + return (int)i; + } + + return -1; + } + + private Data.RuntimeFunction GetRuntimeFunction(Data.ReadyToRunInfo r2rInfo, uint index) + { + TargetPointer first = r2rInfo.RuntimeFunctions; + TargetPointer addr = first + (ulong)(index * _runtimeFunctionSize); + return Target.ProcessedData.GetOrAdd(addr); + } + } +} diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManagerBase.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManagerBase.cs index 01d4925dccf26f..e28c4b30071cf9 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManagerBase.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManagerBase.cs @@ -31,28 +31,20 @@ public ExecutionManagerBase(Target target, Data.RangeSectionMap topRangeSectionM } // Note, because of RelativeOffset, this code info is per code pointer, not per method - // TODO: rethink whether this makes sense. We don't need to copy the runtime's notion of EECodeInfo verbatim private class CodeBlock { - private readonly int _codeHeaderOffset; - public TargetCodePointer StartAddress { get; } - // note: this is the address of the pointer to the "real code header", you need to - // dereference it to get the address of _codeHeaderData - public TargetPointer CodeHeaderAddress => StartAddress.Value - (ulong)_codeHeaderOffset; - private Data.RealCodeHeader _codeHeaderData; + public TargetPointer MethodDescAddress { get; } public TargetPointer JitManagerAddress { get; } public TargetNUInt RelativeOffset { get; } - public CodeBlock(TargetCodePointer startAddress, int codeHeaderOffset, TargetNUInt relativeOffset, Data.RealCodeHeader codeHeaderData, TargetPointer jitManagerAddress) + public CodeBlock(TargetCodePointer startAddress, TargetPointer methodDesc, TargetNUInt relativeOffset, TargetPointer jitManagerAddress) { - _codeHeaderOffset = codeHeaderOffset; StartAddress = startAddress; - _codeHeaderData = codeHeaderData; + MethodDescAddress = methodDesc; RelativeOffset = relativeOffset; JitManagerAddress = jitManagerAddress; } - public TargetPointer MethodDescAddress => _codeHeaderData.MethodDesc; public bool Valid => JitManagerAddress != TargetPointer.Null; } @@ -62,6 +54,7 @@ private enum RangeSectionFlags : int CodeHeap = 0x02, RangeList = 0x04, } + private abstract class JitManager { public Target Target { get; } @@ -72,18 +65,6 @@ protected JitManager(Target target) } public abstract bool GetMethodInfo(RangeSection rangeSection, TargetCodePointer jittedCodeAddress, [NotNullWhen(true)] out CodeBlock? info); - - } - - private class ReadyToRunJitManager : JitManager - { - public ReadyToRunJitManager(Target target) : base(target) - { - } - public override bool GetMethodInfo(RangeSection rangeSection, TargetCodePointer jittedCodeAddress, [NotNullWhen(true)] out CodeBlock? info) - { - throw new NotImplementedException(); // TODO(cdac): ReadyToRunJitManager::JitCodeToMethodInfo - } } private sealed class RangeSection @@ -185,18 +166,16 @@ private JitManager GetJitManager(Data.RangeSection rangeSectionData) TargetPointer IExecutionManager.GetMethodDesc(CodeBlockHandle codeInfoHandle) { if (!_codeInfos.TryGetValue(codeInfoHandle.Address, out CodeBlock? info)) - { - throw new InvalidOperationException("EECodeInfo not found"); - } + throw new InvalidOperationException($"{nameof(CodeBlock)} not found for {codeInfoHandle.Address}"); + return info.MethodDescAddress; } TargetCodePointer IExecutionManager.GetStartAddress(CodeBlockHandle codeInfoHandle) { if (!_codeInfos.TryGetValue(codeInfoHandle.Address, out CodeBlock? info)) - { - throw new InvalidOperationException("EECodeInfo not found"); - } + throw new InvalidOperationException($"{nameof(CodeBlock)} not found for {codeInfoHandle.Address}"); + return info.StartAddress; } } diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/Helpers/HashMapLookup.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/Helpers/HashMapLookup.cs new file mode 100644 index 00000000000000..d039ce9c94eb6b --- /dev/null +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/Helpers/HashMapLookup.cs @@ -0,0 +1,98 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Diagnostics; + +namespace Microsoft.Diagnostics.DataContractReader.ExecutionManagerHelpers; + +internal sealed class HashMapLookup +{ + internal enum SpecialKeys : uint + { + Empty = 0, + Deleted = 1, + InvalidEntry = unchecked((uint)~0), + } + + public static HashMapLookup Create(Target target) + => new HashMapLookup(target); + + private readonly Target _target; + private readonly ulong _valueMask; + + private HashMapLookup(Target target) + { + _target = target; + _valueMask = target.ReadGlobal(Constants.Globals.HashMapValueMask); + } + + public TargetPointer GetValue(TargetPointer mapAddress, TargetPointer key) + { + Data.HashMap map = _target.ProcessedData.GetOrAdd(mapAddress); + + // First pointer of Buckets is actually the number of buckets + uint size = _target.Read(map.Buckets); + HashFunction(key, size, out uint seed, out uint increment); + + // HashMap::LookupValue + uint bucketSize = _target.GetTypeInfo(DataType.Bucket).Size!.Value; + TargetPointer buckets = map.Buckets + bucketSize; + for (int i = 0; i < size; i++) + { + Data.Bucket bucket = _target.ProcessedData.GetOrAdd(buckets + bucketSize * (seed % size)); + for (int slotIdx = 0; slotIdx < bucket.Keys.Length; slotIdx++) + { + if (bucket.Keys[slotIdx] != key) + continue; + + return bucket.Values[slotIdx] & _valueMask; + } + + seed += increment; + + // We didn't find a match and there is no collision + if (!IsCollision(bucket)) + break; + } + + return new TargetPointer((uint)SpecialKeys.InvalidEntry); + } + + internal static void HashFunction(TargetPointer key, uint size, out uint seed, out uint increment) + { + // HashMap::HashFunction + seed = (uint)(key >> 2); + increment = (uint)(1 + (((uint)(key >> 5) + 1) % (size - 1))); + Debug.Assert(increment > 0 && increment < size); + } + + private bool IsCollision(Data.Bucket bucket) + { + return (bucket.Values[0] & ~_valueMask) != 0; + } +} + +internal sealed class PtrHashMapLookup +{ + public static PtrHashMapLookup Create(Target target) + => new PtrHashMapLookup(target); + + private readonly HashMapLookup _lookup; + private PtrHashMapLookup(Target target) + { + _lookup = HashMapLookup.Create(target); + } + + public TargetPointer GetValue(TargetPointer mapAddress, TargetPointer key) + { + // See PtrHashMap::SanitizeKey in hash.h + key = key > (uint)HashMapLookup.SpecialKeys.Deleted ? key : key + 100; + + TargetPointer value = _lookup.GetValue(mapAddress, key); + + // PtrHashMap shifts values right by one bit when storing. See PtrHashMap::LookupValue in hash.h + return value != (uint)HashMapLookup.SpecialKeys.InvalidEntry + ? value << 1 + : value; + } +} 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 d72c1b51a5af1c..d13c382e30d65a 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 @@ -1023,21 +1023,6 @@ private TargetPointer GetAddressOfNonVtableSlot(TargetPointer methodDescPointer, return methodDescPointer.Value + offset; } - private TargetCodePointer CodePointerFromAddress(TargetPointer address) - { - IPlatformMetadata metadata = _target.Contracts.PlatformMetadata; - CodePointerFlags flags = metadata.GetCodePointerFlags(); - if (flags.HasFlag(CodePointerFlags.HasArm32ThumbBit)) - { - return new TargetCodePointer(address.Value | 1); - } else if (flags.HasFlag(CodePointerFlags.HasArm64PtrAuth)) - { - throw new NotImplementedException("CodePointerFromAddress: ARM64 with pointer authentication"); - } - Debug.Assert(flags == default); - return new TargetCodePointer(address.Value); - } - TargetCodePointer IRuntimeTypeSystem.GetNativeCode(MethodDescHandle methodDescHandle) { MethodDesc md = _methodDescs[methodDescHandle.Address]; @@ -1049,7 +1034,7 @@ TargetCodePointer IRuntimeTypeSystem.GetNativeCode(MethodDescHandle methodDescHa // This means that *ppCode is not stable. It can turn from non-zero to zero. TargetPointer ppCode = ((IRuntimeTypeSystem)this).GetAddressOfNativeCodeSlot(methodDescHandle); TargetCodePointer pCode = _target.ReadCodePointer(ppCode); - return CodePointerFromAddress(pCode.AsTargetPointer);; + return CodePointerUtils.CodePointerFromAddress(pCode.AsTargetPointer, _target); } if (!md.HasStableEntryPoint || md.HasPrecode) diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Bucket.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Bucket.cs new file mode 100644 index 00000000000000..a560f36d957142 --- /dev/null +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Bucket.cs @@ -0,0 +1,29 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.Diagnostics.DataContractReader.Data; + +internal sealed class Bucket : IData +{ + static Bucket IData.Create(Target target, TargetPointer address) + => new Bucket(target, address); + + public Bucket(Target target, TargetPointer address) + { + Target.TypeInfo type = target.GetTypeInfo(DataType.Bucket); + ulong keysStart = address + (ulong)type.Fields[nameof(Keys)].Offset; + ulong valuesStart = address + (ulong)type.Fields[nameof(Values)].Offset; + + uint numSlots = target.ReadGlobal(Constants.Globals.HashMapSlotsPerBucket); + Keys = new TargetPointer[numSlots]; + Values = new TargetPointer[numSlots]; + for (int i = 0; i < numSlots; i++) + { + Keys[i] = target.ReadPointer(keysStart + (ulong)(i * target.PointerSize)); + Values[i] = target.ReadPointer(valuesStart + (ulong)(i * target.PointerSize)); + } + } + + public TargetPointer[] Keys { get; } + public TargetPointer[] Values { get; } +} diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/HashMap.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/HashMap.cs new file mode 100644 index 00000000000000..3276fdefd569d9 --- /dev/null +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/HashMap.cs @@ -0,0 +1,19 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.Diagnostics.DataContractReader.Data; + +internal sealed class HashMap : IData +{ + static HashMap IData.Create(Target target, TargetPointer address) + => new HashMap(target, address); + + public HashMap(Target target, TargetPointer address) + { + Target.TypeInfo type = target.GetTypeInfo(DataType.HashMap); + + Buckets = target.ReadPointer(address + (ulong)type.Fields[nameof(Buckets)].Offset); + } + + public TargetPointer Buckets { get; } +} diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/ImageDataDirectory.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/ImageDataDirectory.cs new file mode 100644 index 00000000000000..d3717f5eea481b --- /dev/null +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/ImageDataDirectory.cs @@ -0,0 +1,21 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.Diagnostics.DataContractReader.Data; + +internal sealed class ImageDataDirectory : IData +{ + static ImageDataDirectory IData.Create(Target target, TargetPointer address) + => new ImageDataDirectory(target, address); + + public ImageDataDirectory(Target target, TargetPointer address) + { + Target.TypeInfo type = target.GetTypeInfo(DataType.ImageDataDirectory); + + VirtualAddress = target.Read(address + (ulong)type.Fields[nameof(VirtualAddress)].Offset); + Size = target.Read(address + (ulong)type.Fields[nameof(Size)].Offset); + } + + public uint VirtualAddress { get; } + public uint Size { get; } +} diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Module.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Module.cs index ec19a12f103800..1341f0e73de992 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Module.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Module.cs @@ -25,6 +25,7 @@ public Module(Target target, TargetPointer address) DynamicMetadata = target.ReadPointer(address + (ulong)type.Fields[nameof(DynamicMetadata)].Offset); Path = target.ReadPointer(address + (ulong)type.Fields[nameof(Path)].Offset); FileName = target.ReadPointer(address + (ulong)type.Fields[nameof(FileName)].Offset); + ReadyToRunInfo = target.ReadPointer(address + (ulong)type.Fields[nameof(ReadyToRunInfo)].Offset); FieldDefToDescMap = address + (ulong)type.Fields[nameof(FieldDefToDescMap)].Offset; ManifestModuleReferencesMap = address + (ulong)type.Fields[nameof(ManifestModuleReferencesMap)].Offset; @@ -43,6 +44,7 @@ public Module(Target target, TargetPointer address) public TargetPointer DynamicMetadata { get; init; } public TargetPointer Path { get; init; } public TargetPointer FileName { get; init; } + public TargetPointer ReadyToRunInfo { get; init; } public TargetPointer FieldDefToDescMap { get; init; } public TargetPointer ManifestModuleReferencesMap { get; init; } diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/ReadyToRunInfo.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/ReadyToRunInfo.cs new file mode 100644 index 00000000000000..e64bf88f267117 --- /dev/null +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/ReadyToRunInfo.cs @@ -0,0 +1,36 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.Diagnostics.DataContractReader.Data; + +internal sealed class ReadyToRunInfo : IData +{ + static ReadyToRunInfo IData.Create(Target target, TargetPointer address) + => new ReadyToRunInfo(target, address); + + private readonly Target _target; + + public ReadyToRunInfo(Target target, TargetPointer address) + { + _target = target; + Target.TypeInfo type = target.GetTypeInfo(DataType.ReadyToRunInfo); + + CompositeInfo = target.ReadPointer(address + (ulong)type.Fields[nameof(CompositeInfo)].Offset); + + NumRuntimeFunctions = target.Read(address + (ulong)type.Fields[nameof(NumRuntimeFunctions)].Offset); + RuntimeFunctions = target.ReadPointer(address + (ulong)type.Fields[nameof(RuntimeFunctions)].Offset); + + DelayLoadMethodCallThunks = target.ReadPointer(address + (ulong)type.Fields[nameof(DelayLoadMethodCallThunks)].Offset); + + // Map is from the composite info pointer (set to itself for non-multi-assembly composite images) + EntryPointToMethodDescMap = CompositeInfo + (ulong)type.Fields[nameof(EntryPointToMethodDescMap)].Offset; + } + + internal TargetPointer CompositeInfo { get; } + + public uint NumRuntimeFunctions { get; } + public TargetPointer RuntimeFunctions { get; } + + public TargetPointer DelayLoadMethodCallThunks { get; } + public TargetPointer EntryPointToMethodDescMap { get; } +} diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/RuntimeFunction.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/RuntimeFunction.cs new file mode 100644 index 00000000000000..a95cc6e7647fe8 --- /dev/null +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/RuntimeFunction.cs @@ -0,0 +1,19 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.Diagnostics.DataContractReader.Data; + +internal sealed class RuntimeFunction : IData +{ + static RuntimeFunction IData.Create(Target target, TargetPointer address) + => new RuntimeFunction(target, address); + + public RuntimeFunction(Target target, TargetPointer address) + { + Target.TypeInfo type = target.GetTypeInfo(DataType.RuntimeFunction); + + BeginAddress = target.Read(address + (ulong)type.Fields[nameof(BeginAddress)].Offset); + } + + public uint BeginAddress { get; } +} diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/RuntimeTypeSystemHelpers/MethodValidation.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/RuntimeTypeSystemHelpers/MethodValidation.cs index 3471b7ffba8d57..b167270158feb5 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/RuntimeTypeSystemHelpers/MethodValidation.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/RuntimeTypeSystemHelpers/MethodValidation.cs @@ -167,22 +167,6 @@ private TargetPointer GetAddressOfNonVtableSlot(TargetPointer methodDescPointer, return methodDescPointer.Value + offset; } - // FIXME[cdac]: this is copypasted from RuntimeTypeSystem_1 - put it in a a shared class instead - private TargetCodePointer CodePointerFromAddress(TargetPointer address) - { - IPlatformMetadata metadata = _target.Contracts.PlatformMetadata; - CodePointerFlags flags = metadata.GetCodePointerFlags(); - if (flags.HasFlag(CodePointerFlags.HasArm32ThumbBit)) - { - return new TargetCodePointer(address.Value | 1); - } else if (flags.HasFlag(CodePointerFlags.HasArm64PtrAuth)) - { - throw new NotImplementedException("CodePointerFromAddress: ARM64 with pointer authentication"); - } - Debug.Assert(flags == default); - return new TargetCodePointer(address.Value); - } - private TargetCodePointer GetCodePointer(TargetPointer methodDescPointer, NonValidatedMethodDesc umd) { // TODO(cdac): _ASSERTE(!IsDefaultInterfaceMethod() || HasNativeCodeSlot()); @@ -194,7 +178,7 @@ private TargetCodePointer GetCodePointer(TargetPointer methodDescPointer, NonVal TargetPointer ppCode = GetAddrOfNativeCodeSlot(methodDescPointer, umd); TargetCodePointer pCode = _target.ReadCodePointer(ppCode); - return CodePointerFromAddress(pCode.AsTargetPointer); + return CodePointerUtils.CodePointerFromAddress(pCode.AsTargetPointer, _target); } if (!umd.HasStableEntryPoint || umd.HasPrecode) diff --git a/src/native/managed/cdacreader/tests/CodeVersionsTests.cs b/src/native/managed/cdacreader/tests/CodeVersionsTests.cs index c2efe33c6fbc4a..a7628695731433 100644 --- a/src/native/managed/cdacreader/tests/CodeVersionsTests.cs +++ b/src/native/managed/cdacreader/tests/CodeVersionsTests.cs @@ -240,15 +240,14 @@ public CVTestTarget(MockTarget.Architecture arch, IReadOnlyCollection? codeBlocks = null, IReadOnlyCollection? modules = null, ReadFromTargetDelegate reader = null, - Dictionary? typeInfoCache = null) : base(arch) { + Dictionary? typeInfoCache = null) + : base(arch, reader) + { IExecutionManager mockExecutionManager = new MockExecutionManager(codeBlocks ?? []); IRuntimeTypeSystem mockRuntimeTypeSystem = new MockRuntimeTypeSystem(this, methodDescs ?? [], methodTables ?? []); ILoader loader = new MockLoader(modules ?? []); - if (reader != null) - SetDataReader(reader); if (typeInfoCache != null) SetTypeInfoCache(typeInfoCache); - SetDataCache(new DefaultDataCache(this)); IContractFactory cvfactory = new CodeVersionsFactory(); SetContracts(new TestRegistry() { CodeVersionsContract = new (() => cvfactory.CreateContract(this, 1)), diff --git a/src/native/managed/cdacreader/tests/ExecutionManager/ExecutionManagerTestBuilder.cs b/src/native/managed/cdacreader/tests/ExecutionManager/ExecutionManagerTestBuilder.cs index 6a3e32dd49cb66..5e2228efd80d95 100644 --- a/src/native/managed/cdacreader/tests/ExecutionManager/ExecutionManagerTestBuilder.cs +++ b/src/native/managed/cdacreader/tests/ExecutionManager/ExecutionManagerTestBuilder.cs @@ -23,9 +23,10 @@ public struct AllocationRange // nibble maps for various range section fragments are allocated in this range public ulong NibbleMapStart; public ulong NibbleMapEnd; - // "RealCodeHeader" objects for jitted methods are allocated in this range - public ulong CodeHeaderStart; - public ulong CodeHeaderEnd; + // "RealCodeHeader" objects for jitted methods and the module, info, and runtime functions for R2R + // are allocated in this range + public ulong ExecutionManagerStart; + public ulong ExecutionManagerEnd; } public static readonly AllocationRange DefaultAllocationRange = new AllocationRange { @@ -33,10 +34,10 @@ public struct AllocationRange RangeSectionMapEnd = 0x00de_0000, NibbleMapStart = 0x00ee_0000, NibbleMapEnd = 0x00ef_0000, - CodeHeaderStart = 0x0033_4000, - CodeHeaderEnd = 0x0033_5000, + ExecutionManagerStart = 0x0033_4000, + ExecutionManagerEnd = 0x0033_5000, }; - internal class RangeSectionMapTestBuilder + internal class RangeSectionMapTestBuilder { const ulong DefaultTopLevelAddress = 0x0000_1000u; // arbitrary const int EntriesPerMapLevel = 256; // for now its fixed at 256, see codeman.h RangeSectionMap::entriesPerMapLevel @@ -174,88 +175,123 @@ public static RangeSectionMapTestBuilder CreateRangeSection(MockTarget.Architect return new RangeSectionMapTestBuilder(arch); } + private static readonly MockDescriptors.TypeFields RangeSectionMapFields = new() + { + DataType = DataType.RangeSectionMap, + Fields = + [ + new(nameof(Data.RangeSectionMap.TopLevelData), DataType.pointer), + ] + }; + + private static readonly MockDescriptors.TypeFields RangeSectionFragmentFields = new() + { + DataType = DataType.RangeSectionFragment, + Fields = + [ + new(nameof(Data.RangeSectionFragment.RangeBegin), DataType.pointer), + new(nameof(Data.RangeSectionFragment.RangeEndOpen), DataType.pointer), + new(nameof(Data.RangeSectionFragment.RangeSection), DataType.pointer), + new(nameof(Data.RangeSectionFragment.Next), DataType.pointer) + ] + }; + + private static readonly MockDescriptors.TypeFields RangeSectionFields = new() + { + DataType = DataType.RangeSection, + Fields = + [ + new(nameof(Data.RangeSection.RangeBegin), DataType.pointer), + new(nameof(Data.RangeSection.RangeEndOpen), DataType.pointer), + new(nameof(Data.RangeSection.NextForDelete), DataType.pointer), + new(nameof(Data.RangeSection.JitManager), DataType.pointer), + new(nameof(Data.RangeSection.Flags), DataType.int32), + new(nameof(Data.RangeSection.HeapList), DataType.pointer), + new(nameof(Data.RangeSection.R2RModule), DataType.pointer), + ] + }; + + private static readonly MockDescriptors.TypeFields CodeHeapListNodeFields = new() + { + DataType = DataType.CodeHeapListNode, + Fields = + [ + new(nameof(Data.CodeHeapListNode.Next), DataType.pointer), + new(nameof(Data.CodeHeapListNode.StartAddress), DataType.pointer), + new(nameof(Data.CodeHeapListNode.EndAddress), DataType.pointer), + new(nameof(Data.CodeHeapListNode.MapBase), DataType.pointer), + new(nameof(Data.CodeHeapListNode.HeaderMap), DataType.pointer), + ] + }; + + private static readonly MockDescriptors.TypeFields RealCodeHeaderFields = new() + { + DataType = DataType.RealCodeHeader, + Fields = + [ + new(nameof(Data.RealCodeHeader.MethodDesc), DataType.pointer), + ] + }; + + private static readonly MockDescriptors.TypeFields RuntimeFunctionFields = new() + { + DataType = DataType.RuntimeFunction, + Fields = + [ + new(nameof(Data.RuntimeFunction.BeginAddress), DataType.uint32), + ] + }; + + private static MockDescriptors.TypeFields ReadyToRunInfoFields(TargetTestHelpers helpers) => new() + { + DataType = DataType.ReadyToRunInfo, + Fields = + [ + new(nameof(Data.ReadyToRunInfo.CompositeInfo), DataType.pointer), + new(nameof(Data.ReadyToRunInfo.NumRuntimeFunctions), DataType.uint32), + new(nameof(Data.ReadyToRunInfo.RuntimeFunctions), DataType.pointer), + new(nameof(Data.ReadyToRunInfo.DelayLoadMethodCallThunks), DataType.pointer), + new(nameof(Data.ReadyToRunInfo.EntryPointToMethodDescMap), DataType.Unknown, helpers.LayoutFields(MockDescriptors.HashMap.HashMapFields.Fields).Stride), + ] + }; + internal int Version { get;} internal MockMemorySpace.Builder Builder { get; } + internal Dictionary Types { get; } + private readonly RangeSectionMapTestBuilder _rsmBuilder; private readonly MockMemorySpace.BumpAllocator _rangeSectionMapAllocator; private readonly MockMemorySpace.BumpAllocator _nibbleMapAllocator; - private readonly MockMemorySpace.BumpAllocator _codeHeaderAllocator; + private readonly MockMemorySpace.BumpAllocator _allocator; - internal readonly Dictionary TypeInfoCache = new(); + internal ExecutionManagerTestBuilder(int version, MockTarget.Architecture arch, AllocationRange allocationRange) + : this(version, new MockMemorySpace.Builder(new TargetTestHelpers(arch)), allocationRange) + { } - internal ExecutionManagerTestBuilder(int version, MockTarget.Architecture arch, AllocationRange allocationRange) : this(version, new MockMemorySpace.Builder(new TargetTestHelpers(arch)), allocationRange) - {} - - internal ExecutionManagerTestBuilder(int version, MockMemorySpace.Builder builder, AllocationRange allocationRange, Dictionary? typeInfoCache = null) + internal ExecutionManagerTestBuilder(int version, MockMemorySpace.Builder builder, AllocationRange allocationRange) { Version = version; Builder = builder; _rsmBuilder = new RangeSectionMapTestBuilder(ExecutionManagerCodeRangeMapAddress, builder); _rangeSectionMapAllocator = Builder.CreateAllocator(allocationRange.RangeSectionMapStart, allocationRange.RangeSectionMapEnd); _nibbleMapAllocator = Builder.CreateAllocator(allocationRange.NibbleMapStart, allocationRange.NibbleMapEnd); - _codeHeaderAllocator = Builder.CreateAllocator(allocationRange.CodeHeaderStart, allocationRange.CodeHeaderEnd); - TypeInfoCache = typeInfoCache ?? CreateTypeInfoCache(Builder.TargetTestHelpers); - } - - internal static Dictionary CreateTypeInfoCache(TargetTestHelpers targetTestHelpers) - { - Dictionary typeInfoCache = new(); - AddToTypeInfoCache(targetTestHelpers, typeInfoCache); - return typeInfoCache; - } - - internal static void AddToTypeInfoCache(TargetTestHelpers targetTestHelpers, Dictionary typeInfoCache) - { - var layout = targetTestHelpers.LayoutFields([ - (nameof(Data.RangeSectionMap.TopLevelData), DataType.pointer), - ]); - typeInfoCache[DataType.RangeSectionMap] = new Target.TypeInfo() { - Fields = layout.Fields, - Size = layout.Stride, - }; - layout = targetTestHelpers.LayoutFields([ - (nameof(Data.RangeSectionFragment.RangeBegin), DataType.pointer), - (nameof(Data.RangeSectionFragment.RangeEndOpen), DataType.pointer), - (nameof(Data.RangeSectionFragment.RangeSection), DataType.pointer), - (nameof(Data.RangeSectionFragment.Next), DataType.pointer) - ]); - typeInfoCache[DataType.RangeSectionFragment] = new Target.TypeInfo() { - Fields = layout.Fields, - Size = layout.Stride, - }; - layout = targetTestHelpers.LayoutFields([ - (nameof(Data.RangeSection.RangeBegin), DataType.pointer), - (nameof(Data.RangeSection.RangeEndOpen), DataType.pointer), - (nameof(Data.RangeSection.NextForDelete), DataType.pointer), - (nameof(Data.RangeSection.JitManager), DataType.pointer), - (nameof(Data.RangeSection.Flags), DataType.int32), - (nameof(Data.RangeSection.HeapList), DataType.pointer), - (nameof(Data.RangeSection.R2RModule), DataType.pointer), - ]); - typeInfoCache[DataType.RangeSection] = new Target.TypeInfo() { - Fields = layout.Fields, - Size = layout.Stride, - }; - layout = targetTestHelpers.LayoutFields([ - (nameof(Data.CodeHeapListNode.Next), DataType.pointer), - (nameof(Data.CodeHeapListNode.StartAddress), DataType.pointer), - (nameof(Data.CodeHeapListNode.EndAddress), DataType.pointer), - (nameof(Data.CodeHeapListNode.MapBase), DataType.pointer), - (nameof(Data.CodeHeapListNode.HeaderMap), DataType.pointer), - ]); - typeInfoCache[DataType.CodeHeapListNode] = new Target.TypeInfo() { - Fields = layout.Fields, - Size = layout.Stride, - }; - layout = targetTestHelpers.LayoutFields([ - (nameof(Data.RealCodeHeader.MethodDesc), DataType.pointer), - ]); - typeInfoCache[DataType.RealCodeHeader] = new Target.TypeInfo() { - Fields = layout.Fields, - Size = layout.Stride, - }; + _allocator = Builder.CreateAllocator(allocationRange.ExecutionManagerStart, allocationRange.ExecutionManagerEnd); + Types = MockDescriptors.GetTypesForTypeFields( + Builder.TargetTestHelpers, + [ + RangeSectionMapFields, + RangeSectionFragmentFields, + RangeSectionFields, + CodeHeapListNodeFields, + RealCodeHeaderFields, + RuntimeFunctionFields, + MockDescriptors.HashMap.HashMapFields, + MockDescriptors.HashMap.BucketFields(Builder.TargetTestHelpers), + ReadyToRunInfoFields(Builder.TargetTestHelpers), + MockDescriptors.ModuleFields, + ]); } internal NibbleMapTestBuilderBase CreateNibbleMap(ulong codeRangeStart, uint codeRangeSize) @@ -289,7 +325,7 @@ public JittedCodeRange AllocateJittedCodeRange(ulong codeRangeStart, uint codeRa public TargetPointer AddRangeSection(JittedCodeRange jittedCodeRange, TargetPointer jitManagerAddress, TargetPointer codeHeapListNodeAddress) { - var tyInfo = TypeInfoCache[DataType.RangeSection]; + var tyInfo = Types[DataType.RangeSection]; uint rangeSectionSize = tyInfo.Size.Value; MockMemorySpace.HeapFragment rangeSection = _rangeSectionMapAllocator.Allocate(rangeSectionSize, "RangeSection"); Builder.AddHeapFragment(rangeSection); @@ -306,9 +342,24 @@ public TargetPointer AddRangeSection(JittedCodeRange jittedCodeRange, TargetPoin return rangeSection.Address; } + public TargetPointer AddReadyToRunRangeSection(JittedCodeRange jittedCodeRange, TargetPointer jitManagerAddress, TargetPointer r2rModule) + { + var tyInfo = Types[DataType.RangeSection]; + uint rangeSectionSize = tyInfo.Size.Value; + MockMemorySpace.HeapFragment rangeSection = _rangeSectionMapAllocator.Allocate(rangeSectionSize, "RangeSection"); + Builder.AddHeapFragment(rangeSection); + int pointerSize = Builder.TargetTestHelpers.PointerSize; + Span rs = Builder.BorrowAddressRange(rangeSection.Address, (int)rangeSectionSize); + Builder.TargetTestHelpers.WritePointer(rs.Slice(tyInfo.Fields[nameof(Data.RangeSection.RangeBegin)].Offset, pointerSize), jittedCodeRange.RangeStart); + Builder.TargetTestHelpers.WritePointer(rs.Slice(tyInfo.Fields[nameof(Data.RangeSection.RangeEndOpen)].Offset, pointerSize), jittedCodeRange.RangeEnd); + Builder.TargetTestHelpers.WritePointer(rs.Slice(tyInfo.Fields[nameof(Data.RangeSection.R2RModule)].Offset, pointerSize), r2rModule); + Builder.TargetTestHelpers.WritePointer(rs.Slice(tyInfo.Fields[nameof(Data.RangeSection.JitManager)].Offset, pointerSize), jitManagerAddress); + return rangeSection.Address; + } + public TargetPointer AddRangeSectionFragment(JittedCodeRange jittedCodeRange, TargetPointer rangeSectionAddress) { - var tyInfo = TypeInfoCache[DataType.RangeSectionFragment]; + var tyInfo = Types[DataType.RangeSectionFragment]; uint rangeSectionFragmentSize = tyInfo.Size.Value; MockMemorySpace.HeapFragment rangeSectionFragment = _rangeSectionMapAllocator.Allocate(rangeSectionFragmentSize, "RangeSectionFragment"); // FIXME: this shouldn't really be called InsertAddressRange, but maybe InsertRangeSectionFragment? @@ -326,7 +377,7 @@ public TargetPointer AddRangeSectionFragment(JittedCodeRange jittedCodeRange, Ta public TargetPointer AddCodeHeapListNode(TargetPointer next, TargetPointer startAddress, TargetPointer endAddress, TargetPointer mapBase, TargetPointer headerMap) { - var tyInfo = TypeInfoCache[DataType.CodeHeapListNode]; + var tyInfo = Types[DataType.CodeHeapListNode]; uint codeHeapListNodeSize = tyInfo.Size.Value; MockMemorySpace.HeapFragment codeHeapListNode = _rangeSectionMapAllocator.Allocate (codeHeapListNodeSize, "CodeHeapListNode"); Builder.AddHeapFragment(codeHeapListNode); @@ -358,18 +409,66 @@ public TargetCodePointer AddJittedMethod(JittedCodeRange jittedCodeRange, uint c { (MockMemorySpace.HeapFragment methodFragment, TargetCodePointer codeStart) = AllocateJittedMethod(jittedCodeRange, codeSize); - MockMemorySpace.HeapFragment codeHeaderFragment = _codeHeaderAllocator.Allocate(RealCodeHeaderSize, "RealCodeHeader"); + MockMemorySpace.HeapFragment codeHeaderFragment = _allocator.Allocate(RealCodeHeaderSize, "RealCodeHeader"); Builder.AddHeapFragment(codeHeaderFragment); Span mfPtr = Builder.BorrowAddressRange(methodFragment.Address, (int)CodeHeaderSize); Builder.TargetTestHelpers.WritePointer(mfPtr.Slice(0, Builder.TargetTestHelpers.PointerSize), codeHeaderFragment.Address); Span chf = Builder.BorrowAddressRange(codeHeaderFragment.Address, RealCodeHeaderSize); - var tyInfo = TypeInfoCache[DataType.RealCodeHeader]; + var tyInfo = Types[DataType.RealCodeHeader]; Builder.TargetTestHelpers.WritePointer(chf.Slice(tyInfo.Fields[nameof(Data.RealCodeHeader.MethodDesc)].Offset, Builder.TargetTestHelpers.PointerSize), methodDescAddress); return codeStart; } + public TargetPointer AddReadyToRunInfo(uint[] runtimeFunctions) + { + TargetTestHelpers helpers = Builder.TargetTestHelpers; + + // Add the array of runtime functions + uint numRuntimeFunctions = (uint)runtimeFunctions.Length; + Target.TypeInfo runtimeFunctionType = Types[DataType.RuntimeFunction]; + uint runtimeFunctionSize = runtimeFunctionType.Size.Value; + MockMemorySpace.HeapFragment runtimeFunctionsFragment = _allocator.Allocate((numRuntimeFunctions + 1) * runtimeFunctionSize, $"RuntimeFunctions[{numRuntimeFunctions}]"); + Builder.AddHeapFragment(runtimeFunctionsFragment); + for (uint i = 0; i < numRuntimeFunctions; i++) + { + Span func = Builder.BorrowAddressRange(runtimeFunctionsFragment.Address + i * runtimeFunctionSize, (int)runtimeFunctionSize); + helpers.Write(func.Slice(runtimeFunctionType.Fields[nameof(Data.RuntimeFunction.BeginAddress)].Offset, sizeof(uint)), runtimeFunctions[i]); + } + + // Runtime function entries are terminated by a sentinel value of -1 + Span sentinel = Builder.BorrowAddressRange(runtimeFunctionsFragment.Address + numRuntimeFunctions * runtimeFunctionSize, (int)runtimeFunctionSize); + helpers.Write(sentinel.Slice(runtimeFunctionType.Fields[nameof(Data.RuntimeFunction.BeginAddress)].Offset, sizeof(uint)), ~0u); + + // Add ReadyToRunInfo + Target.TypeInfo r2rInfoType = Types[DataType.ReadyToRunInfo]; + MockMemorySpace.HeapFragment r2rInfo = _allocator.Allocate(r2rInfoType.Size.Value, "ReadyToRunInfo"); + Builder.AddHeapFragment(r2rInfo); + Span data = r2rInfo.Data; + + // Point composite info at itself + helpers.WritePointer(data.Slice(r2rInfoType.Fields[nameof(Data.ReadyToRunInfo.CompositeInfo)].Offset, helpers.PointerSize), r2rInfo.Address); + + // Point at the runtime functions + helpers.Write(data.Slice(r2rInfoType.Fields[nameof(Data.ReadyToRunInfo.NumRuntimeFunctions)].Offset, sizeof(uint)), numRuntimeFunctions); + helpers.WritePointer(data.Slice(r2rInfoType.Fields[nameof(Data.ReadyToRunInfo.RuntimeFunctions)].Offset, helpers.PointerSize), runtimeFunctionsFragment.Address); + + return r2rInfo.Address; + } + + public TargetPointer AddReadyToRunModule(TargetPointer r2rInfo) + { + TargetTestHelpers helpers = Builder.TargetTestHelpers; + + Target.TypeInfo moduleType = Types[DataType.Module]; + MockMemorySpace.HeapFragment r2rModule = _allocator.Allocate(moduleType.Size.Value, "R2R Module"); + Builder.AddHeapFragment(r2rModule); + helpers.WritePointer(r2rModule.Data.AsSpan().Slice(moduleType.Fields[nameof(Data.Module.ReadyToRunInfo)].Offset, helpers.PointerSize), r2rInfo); + + return r2rModule.Address; + } + public void MarkCreated() => Builder.MarkCreated(); } diff --git a/src/native/managed/cdacreader/tests/ExecutionManager/ExecutionManagerTests.cs b/src/native/managed/cdacreader/tests/ExecutionManager/ExecutionManagerTests.cs index df151ee05dcac6..78f34095132df9 100644 --- a/src/native/managed/cdacreader/tests/ExecutionManager/ExecutionManagerTests.cs +++ b/src/native/managed/cdacreader/tests/ExecutionManager/ExecutionManagerTests.cs @@ -5,12 +5,12 @@ using System.Collections.Generic; using Microsoft.Diagnostics.DataContractReader.Contracts; +using Moq; namespace Microsoft.Diagnostics.DataContractReader.UnitTests.ExecutionManager; public class ExecutionManagerTests { - internal class ExecutionManagerTestTarget : TestPlaceholderTarget { private readonly ulong _topRangeSectionMap; @@ -20,19 +20,19 @@ public static ExecutionManagerTestTarget FromBuilder(ExecutionManagerTestBuilder var arch = emBuilder.Builder.TargetTestHelpers.Arch; ReadFromTargetDelegate reader = emBuilder.Builder.GetReadContext().ReadFromTarget; var topRangeSectionMap = ExecutionManagerTestBuilder.ExecutionManagerCodeRangeMapAddress; - var typeInfo = emBuilder.TypeInfoCache; + var typeInfo = emBuilder.Types; return new ExecutionManagerTestTarget(emBuilder.Version, arch, reader, topRangeSectionMap, typeInfo); } - public ExecutionManagerTestTarget(int version, MockTarget.Architecture arch, ReadFromTargetDelegate dataReader, TargetPointer topRangeSectionMap, Dictionary typeInfoCache) : base(arch) + public ExecutionManagerTestTarget(int version, MockTarget.Architecture arch, ReadFromTargetDelegate dataReader, TargetPointer topRangeSectionMap, Dictionary typeInfoCache) + : base(arch, dataReader) { _topRangeSectionMap = topRangeSectionMap; - SetDataReader(dataReader); SetTypeInfoCache(typeInfoCache); - SetDataCache(new DefaultDataCache(this)); IContractFactory emfactory = new ExecutionManagerFactory(); SetContracts(new TestRegistry() { ExecutionManagerContract = new (() => emfactory.CreateContract(this, version)), + PlatformMetadataContract = new (() => new Mock().Object) }); } public override TargetPointer ReadGlobalPointer(string global) @@ -54,18 +54,28 @@ public override T ReadGlobal(string name) if (typeof(T) == typeof(byte)) return (T)(object)(byte)0x0Fu; break; + case Constants.Globals.FeatureEHFunclets: + if (typeof(T) == typeof(byte)) + return (T)(object)(byte)1; + break; + case Constants.Globals.HashMapValueMask: + if (typeof(T) == typeof(ulong)) + return (T)(object)(PointerSize == 4 ? 0x7FFFFFFFu : 0x7FFFFFFFFFFFFFFFu); + break; + case Constants.Globals.HashMapSlotsPerBucket: + if (typeof(T) == typeof(uint)) + return (T)(object)4u; + break; default: break; } return base.ReadGlobal(name); - } - } [Theory] [MemberData(nameof(StdArchAllVersions))] - public void LookupNull(int version, MockTarget.Architecture arch) + public void GetCodeBlockHandle_Null(int version, MockTarget.Architecture arch) { ExecutionManagerTestBuilder emBuilder = new (version, arch, ExecutionManagerTestBuilder.DefaultAllocationRange); emBuilder.MarkCreated(); @@ -79,7 +89,7 @@ public void LookupNull(int version, MockTarget.Architecture arch) [Theory] [MemberData(nameof(StdArchAllVersions))] - public void LookupNonNullMissing(int version, MockTarget.Architecture arch) + public void GetCodeBlockHandle_NoRangeSections(int version, MockTarget.Architecture arch) { ExecutionManagerTestBuilder emBuilder = new (version, arch, ExecutionManagerTestBuilder.DefaultAllocationRange); emBuilder.MarkCreated(); @@ -93,7 +103,7 @@ public void LookupNonNullMissing(int version, MockTarget.Architecture arch) [Theory] [MemberData(nameof(StdArchAllVersions))] - public void LookupNonNullOneRangeOneMethod(int version, MockTarget.Architecture arch) + public void GetMethodDesc_OneRangeOneMethod(int version, MockTarget.Architecture arch) { const ulong codeRangeStart = 0x0a0a_0000u; // arbitrary const uint codeRangeSize = 0xc000u; // arbitrary @@ -143,7 +153,7 @@ public void LookupNonNullOneRangeOneMethod(int version, MockTarget.Architecture [Theory] [MemberData(nameof(StdArchAllVersions))] - public void LookupNullOneRangeZeroMethod(int version, MockTarget.Architecture arch) + public void GetCodeBlockHandle_OneRangeZeroMethod(int version, MockTarget.Architecture arch) { const ulong codeRangeStart = 0x0a0a_0000u; // arbitrary const uint codeRangeSize = 0xc000u; // arbitrary @@ -179,6 +189,155 @@ public void LookupNullOneRangeZeroMethod(int version, MockTarget.Architecture ar Assert.Null(eeInfo); } + [Theory] + [MemberData(nameof(StdArchAllVersions))] + public void GetCodeBlockHandle_R2R_NoRuntimeFunctionMatch(int version, MockTarget.Architecture arch) + { + const ulong codeRangeStart = 0x0a0a_0000u; // arbitrary + const uint codeRangeSize = 0xc000u; // arbitrary + TargetPointer jitManagerAddress = new(0x000b_ff00); // arbitrary + + ExecutionManagerTestBuilder emBuilder = new(version, arch, ExecutionManagerTestBuilder.DefaultAllocationRange); + var jittedCode = emBuilder.AllocateJittedCodeRange(codeRangeStart, codeRangeSize); + + uint runtimeFunction = 0x100; + + TargetPointer r2rInfo = emBuilder.AddReadyToRunInfo([runtimeFunction]); + MockDescriptors.HashMap hashMapBuilder = new(emBuilder.Builder); + hashMapBuilder.PopulatePtrMap( + r2rInfo + (uint)emBuilder.Types[DataType.ReadyToRunInfo].Fields[nameof(Data.ReadyToRunInfo.EntryPointToMethodDescMap)].Offset, + []); + + TargetPointer r2rModule = emBuilder.AddReadyToRunModule(r2rInfo); + TargetPointer rangeSectionAddress = emBuilder.AddReadyToRunRangeSection(jittedCode, jitManagerAddress, r2rModule); + _ = emBuilder.AddRangeSectionFragment(jittedCode, rangeSectionAddress); + + emBuilder.MarkCreated(); + + Target target = ExecutionManagerTestTarget.FromBuilder(emBuilder); + + IExecutionManager em = target.Contracts.ExecutionManager; + Assert.NotNull(em); + + // Before any functions + var handle = em.GetCodeBlockHandle(codeRangeStart + runtimeFunction - 1); + Assert.Null(handle); + } + + [Theory] + [MemberData(nameof(StdArchAllVersions))] + public void GetMethodDesc_R2R_OneRuntimeFunction(int version, MockTarget.Architecture arch) + { + const ulong codeRangeStart = 0x0a0a_0000u; // arbitrary + const uint codeRangeSize = 0xc000u; // arbitrary + TargetPointer jitManagerAddress = new(0x000b_ff00); // arbitrary + + TargetPointer expectedMethodDescAddress = new TargetPointer(0x0101_aaa0); + + ExecutionManagerTestBuilder emBuilder = new(version, arch, ExecutionManagerTestBuilder.DefaultAllocationRange); + var jittedCode = emBuilder.AllocateJittedCodeRange(codeRangeStart, codeRangeSize); + + uint expectedRuntimeFunction = 0x100; + + TargetPointer r2rInfo = emBuilder.AddReadyToRunInfo([expectedRuntimeFunction]); + MockDescriptors.HashMap hashMapBuilder = new(emBuilder.Builder); + hashMapBuilder.PopulatePtrMap( + r2rInfo + (uint)emBuilder.Types[DataType.ReadyToRunInfo].Fields[nameof(Data.ReadyToRunInfo.EntryPointToMethodDescMap)].Offset, + [(jittedCode.RangeStart + expectedRuntimeFunction, expectedMethodDescAddress)]); + + TargetPointer r2rModule = emBuilder.AddReadyToRunModule(r2rInfo); + TargetPointer rangeSectionAddress = emBuilder.AddReadyToRunRangeSection(jittedCode, jitManagerAddress, r2rModule); + _ = emBuilder.AddRangeSectionFragment(jittedCode, rangeSectionAddress); + + emBuilder.MarkCreated(); + + Target target = ExecutionManagerTestTarget.FromBuilder(emBuilder); + + IExecutionManager em = target.Contracts.ExecutionManager; + Assert.NotNull(em); + + { + // Function start + var handle = em.GetCodeBlockHandle(codeRangeStart + expectedRuntimeFunction); + Assert.NotNull(handle); + TargetPointer actualMethodDesc = em.GetMethodDesc(handle.Value); + Assert.Equal(expectedMethodDescAddress, actualMethodDesc); + } + { + // Past function start + var handle = em.GetCodeBlockHandle(codeRangeStart + expectedRuntimeFunction * 2); + Assert.NotNull(handle); + TargetPointer actualMethodDesc = em.GetMethodDesc(handle.Value); + Assert.Equal(expectedMethodDescAddress, actualMethodDesc); + } + } + + [Theory] + [MemberData(nameof(StdArchAllVersions))] + public void GetMethodDesc_R2R_MultipleRuntimeFunctions(int version, MockTarget.Architecture arch) + { + const ulong codeRangeStart = 0x0a0a_0000u; // arbitrary + const uint codeRangeSize = 0xc000u; // arbitrary + TargetPointer jitManagerAddress = new(0x000b_ff00); // arbitrary + + TargetPointer[] methodDescAddresses = [ 0x0101_aaa0, 0x0201_aaa0]; + + ExecutionManagerTestBuilder emBuilder = new(version, arch, ExecutionManagerTestBuilder.DefaultAllocationRange); + var jittedCode = emBuilder.AllocateJittedCodeRange(codeRangeStart, codeRangeSize); + + uint[] runtimeFunctions = [ 0x100, 0xc00 ]; + + TargetPointer r2rInfo = emBuilder.AddReadyToRunInfo(runtimeFunctions); + MockDescriptors.HashMap hashMapBuilder = new(emBuilder.Builder); + hashMapBuilder.PopulatePtrMap( + r2rInfo + (uint)emBuilder.Types[DataType.ReadyToRunInfo].Fields[nameof(Data.ReadyToRunInfo.EntryPointToMethodDescMap)].Offset, + [ + (jittedCode.RangeStart + runtimeFunctions[0], methodDescAddresses[0]), + (jittedCode.RangeStart + runtimeFunctions[1], methodDescAddresses[1]), + ]); + + TargetPointer r2rModule = emBuilder.AddReadyToRunModule(r2rInfo); + TargetPointer rangeSectionAddress = emBuilder.AddReadyToRunRangeSection(jittedCode, jitManagerAddress, r2rModule); + _ = emBuilder.AddRangeSectionFragment(jittedCode, rangeSectionAddress); + + emBuilder.MarkCreated(); + + Target target = ExecutionManagerTestTarget.FromBuilder(emBuilder); + + IExecutionManager em = target.Contracts.ExecutionManager; + Assert.NotNull(em); + + { + // Match first function + var handle = em.GetCodeBlockHandle(codeRangeStart + runtimeFunctions[0]); + Assert.NotNull(handle); + TargetPointer actualMethodDesc = em.GetMethodDesc(handle.Value); + Assert.Equal(methodDescAddresses[0], actualMethodDesc); + } + { + // After first function, before second - match first function + uint betweenFirstAndSecond = runtimeFunctions[0] + (runtimeFunctions[1] - runtimeFunctions[0]) / 2; + var handle = em.GetCodeBlockHandle(codeRangeStart + betweenFirstAndSecond); + Assert.NotNull(handle); + TargetPointer actualMethodDesc = em.GetMethodDesc(handle.Value); + Assert.Equal(methodDescAddresses[0], actualMethodDesc); + } + { + // Match second function + var handle = em.GetCodeBlockHandle(codeRangeStart + runtimeFunctions[1]); + Assert.NotNull(handle); + TargetPointer actualMethodDesc = em.GetMethodDesc(handle.Value); + Assert.Equal(methodDescAddresses[1], actualMethodDesc); + } + { + // After second/last function - match second/last function + var handle = em.GetCodeBlockHandle(codeRangeStart + runtimeFunctions[1] * 2); + Assert.NotNull(handle); + TargetPointer actualMethodDesc = em.GetMethodDesc(handle.Value); + Assert.Equal(methodDescAddresses[1], actualMethodDesc); + } + } + public static IEnumerable StdArchAllVersions() { const int highestVersion = 2; diff --git a/src/native/managed/cdacreader/tests/ExecutionManager/HashMapTests.cs b/src/native/managed/cdacreader/tests/ExecutionManager/HashMapTests.cs new file mode 100644 index 00000000000000..61b207abad2399 --- /dev/null +++ b/src/native/managed/cdacreader/tests/ExecutionManager/HashMapTests.cs @@ -0,0 +1,128 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Xunit; + +using Microsoft.Diagnostics.DataContractReader.ExecutionManagerHelpers; + +namespace Microsoft.Diagnostics.DataContractReader.UnitTests.ExecutionManager; + +public class HashMapTests +{ + internal class HashMapTestTarget : TestPlaceholderTarget + { + private readonly (string Name, ulong Value, string? Type)[] _globals; + + public HashMapTestTarget(MockTarget.Architecture arch, MockMemorySpace.ReadContext readContext, MockDescriptors.HashMap hashMap) + : base (arch, readContext.ReadFromTarget) + { + _globals = hashMap.Globals; + SetTypeInfoCache(hashMap.Types); + } + + public override T ReadGlobal(string name) + { + foreach (var global in _globals) + { + if (global.Name == name) + return T.CreateChecked(global.Value); + } + + return base.ReadGlobal(name); + } + } + + [Theory] + [ClassData(typeof(MockTarget.StdArch))] + public void GetValue(MockTarget.Architecture arch) + { + MockMemorySpace.Builder builder = new(new TargetTestHelpers(arch)); + MockDescriptors.HashMap hashMap = new(builder); + (TargetPointer Key, TargetPointer Value)[] entries = + [ + (0x100, 0x10), + (0x200, 0x20), + (0x300, 0x30), + (0x400, 0x40), + ]; + TargetPointer mapAddress = hashMap.CreateMap(entries); + TargetPointer ptrMapAddress = hashMap.CreatePtrMap(entries); + builder.MarkCreated(); + + Target target = new HashMapTestTarget(arch, builder.GetReadContext(), hashMap); + + var lookup = HashMapLookup.Create(target); + var ptrLookup = PtrHashMapLookup.Create(target); + foreach (var entry in entries) + { + TargetPointer value = lookup.GetValue(mapAddress, entry.Key); + Assert.Equal(entry.Value, value); + + TargetPointer ptrValue = ptrLookup.GetValue(ptrMapAddress, entry.Key); + Assert.Equal(entry.Value, ptrValue); + } + } + + [Theory] + [ClassData(typeof(MockTarget.StdArch))] + public void GetValue_Collision(MockTarget.Architecture arch) + { + MockMemorySpace.Builder builder = new(new TargetTestHelpers(arch)); + MockDescriptors.HashMap hashMap = new(builder); + + // Keys are chosen to result in a collision based on HashMapLookup.HashFunction and the size + // of the map (based on the number of entries - see MockDescriptors.HashMap.PopulateMap). + // They result in the same seed and there are more entries than HashMapSlotsPerBucket + (TargetPointer Key, TargetPointer Value) firstEntryDuplicateKey = (0x04, 0x40); + (TargetPointer Key, TargetPointer Value)[] entries = + [ + firstEntryDuplicateKey, + (0x04, 0x41), + (0x05, 0x50), + (0x06, 0x60), + (0x07, 0x70), + ]; + TargetPointer mapAddress = hashMap.CreateMap(entries); + TargetPointer ptrMapAddress = hashMap.CreatePtrMap(entries); + builder.MarkCreated(); + + Target target = new HashMapTestTarget(arch, builder.GetReadContext(), hashMap); + + var lookup = HashMapLookup.Create(target); + var ptrLookup = PtrHashMapLookup.Create(target); + foreach (var entry in entries) + { + TargetPointer expectedValue = entry.Key == firstEntryDuplicateKey.Key ? firstEntryDuplicateKey.Value : entry.Value; + TargetPointer value = lookup.GetValue(mapAddress, entry.Key); + Assert.Equal(expectedValue, value); + + TargetPointer ptrValue = ptrLookup.GetValue(ptrMapAddress, entry.Key); + Assert.Equal(expectedValue, ptrValue); + } + } + + [Theory] + [ClassData(typeof(MockTarget.StdArch))] + public void GetValue_NoMatch(MockTarget.Architecture arch) + { + MockMemorySpace.Builder builder = new(new TargetTestHelpers(arch)); + MockDescriptors.HashMap hashMap = new(builder); + (TargetPointer Key, TargetPointer Value)[] entries = [(0x100, 0x010)]; + TargetPointer mapAddress = hashMap.CreateMap(entries); + TargetPointer ptrMapAddress = hashMap.CreatePtrMap(entries); + builder.MarkCreated(); + + Target target = new HashMapTestTarget(arch, builder.GetReadContext(), hashMap); + + { + var lookup = HashMapLookup.Create(target); + TargetPointer value = lookup.GetValue(mapAddress, 0x101); + Assert.Equal((uint)HashMapLookup.SpecialKeys.InvalidEntry, value); + } + { + var lookup = PtrHashMapLookup.Create(target); + TargetPointer value = lookup.GetValue(ptrMapAddress, 0x101); + Assert.Equal((uint)HashMapLookup.SpecialKeys.InvalidEntry, value); + } + } +} diff --git a/src/native/managed/cdacreader/tests/ExecutionManager/NibbleMapTests.cs b/src/native/managed/cdacreader/tests/ExecutionManager/NibbleMapTests.cs index c92fc3f1afa4ce..e9c8369ade4104 100644 --- a/src/native/managed/cdacreader/tests/ExecutionManager/NibbleMapTests.cs +++ b/src/native/managed/cdacreader/tests/ExecutionManager/NibbleMapTests.cs @@ -11,13 +11,10 @@ public class NibbleMapTestsBase { internal class NibbleMapTestTarget : TestPlaceholderTarget { - private readonly MockMemorySpace.ReadContext _readContext; - public NibbleMapTestTarget(MockTarget.Architecture arch, MockMemorySpace.ReadContext readContext) : base(arch) + public NibbleMapTestTarget(MockTarget.Architecture arch, MockMemorySpace.ReadContext readContext) + : base(arch, readContext.ReadFromTarget) { - _readContext = readContext; - SetDataReader(_readContext.ReadFromTarget); } - } internal static NibbleMapTestTarget CreateTarget(NibbleMapTestBuilderBase nibbleMapTestBuilder) diff --git a/src/native/managed/cdacreader/tests/ExecutionManager/RangeSectionMapTests.cs b/src/native/managed/cdacreader/tests/ExecutionManager/RangeSectionMapTests.cs index 36d3a5f9647d0a..fed8cc942ad442 100644 --- a/src/native/managed/cdacreader/tests/ExecutionManager/RangeSectionMapTests.cs +++ b/src/native/managed/cdacreader/tests/ExecutionManager/RangeSectionMapTests.cs @@ -13,17 +13,12 @@ public class RangeSectionMapTests { internal class RSMTestTarget : TestPlaceholderTarget { - private readonly MockMemorySpace.ReadContext _readContext; public RSMTestTarget(MockTarget.Architecture arch, MockMemorySpace.ReadContext readContext) - : base (arch) + : base (arch, readContext.ReadFromTarget) { - _readContext = readContext; - SetDataReader(_readContext.ReadFromTarget); } } - - [Theory] [ClassData(typeof(MockTarget.StdArch))] public void TestLookupFail(MockTarget.Architecture arch) diff --git a/src/native/managed/cdacreader/tests/Microsoft.Diagnostics.DataContractReader.Tests.csproj b/src/native/managed/cdacreader/tests/Microsoft.Diagnostics.DataContractReader.Tests.csproj index c12c45e6f1fe84..f00e248f563be6 100644 --- a/src/native/managed/cdacreader/tests/Microsoft.Diagnostics.DataContractReader.Tests.csproj +++ b/src/native/managed/cdacreader/tests/Microsoft.Diagnostics.DataContractReader.Tests.csproj @@ -1,4 +1,4 @@ - + true $(NetCoreAppToolCurrent) @@ -9,8 +9,10 @@ - + + + + diff --git a/src/native/managed/cdacreader/tests/MockDescriptors/MockDescriptors.CodeVersions.cs b/src/native/managed/cdacreader/tests/MockDescriptors/MockDescriptors.CodeVersions.cs index 98d1986bb01f69..a6b833044abd7f 100644 --- a/src/native/managed/cdacreader/tests/MockDescriptors/MockDescriptors.CodeVersions.cs +++ b/src/native/managed/cdacreader/tests/MockDescriptors/MockDescriptors.CodeVersions.cs @@ -19,8 +19,8 @@ public class CodeVersions DataType = DataType.MethodDescVersioningState, Fields = [ - (nameof(Data.MethodDescVersioningState.NativeCodeVersionNode), DataType.pointer), - (nameof(Data.MethodDescVersioningState.Flags), DataType.uint8), + new(nameof(Data.MethodDescVersioningState.NativeCodeVersionNode), DataType.pointer), + new(nameof(Data.MethodDescVersioningState.Flags), DataType.uint8), ] }; @@ -29,11 +29,11 @@ public class CodeVersions DataType = DataType.NativeCodeVersionNode, Fields = [ - (nameof(Data.NativeCodeVersionNode.Next), DataType.pointer), - (nameof(Data.NativeCodeVersionNode.MethodDesc), DataType.pointer), - (nameof(Data.NativeCodeVersionNode.NativeCode), DataType.pointer), - (nameof(Data.NativeCodeVersionNode.Flags), DataType.uint32), - (nameof(Data.NativeCodeVersionNode.ILVersionId), DataType.nuint), + new(nameof(Data.NativeCodeVersionNode.Next), DataType.pointer), + new(nameof(Data.NativeCodeVersionNode.MethodDesc), DataType.pointer), + new(nameof(Data.NativeCodeVersionNode.NativeCode), DataType.pointer), + new(nameof(Data.NativeCodeVersionNode.Flags), DataType.uint32), + new(nameof(Data.NativeCodeVersionNode.ILVersionId), DataType.nuint), ] }; @@ -42,10 +42,10 @@ public class CodeVersions DataType = DataType.ILCodeVersioningState, Fields = [ - (nameof(Data.ILCodeVersioningState.ActiveVersionMethodDef), DataType.uint32), - (nameof(Data.ILCodeVersioningState.ActiveVersionModule), DataType.pointer), - (nameof(Data.ILCodeVersioningState.ActiveVersionKind), DataType.uint32), - (nameof(Data.ILCodeVersioningState.ActiveVersionNode), DataType.pointer), + new(nameof(Data.ILCodeVersioningState.ActiveVersionMethodDef), DataType.uint32), + new(nameof(Data.ILCodeVersioningState.ActiveVersionModule), DataType.pointer), + new(nameof(Data.ILCodeVersioningState.ActiveVersionKind), DataType.uint32), + new(nameof(Data.ILCodeVersioningState.ActiveVersionNode), DataType.pointer), ] }; @@ -54,7 +54,7 @@ public class CodeVersions DataType = DataType.ILCodeVersionNode, Fields = [ - (nameof(Data.ILCodeVersionNode.VersionId), DataType.nuint), + new(nameof(Data.ILCodeVersionNode.VersionId), DataType.nuint), ] }; diff --git a/src/native/managed/cdacreader/tests/MockDescriptors/MockDescriptors.HashMap.cs b/src/native/managed/cdacreader/tests/MockDescriptors/MockDescriptors.HashMap.cs new file mode 100644 index 00000000000000..597d75cf6001fd --- /dev/null +++ b/src/native/managed/cdacreader/tests/MockDescriptors/MockDescriptors.HashMap.cs @@ -0,0 +1,176 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Microsoft.Diagnostics.DataContractReader.UnitTests; + +internal partial class MockDescriptors +{ + public class HashMap + { + private const uint HashMapSlotsPerBucket = 4; + + internal static readonly TypeFields HashMapFields = new TypeFields() + { + DataType = DataType.HashMap, + Fields = + [ + new (nameof(Data.HashMap.Buckets), DataType.pointer), + ] + }; + + internal static TypeFields BucketFields(TargetTestHelpers helpers) => new TypeFields() + { + DataType = DataType.Bucket, + Fields = + [ + new(nameof(Data.Bucket.Keys), DataType.Unknown, HashMapSlotsPerBucket * (uint)helpers.PointerSize), + new(nameof(Data.Bucket.Values), DataType.Unknown, HashMapSlotsPerBucket * (uint)helpers.PointerSize), + ] + }; + + internal Dictionary Types { get; } + internal (string Name, ulong Value, string? Type)[] Globals { get; } + + private const ulong DefaultAllocationRangeStart = 0x0003_0000; + private const ulong DefaultAllocationRangeEnd = 0x0004_0000; + + // See g_rgPrimes in hash.cpp + private static readonly uint[] PossibleSizes = [5, 11, 17, 23, 29, 37]; + + private readonly MockMemorySpace.Builder _builder; + private readonly MockMemorySpace.BumpAllocator _allocator; + + public HashMap(MockMemorySpace.Builder builder) + : this(builder, (DefaultAllocationRangeStart, DefaultAllocationRangeEnd)) + { } + + public HashMap(MockMemorySpace.Builder builder, (ulong Start, ulong End) allocationRange) + { + _builder = builder; + _allocator = _builder.CreateAllocator(allocationRange.Start, allocationRange.End); + Types = GetTypes(); + Globals = + [ + (nameof(Constants.Globals.HashMapSlotsPerBucket), HashMapSlotsPerBucket, "uint32"), + (nameof(Constants.Globals.HashMapValueMask), _builder.TargetTestHelpers.PointerSize == 4 ? 0x7FFFFFFFu : 0x7FFFFFFFFFFFFFFFu, "uint64"), + ]; + } + + private Dictionary GetTypes() + { + return GetTypesForTypeFields( + _builder.TargetTestHelpers, + [ + HashMapFields, + BucketFields(_builder.TargetTestHelpers), + ]); + } + + public TargetPointer CreateMap((TargetPointer Key, TargetPointer Value)[] entries) + { + Target.TypeInfo hashMapType = Types[DataType.HashMap]; + MockMemorySpace.HeapFragment map = _allocator.Allocate(hashMapType.Size!.Value, "HashMap"); + _builder.AddHeapFragment(map); + PopulateMap(map.Address, entries); + return map.Address; + } + + public void PopulateMap(TargetPointer mapAddress, (TargetPointer Key, TargetPointer Value)[] entries) + { + TargetTestHelpers helpers = _builder.TargetTestHelpers; + + // HashMap::NewSize + int requiredSlots = entries.Length * 3 / 2; + uint size = PossibleSizes.Where(i => i > requiredSlots).First(); + + // Allocate the buckets + Target.TypeInfo bucketType = Types[DataType.Bucket]; + uint bucketSize = bucketType.Size!.Value; + + // First bucket is the number of buckets + uint numBuckets = size + 1; + uint totalBucketsSize = bucketSize * numBuckets; + MockMemorySpace.HeapFragment buckets = _allocator.Allocate(totalBucketsSize, $"Buckets[{numBuckets}]"); + _builder.AddHeapFragment(buckets); + helpers.Write(buckets.Data.AsSpan().Slice(0, sizeof(uint)), size); + + const int maxRetry = 8; + foreach ((TargetPointer key, TargetPointer value) in entries) + { + ExecutionManagerHelpers.HashMapLookup.HashFunction(key, size, out uint seed, out uint increment); + + int tryCount = 0; + while (tryCount < maxRetry) + { + Span bucket = buckets.Data.AsSpan().Slice((int)(bucketSize * ((seed % size) + 1))); + if (TryAddEntryToBucket(bucket, key, value)) + break; + + seed += increment; + tryCount++; + } + + if (tryCount >= maxRetry) + throw new InvalidOperationException("HashMap test helper does not handle re-hashing"); + } + + // Update the map to point at the buckets + Target.TypeInfo hashMapType = Types[DataType.HashMap]; + Span map = _builder.BorrowAddressRange(mapAddress, (int)hashMapType.Size!.Value); + helpers.WritePointer(map.Slice(hashMapType.Fields[nameof(Data.HashMap.Buckets)].Offset, helpers.PointerSize), buckets.Address); + } + + public TargetPointer CreatePtrMap((TargetPointer Key, TargetPointer Value)[] entries) + { + // PtrHashMap shifts values right by one bit + (TargetPointer Key, TargetPointer Value)[] ptrMapEntries = entries + .Select(e => (e.Key, new TargetPointer(e.Value >> 1))) + .ToArray(); + return CreateMap(ptrMapEntries); + } + + public void PopulatePtrMap(TargetPointer mapAddress, (TargetPointer Key, TargetPointer Value)[] entries) + { + // PtrHashMap shifts values right by one bit + (TargetPointer Key, TargetPointer Value)[] ptrMapEntries = entries + .Select(e => (e.Key, new TargetPointer(e.Value >> 1))) + .ToArray(); + PopulateMap(mapAddress, ptrMapEntries); + } + + private bool TryAddEntryToBucket(Span bucket, TargetPointer key, TargetPointer value) + { + TargetTestHelpers helpers = _builder.TargetTestHelpers; + Target.TypeInfo bucketType = Types[DataType.Bucket]; + for (int i = 0; i < HashMapSlotsPerBucket; i++) + { + Span keySpan = bucket.Slice(bucketType.Fields[nameof(Data.Bucket.Keys)].Offset + i * helpers.PointerSize, helpers.PointerSize); + if (helpers.ReadPointer(keySpan) != (uint)ExecutionManagerHelpers.HashMapLookup.SpecialKeys.Empty) + continue; + + helpers.WritePointer(keySpan, key); + helpers.WritePointer(bucket.Slice(bucketType.Fields[nameof(Data.Bucket.Values)].Offset + i * helpers.PointerSize, helpers.PointerSize), value); + return true; + } + + // Bucket::SetCollision + ulong valueMask = Globals.Where(g => g.Name == nameof(Constants.Globals.HashMapValueMask)).Select(g => g.Value).First(); + + // Collision bit + Span firstValueSpan = bucket.Slice(bucketType.Fields[nameof(Data.Bucket.Values)].Offset, helpers.PointerSize); + TargetPointer firstValue = helpers.ReadPointer(firstValueSpan); + helpers.WritePointer(firstValueSpan, firstValue | ~valueMask); + + // Has free slots bit + Span secondValueSpan = bucket.Slice(bucketType.Fields[nameof(Data.Bucket.Values)].Offset + helpers.PointerSize, helpers.PointerSize); + TargetPointer secondValue = helpers.ReadPointer(secondValueSpan); + helpers.WritePointer(secondValueSpan, secondValue & valueMask); + + return false; + } + } +} diff --git a/src/native/managed/cdacreader/tests/MockDescriptors/MockDescriptors.MethodDescriptors.cs b/src/native/managed/cdacreader/tests/MockDescriptors/MockDescriptors.MethodDescriptors.cs index 35fff02a1d5281..4bba2e77b23ec8 100644 --- a/src/native/managed/cdacreader/tests/MockDescriptors/MockDescriptors.MethodDescriptors.cs +++ b/src/native/managed/cdacreader/tests/MockDescriptors/MockDescriptors.MethodDescriptors.cs @@ -18,12 +18,12 @@ public class MethodDescriptors DataType = DataType.MethodDesc, Fields = [ - (nameof(Data.MethodDesc.ChunkIndex), DataType.uint8), - (nameof(Data.MethodDesc.Slot), DataType.uint16), - (nameof(Data.MethodDesc.Flags), DataType.uint16), - (nameof(Data.MethodDesc.Flags3AndTokenRemainder), DataType.uint16), - (nameof(Data.MethodDesc.EntryPointFlags), DataType.uint8), - (nameof(Data.MethodDesc.CodeData), DataType.pointer), + new(nameof(Data.MethodDesc.ChunkIndex), DataType.uint8), + new(nameof(Data.MethodDesc.Slot), DataType.uint16), + new(nameof(Data.MethodDesc.Flags), DataType.uint16), + new(nameof(Data.MethodDesc.Flags3AndTokenRemainder), DataType.uint16), + new(nameof(Data.MethodDesc.EntryPointFlags), DataType.uint8), + new(nameof(Data.MethodDesc.CodeData), DataType.pointer), ] }; @@ -32,11 +32,11 @@ public class MethodDescriptors DataType = DataType.MethodDescChunk, Fields = [ - (nameof(Data.MethodDescChunk.MethodTable), DataType.pointer), - (nameof(Data.MethodDescChunk.Next), DataType.pointer), - (nameof(Data.MethodDescChunk.Size), DataType.uint8), - (nameof(Data.MethodDescChunk.Count), DataType.uint8), - (nameof(Data.MethodDescChunk.FlagsAndTokenRange), DataType.uint16) + new(nameof(Data.MethodDescChunk.MethodTable), DataType.pointer), + new(nameof(Data.MethodDescChunk.Next), DataType.pointer), + new(nameof(Data.MethodDescChunk.Size), DataType.uint8), + new(nameof(Data.MethodDescChunk.Count), DataType.uint8), + new(nameof(Data.MethodDescChunk.FlagsAndTokenRange), DataType.uint16) ] }; @@ -59,7 +59,7 @@ internal MethodDescriptors(RuntimeTypeSystem rtsBuilder, Loader loaderBuilder) Types = GetTypes(); Globals = rtsBuilder.Globals.Concat( [ - (nameof(Constants.Globals.MethodDescTokenRemainderBitCount), TokenRemainderBitCount, "uint8"), + new(nameof(Constants.Globals.MethodDescTokenRemainderBitCount), TokenRemainderBitCount, "uint8"), ]).ToArray(); } diff --git a/src/native/managed/cdacreader/tests/MockDescriptors/MockDescriptors.cs b/src/native/managed/cdacreader/tests/MockDescriptors/MockDescriptors.cs index c6f151b896a6ab..cb5bd5271708cf 100644 --- a/src/native/managed/cdacreader/tests/MockDescriptors/MockDescriptors.cs +++ b/src/native/managed/cdacreader/tests/MockDescriptors/MockDescriptors.cs @@ -7,10 +7,10 @@ namespace Microsoft.Diagnostics.DataContractReader.UnitTests; internal partial class MockDescriptors { - private record TypeFields + internal record TypeFields { public DataType DataType; - public (string Name, DataType Type)[] Fields; + public TargetTestHelpers.Field[] Fields; public TypeFields BaseTypeFields; } @@ -19,16 +19,16 @@ private record TypeFields DataType = DataType.MethodTable, Fields = [ - (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), - (nameof(Data.MethodTable.AuxiliaryData), DataType.pointer), + new(nameof(Data.MethodTable.MTFlags), DataType.uint32), + new(nameof(Data.MethodTable.BaseSize), DataType.uint32), + new(nameof(Data.MethodTable.MTFlags2), DataType.uint32), + new(nameof(Data.MethodTable.EEClassOrCanonMT), DataType.nuint), + new(nameof(Data.MethodTable.Module), DataType.pointer), + new(nameof(Data.MethodTable.ParentMethodTable), DataType.pointer), + new(nameof(Data.MethodTable.NumInterfaces), DataType.uint16), + new(nameof(Data.MethodTable.NumVirtuals), DataType.uint16), + new(nameof(Data.MethodTable.PerInstInfo), DataType.pointer), + new(nameof(Data.MethodTable.AuxiliaryData), DataType.pointer), ] }; @@ -37,11 +37,11 @@ private record TypeFields DataType = DataType.EEClass, Fields = [ - (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), + new(nameof(Data.EEClass.MethodTable), DataType.pointer), + new(nameof(Data.EEClass.CorTypeAttr), DataType.uint32), + new(nameof(Data.EEClass.NumMethods), DataType.uint16), + new(nameof(Data.EEClass.InternalCorElementType), DataType.uint8), + new(nameof(Data.EEClass.NumNonVirtualSlots), DataType.uint16), ] }; @@ -50,8 +50,8 @@ private record TypeFields DataType = DataType.MethodTableAuxiliaryData, Fields = [ - (nameof(Data.MethodTableAuxiliaryData.LoaderModule), DataType.pointer), - (nameof(Data.MethodTableAuxiliaryData.OffsetToNonVirtualSlots), DataType.int16), + new(nameof(Data.MethodTableAuxiliaryData.LoaderModule), DataType.pointer), + new(nameof(Data.MethodTableAuxiliaryData.OffsetToNonVirtualSlots), DataType.int16), ] }; @@ -60,7 +60,7 @@ private record TypeFields DataType = DataType.ArrayClass, Fields = [ - (nameof(Data.ArrayClass.Rank), DataType.uint8), + new(nameof(Data.ArrayClass.Rank), DataType.uint8), ], BaseTypeFields = EEClassFields }; @@ -70,7 +70,7 @@ private record TypeFields DataType = DataType.Object, Fields = [ - ("m_pMethTab", DataType.pointer), + new("m_pMethTab", DataType.pointer), ] }; @@ -79,8 +79,8 @@ private record TypeFields DataType = DataType.String, Fields = [ - ("m_StringLength", DataType.uint32), - ("m_FirstChar", DataType.uint16), + new("m_StringLength", DataType.uint32), + new("m_FirstChar", DataType.uint16), ], BaseTypeFields = ObjectFields }; @@ -90,7 +90,7 @@ private record TypeFields DataType = DataType.Array, Fields = [ - ("m_NumComponents", DataType.uint32), + new("m_NumComponents", DataType.uint32), ], BaseTypeFields = ObjectFields }; @@ -100,7 +100,7 @@ private record TypeFields DataType = DataType.SyncTableEntry, Fields = [ - (nameof(Data.SyncTableEntry.SyncBlock), DataType.pointer), + new(nameof(Data.SyncTableEntry.SyncBlock), DataType.pointer), ] }; @@ -109,7 +109,7 @@ private record TypeFields DataType = DataType.SyncBlock, Fields = [ - (nameof(Data.SyncBlock.InteropInfo), DataType.pointer), + new(nameof(Data.SyncBlock.InteropInfo), DataType.pointer), ] }; @@ -118,31 +118,32 @@ private record TypeFields DataType = DataType.InteropSyncBlockInfo, Fields = [ - (nameof(Data.InteropSyncBlockInfo.RCW), DataType.pointer), - (nameof(Data.InteropSyncBlockInfo.CCW), DataType.pointer), + new(nameof(Data.InteropSyncBlockInfo.RCW), DataType.pointer), + new(nameof(Data.InteropSyncBlockInfo.CCW), DataType.pointer), ] }; - private static readonly TypeFields ModuleFields = new TypeFields() + internal static readonly TypeFields ModuleFields = new TypeFields() { DataType = DataType.Module, Fields = [ - (nameof(Data.Module.Assembly), DataType.pointer), - (nameof(Data.Module.Flags), DataType.uint32), - (nameof(Data.Module.Base), DataType.pointer), - (nameof(Data.Module.LoaderAllocator), DataType.pointer), - (nameof(Data.Module.ThunkHeap), DataType.pointer), - (nameof(Data.Module.DynamicMetadata), DataType.pointer), - (nameof(Data.Module.Path), DataType.pointer), - (nameof(Data.Module.FileName), DataType.pointer), - (nameof(Data.Module.FieldDefToDescMap), DataType.pointer), - (nameof(Data.Module.ManifestModuleReferencesMap), DataType.pointer), - (nameof(Data.Module.MemberRefToDescMap), DataType.pointer), - (nameof(Data.Module.MethodDefToDescMap), DataType.pointer), - (nameof(Data.Module.TypeDefToMethodTableMap), DataType.pointer), - (nameof(Data.Module.TypeRefToMethodTableMap), DataType.pointer), - (nameof(Data.Module.MethodDefToILCodeVersioningStateMap), DataType.pointer), + new(nameof(Data.Module.Assembly), DataType.pointer), + new(nameof(Data.Module.Flags), DataType.uint32), + new(nameof(Data.Module.Base), DataType.pointer), + new(nameof(Data.Module.LoaderAllocator), DataType.pointer), + new(nameof(Data.Module.ThunkHeap), DataType.pointer), + new(nameof(Data.Module.DynamicMetadata), DataType.pointer), + new(nameof(Data.Module.Path), DataType.pointer), + new(nameof(Data.Module.FileName), DataType.pointer), + new(nameof(Data.Module.FieldDefToDescMap), DataType.pointer), + new(nameof(Data.Module.ManifestModuleReferencesMap), DataType.pointer), + new(nameof(Data.Module.MemberRefToDescMap), DataType.pointer), + new(nameof(Data.Module.MethodDefToDescMap), DataType.pointer), + new(nameof(Data.Module.TypeDefToMethodTableMap), DataType.pointer), + new(nameof(Data.Module.TypeRefToMethodTableMap), DataType.pointer), + new(nameof(Data.Module.MethodDefToILCodeVersioningStateMap), DataType.pointer), + new(nameof(Data.Module.ReadyToRunInfo), DataType.pointer), ] }; @@ -151,7 +152,7 @@ private record TypeFields DataType = DataType.Assembly, Fields = [ - (nameof(Data.Assembly.IsCollectible), DataType.uint8), + new(nameof(Data.Assembly.IsCollectible), DataType.uint8), ] }; @@ -160,8 +161,8 @@ private record TypeFields DataType = DataType.ExceptionInfo, Fields = [ - (nameof(Data.ExceptionInfo.PreviousNestedInfo), DataType.pointer), - (nameof(Data.ExceptionInfo.ThrownObject), DataType.pointer), + new(nameof(Data.ExceptionInfo.PreviousNestedInfo), DataType.pointer), + new(nameof(Data.ExceptionInfo.ThrownObject), DataType.pointer), ] }; @@ -170,16 +171,16 @@ private record TypeFields DataType = DataType.Thread, Fields = [ - (nameof(Data.Thread.Id), DataType.uint32), - (nameof(Data.Thread.OSId), DataType.nuint), - (nameof(Data.Thread.State), DataType.uint32), - (nameof(Data.Thread.PreemptiveGCDisabled), DataType.uint32), - (nameof(Data.Thread.RuntimeThreadLocals), DataType.pointer), - (nameof(Data.Thread.Frame), DataType.pointer), - (nameof(Data.Thread.TEB), DataType.pointer), - (nameof(Data.Thread.LastThrownObject), DataType.pointer), - (nameof(Data.Thread.LinkNext), DataType.pointer), - (nameof(Data.Thread.ExceptionTracker), DataType.pointer), + new(nameof(Data.Thread.Id), DataType.uint32), + new(nameof(Data.Thread.OSId), DataType.nuint), + new(nameof(Data.Thread.State), DataType.uint32), + new(nameof(Data.Thread.PreemptiveGCDisabled), DataType.uint32), + new(nameof(Data.Thread.RuntimeThreadLocals), DataType.pointer), + new(nameof(Data.Thread.Frame), DataType.pointer), + new(nameof(Data.Thread.TEB), DataType.pointer), + new(nameof(Data.Thread.LastThrownObject), DataType.pointer), + new(nameof(Data.Thread.LinkNext), DataType.pointer), + new(nameof(Data.Thread.ExceptionTracker), DataType.pointer), ] }; @@ -188,16 +189,16 @@ private record TypeFields DataType = DataType.ThreadStore, Fields = [ - (nameof(Data.ThreadStore.ThreadCount), DataType.uint32), - (nameof(Data.ThreadStore.FirstThreadLink), DataType.pointer), - (nameof(Data.ThreadStore.UnstartedCount), DataType.uint32), - (nameof(Data.ThreadStore.BackgroundCount), DataType.uint32), - (nameof(Data.ThreadStore.PendingCount), DataType.uint32), - (nameof(Data.ThreadStore.DeadCount), DataType.uint32), + new(nameof(Data.ThreadStore.ThreadCount), DataType.uint32), + new(nameof(Data.ThreadStore.FirstThreadLink), DataType.pointer), + new(nameof(Data.ThreadStore.UnstartedCount), DataType.uint32), + new(nameof(Data.ThreadStore.BackgroundCount), DataType.uint32), + new(nameof(Data.ThreadStore.PendingCount), DataType.uint32), + new(nameof(Data.ThreadStore.DeadCount), DataType.uint32), ] }; - private static Dictionary GetTypesForTypeFields(TargetTestHelpers helpers, TypeFields[] typeFields) + internal static Dictionary GetTypesForTypeFields(TargetTestHelpers helpers, TypeFields[] typeFields) { Dictionary types = new(); foreach (var toAdd in typeFields) diff --git a/src/native/managed/cdacreader/tests/PrecodeStubsTests.cs b/src/native/managed/cdacreader/tests/PrecodeStubsTests.cs index 4f2dbb3a32974e..1647d5f2639524 100644 --- a/src/native/managed/cdacreader/tests/PrecodeStubsTests.cs +++ b/src/native/managed/cdacreader/tests/PrecodeStubsTests.cs @@ -2,11 +2,13 @@ // The .NET Foundation licenses this file to you under the MIT license. using Xunit; +using Moq; using Microsoft.Diagnostics.DataContractReader.Contracts; using System.Collections.Generic; using System; using System.Reflection; + namespace Microsoft.Diagnostics.DataContractReader.UnitTests; public class PrecodeStubsTests @@ -198,23 +200,23 @@ public PrecodeBuilder(AllocationRange allocationRange, MockMemorySpace.Builder b public void AddToTypeInfoCache(Dictionary typeInfoCache, TargetTestHelpers targetTestHelpers) { var layout = targetTestHelpers.LayoutFields([ - (nameof(Data.PrecodeMachineDescriptor.StubCodePageSize), DataType.uint32), - (nameof(Data.PrecodeMachineDescriptor.OffsetOfPrecodeType), DataType.uint8), - (nameof(Data.PrecodeMachineDescriptor.ReadWidthOfPrecodeType), DataType.uint8), - (nameof(Data.PrecodeMachineDescriptor.ShiftOfPrecodeType), DataType.uint8), - (nameof(Data.PrecodeMachineDescriptor.InvalidPrecodeType), DataType.uint8), - (nameof(Data.PrecodeMachineDescriptor.StubPrecodeType), DataType.uint8), - (nameof(Data.PrecodeMachineDescriptor.PInvokeImportPrecodeType), DataType.uint8), - (nameof(Data.PrecodeMachineDescriptor.FixupPrecodeType), DataType.uint8), - (nameof(Data.PrecodeMachineDescriptor.ThisPointerRetBufPrecodeType), DataType.uint8), + new(nameof(Data.PrecodeMachineDescriptor.StubCodePageSize), DataType.uint32), + new(nameof(Data.PrecodeMachineDescriptor.OffsetOfPrecodeType), DataType.uint8), + new(nameof(Data.PrecodeMachineDescriptor.ReadWidthOfPrecodeType), DataType.uint8), + new(nameof(Data.PrecodeMachineDescriptor.ShiftOfPrecodeType), DataType.uint8), + new(nameof(Data.PrecodeMachineDescriptor.InvalidPrecodeType), DataType.uint8), + new(nameof(Data.PrecodeMachineDescriptor.StubPrecodeType), DataType.uint8), + new(nameof(Data.PrecodeMachineDescriptor.PInvokeImportPrecodeType), DataType.uint8), + new(nameof(Data.PrecodeMachineDescriptor.FixupPrecodeType), DataType.uint8), + new(nameof(Data.PrecodeMachineDescriptor.ThisPointerRetBufPrecodeType), DataType.uint8), ]); typeInfoCache[DataType.PrecodeMachineDescriptor] = new Target.TypeInfo() { Fields = layout.Fields, Size = layout.Stride, }; layout = targetTestHelpers.LayoutFields([ - (nameof(Data.StubPrecodeData.Type), DataType.uint8), - (nameof(Data.StubPrecodeData.MethodDesc), DataType.pointer), + new(nameof(Data.StubPrecodeData.Type), DataType.uint8), + new(nameof(Data.StubPrecodeData.MethodDesc), DataType.pointer), ]); typeInfoCache[DataType.StubPrecodeData] = new Target.TypeInfo() { Fields = layout.Fields, @@ -276,17 +278,6 @@ public TargetCodePointer AddStubPrecodeEntry(string name, PrecodeTestDescriptor internal class PrecodeTestTarget : TestPlaceholderTarget { - private class TestPlatformMetadata : IPlatformMetadata - { - private readonly CodePointerFlags _codePointerFlags; - private readonly TargetPointer _precodeMachineDescriptorAddress; - public TestPlatformMetadata(CodePointerFlags codePointerFlags, TargetPointer precodeMachineDescriptorAddress) { - _codePointerFlags = codePointerFlags; - _precodeMachineDescriptorAddress = precodeMachineDescriptorAddress; - } - TargetPointer IPlatformMetadata.GetPrecodeMachineDescriptor() => _precodeMachineDescriptorAddress; - CodePointerFlags IPlatformMetadata.GetCodePointerFlags() => _codePointerFlags; - } internal readonly TargetPointer PrecodeMachineDescriptorAddress; // hack for this test put the precode machine descriptor at the same address as the PlatformMetadata internal TargetPointer PlatformMetadataAddress => PrecodeMachineDescriptorAddress; @@ -298,16 +289,20 @@ public static PrecodeTestTarget FromBuilder(PrecodeBuilder precodeBuilder) var typeInfo = precodeBuilder.TypeInfoCache; return new PrecodeTestTarget(arch, reader, precodeBuilder.CodePointerFlags, precodeBuilder.MachineDescriptorAddress, typeInfo); } - public PrecodeTestTarget(MockTarget.Architecture arch, ReadFromTargetDelegate reader, CodePointerFlags codePointerFlags, TargetPointer platformMetadataAddress, Dictionary typeInfoCache) : base(arch) { + public PrecodeTestTarget(MockTarget.Architecture arch, ReadFromTargetDelegate reader, CodePointerFlags codePointerFlags, TargetPointer platformMetadataAddress, Dictionary typeInfoCache) + : base(arch, reader) + { PrecodeMachineDescriptorAddress = platformMetadataAddress; SetTypeInfoCache(typeInfoCache); - SetDataCache(new DefaultDataCache(this)); - SetDataReader(reader); IContractFactory precodeFactory = new PrecodeStubsFactory(); + + Mock platformMetadata = new(MockBehavior.Strict); + platformMetadata.Setup(p => p.GetCodePointerFlags()).Returns(codePointerFlags); + platformMetadata.Setup(p => p.GetPrecodeMachineDescriptor()).Returns(PrecodeMachineDescriptorAddress); + SetContracts(new TestRegistry() { - PlatformMetadataContract = new (() => new TestPlatformMetadata(codePointerFlags, PrecodeMachineDescriptorAddress)), + PlatformMetadataContract = new (() => platformMetadata.Object), PrecodeStubsContract = new (() => precodeFactory.CreateContract(this, 1)), - }); } diff --git a/src/native/managed/cdacreader/tests/TargetTestHelpers.cs b/src/native/managed/cdacreader/tests/TargetTestHelpers.cs index 71844fff6f4298..41e47ca83766b7 100644 --- a/src/native/managed/cdacreader/tests/TargetTestHelpers.cs +++ b/src/native/managed/cdacreader/tests/TargetTestHelpers.cs @@ -333,26 +333,28 @@ public readonly struct LayoutResult public readonly uint MaxAlign { get; init; } } + internal record Field(string Name, DataType Type, uint? Size = null); + // 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) + public LayoutResult LayoutFields(Field[] fields) => LayoutFields(FieldLayout.CIsh, fields); // Layout the fields of a structure according to the specified layout style. - public LayoutResult LayoutFields(FieldLayout style, (string Name, DataType Type)[] fields) + public LayoutResult LayoutFields(FieldLayout style, Field[] fields) { 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) + private LayoutResult LayoutFieldsWorker(FieldLayout style, Field[] fields, ref int offset, ref int maxAlign) { Dictionary fieldInfos = new (); for (int i = 0; i < fields.Length; i++) { - var (name, type) = fields[i]; - int size = SizeOfPrimitive(type); + var (name, type, sizeMaybe) = fields[i]; + int size = sizeMaybe.HasValue ? (int)sizeMaybe.Value : SizeOfPrimitive(type); int align = size; if (align > maxAlign) { @@ -379,9 +381,9 @@ private LayoutResult LayoutFieldsWorker(FieldLayout style, (string Name, DataTyp } // 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(Field[] fields, LayoutResult baseClass) => ExtendLayout(FieldLayout.CIsh, fields, baseClass); - public LayoutResult ExtendLayout(FieldLayout fieldLayout, (string Name, DataType Type)[] fields, LayoutResult baseClass) + public LayoutResult ExtendLayout(FieldLayout fieldLayout, Field[] fields, LayoutResult baseClass) { int offset = (int)baseClass.Stride; int maxAlign = (int)baseClass.MaxAlign; diff --git a/src/native/managed/cdacreader/tests/TestPlaceholderTarget.cs b/src/native/managed/cdacreader/tests/TestPlaceholderTarget.cs index 0762f0c95bbf04..7c94e57b011ffa 100644 --- a/src/native/managed/cdacreader/tests/TestPlaceholderTarget.cs +++ b/src/native/managed/cdacreader/tests/TestPlaceholderTarget.cs @@ -6,6 +6,7 @@ using System.Diagnostics.CodeAnalysis; using System.Numerics; using System.Runtime.CompilerServices; +using Moq; namespace Microsoft.Diagnostics.DataContractReader.UnitTests; @@ -23,13 +24,14 @@ internal class TestPlaceholderTarget : Target protected ReadFromTargetDelegate _dataReader = (address, buffer) => throw new NotImplementedException(); #region Setup - public TestPlaceholderTarget(MockTarget.Architecture arch) + public TestPlaceholderTarget(MockTarget.Architecture arch, ReadFromTargetDelegate reader) { IsLittleEndian = arch.IsLittleEndian; PointerSize = arch.Is64Bit ? 8 : 4; - contractRegistry = new TestRegistry();; - dataCache = new TestDataCache(); + contractRegistry = new TestRegistry(); + dataCache = new DefaultDataCache(this); typeInfoCache = null; + _dataReader = reader; } internal void SetContracts(ContractRegistry contracts) @@ -37,20 +39,11 @@ internal void SetContracts(ContractRegistry contracts) contractRegistry = contracts; } - internal void SetDataCache(Target.IDataCache cache) - { - dataCache = cache; - } - internal void SetTypeInfoCache(Dictionary cache) { typeInfoCache = cache; } - internal void SetDataReader(ReadFromTargetDelegate reader) - { - _dataReader = reader; - } #endregion Setup public override int PointerSize { get; } @@ -229,34 +222,8 @@ public TestRegistry() { } public override Contracts.IReJIT ReJIT => ReJITContract.Value ?? throw new NotImplementedException(); } - // a data cache that throws NotImplementedException for all methods, - // useful for subclassing to override specific methods - internal class TestDataCache : Target.IDataCache - { - public TestDataCache() {} - - public virtual T GetOrAdd(TargetPointer address) where T : Data.IData - { - if (TryGet(address.Value, out T? data)) - { - return data; - } - return Add(address.Value); - } - - public virtual bool TryGet(ulong address, [NotNullWhen(true)] out T? data) - { - throw new NotImplementedException(); - } - - protected virtual T Add(ulong address) where T : Data.IData - { - throw new NotImplementedException(); - } - } - // A data cache that stores data in a dictionary and calls IData.Create to construct the data. - internal class DefaultDataCache : Target.IDataCache + private class DefaultDataCache : Target.IDataCache { protected readonly Target _target; protected readonly Dictionary<(ulong, Type), object?> _readDataByAddress = [];