diff --git a/src/native/managed/cdacreader/tests/CodeVersionsTests.cs b/src/native/managed/cdacreader/tests/CodeVersionsTests.cs index 69e39c597feda8..f60f75df5947aa 100644 --- a/src/native/managed/cdacreader/tests/CodeVersionsTests.cs +++ b/src/native/managed/cdacreader/tests/CodeVersionsTests.cs @@ -7,7 +7,7 @@ using Moq; using Xunit; -namespace Microsoft.Diagnostics.DataContractReader.UnitTests; +namespace Microsoft.Diagnostics.DataContractReader.Tests; using MockCodeVersions = MockDescriptors.CodeVersions; diff --git a/src/native/managed/cdacreader/tests/ContractDescriptor/ContractDescriptorBuilder.cs b/src/native/managed/cdacreader/tests/ContractDescriptor/ContractDescriptorBuilder.cs new file mode 100644 index 00000000000000..e22d8653368ebf --- /dev/null +++ b/src/native/managed/cdacreader/tests/ContractDescriptor/ContractDescriptorBuilder.cs @@ -0,0 +1,173 @@ +// 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.Diagnostics.CodeAnalysis; +using System.Diagnostics; +using System.Linq; +using System.Text; + +namespace Microsoft.Diagnostics.DataContractReader.Tests.ContractDescriptor; + +internal class ContractDescriptorBuilder : MockMemorySpace.Builder +{ + // These addresses are arbitrary and are used to store the contract descriptor components. + // They should not overlap with any other heap fragment addresses. + private const ulong ContractDescriptorAddr = 0xaaaaaaaa; + private const uint JsonDescriptorAddr = 0xdddddddd; + private const uint ContractPointerDataAddr = 0xeeeeeeee; + + private bool _created = false; + + private IReadOnlyCollection _contracts; + private IDictionary _types; + private IReadOnlyCollection<(string Name, ulong? Value, uint? IndirectIndex, string? TypeName)> _globals; + private IReadOnlyCollection _indirectValues; + + public ContractDescriptorBuilder(TargetTestHelpers targetTestHelpers) + : base(targetTestHelpers) + { } + + public ContractDescriptorBuilder SetContracts(IReadOnlyCollection contracts) + { + if (_created) + throw new InvalidOperationException("Context already created"); + _contracts = contracts; + return this; + } + + public ContractDescriptorBuilder SetTypes(IDictionary types) + { + if (_created) + throw new InvalidOperationException("Context already created"); + _types = types; + return this; + } + + public ContractDescriptorBuilder SetGlobals(IReadOnlyCollection<(string Name, ulong Value, string? TypeName)> globals) + { + if (_created) + throw new InvalidOperationException("Context already created"); + if (_globals != null) + throw new InvalidOperationException("Globals already set"); + _globals = globals.Select(g => (g.Name, (ulong?)g.Value, (uint?)null, g.TypeName)).ToArray(); + _indirectValues = null; + return this; + } + + public ContractDescriptorBuilder SetGlobals(IReadOnlyCollection<(string Name, ulong? Value, uint? IndirectIndex, string? TypeName)> globals, IReadOnlyCollection indirectValues) + { + if (_created) + throw new InvalidOperationException("Context already created"); + if (_globals != null) + throw new InvalidOperationException("Globals already set"); + _globals = globals; + _indirectValues = indirectValues; + return this; + } + + private MockMemorySpace.HeapFragment CreateContractDescriptor(int jsonLength, int pointerDataCount) + { + byte[] descriptor = new byte[ContractDescriptorHelpers.Size(TargetTestHelpers.Arch.Is64Bit)]; + ContractDescriptorHelpers.Fill(descriptor, TargetTestHelpers.Arch, jsonLength, JsonDescriptorAddr, pointerDataCount, ContractPointerDataAddr); + return new MockMemorySpace.HeapFragment + { + Address = ContractDescriptorAddr, + Data = descriptor, + Name = "ContractDescriptor" + }; + } + + private string MakeContractsJson() + { + if (_contracts.Count == 0) + return string.Empty; + StringBuilder sb = new(); + foreach (var c in _contracts) + { + sb.Append($"\"{c}\": 1,"); + } + Debug.Assert(sb.Length > 0); + sb.Length--; // remove trailing comma + return sb.ToString(); + } + + private (MockMemorySpace.HeapFragment json, MockMemorySpace.HeapFragment pointerData) CreateDataDescriptor() + { + string metadataTypesJson = _types is not null ? ContractDescriptorHelpers.MakeTypesJson(_types) : string.Empty; + string metadataGlobalsJson = _globals is not null ? ContractDescriptorHelpers.MakeGlobalsJson(_globals) : string.Empty; + string interpolatedContracts = _contracts is not null ? MakeContractsJson() : string.Empty; + byte[] jsonBytes = Encoding.UTF8.GetBytes($$""" + { + "version": 0, + "baseline": "empty", + "contracts": { {{interpolatedContracts}} }, + "types": { {{metadataTypesJson}} }, + "globals": { {{metadataGlobalsJson}} } + } + """); + MockMemorySpace.HeapFragment json = new() + { + Address = JsonDescriptorAddr, + Data = jsonBytes, + Name = "JsonDescriptor" + }; + + MockMemorySpace.HeapFragment pointerData; + if (_indirectValues != null) + { + int pointerSize = TargetTestHelpers.PointerSize; + byte[] pointerDataBytes = new byte[_indirectValues.Count * pointerSize]; + int offset = 0; + foreach (var value in _indirectValues) + { + TargetTestHelpers.WritePointer(pointerDataBytes.AsSpan(offset, pointerSize), value); + offset += pointerSize; + } + pointerData = new MockMemorySpace.HeapFragment + { + Address = ContractPointerDataAddr, + Data = pointerDataBytes, + Name = "PointerData" + }; + } + else + { + pointerData = new MockMemorySpace.HeapFragment + { + Address = ContractPointerDataAddr, + Data = Array.Empty(), + Name = "PointerData" + }; + } + return (json, pointerData); + } + + private ulong CreateDescriptorFragments() + { + if (_created) + throw new InvalidOperationException("Context already created"); + + (var json, var pointerData) = CreateDataDescriptor(); + int pointerDataCount = pointerData.Data is null ? 0 : pointerData.Data.Length / TargetTestHelpers.PointerSize; + MockMemorySpace.HeapFragment descriptor = CreateContractDescriptor(json.Data.Length, pointerDataCount); + + AddHeapFragment(descriptor); + AddHeapFragment(json); + if (pointerData.Data.Length > 0) + AddHeapFragment(pointerData); + + _created = true; + return descriptor.Address; + } + + public bool TryCreateTarget([NotNullWhen(true)] out ContractDescriptorTarget? target) + { + if (_created) + throw new InvalidOperationException("Context already created"); + ulong contractDescriptorAddress = CreateDescriptorFragments(); + MockMemorySpace.ReadContext context = GetReadContext(); + return ContractDescriptorTarget.TryCreate(contractDescriptorAddress, context.ReadFromTarget, out target); + } +} diff --git a/src/native/managed/cdacreader/tests/ContractDescriptor/ContractDescriptorHelpers.cs b/src/native/managed/cdacreader/tests/ContractDescriptor/ContractDescriptorHelpers.cs new file mode 100644 index 00000000000000..d17759a92c6aee --- /dev/null +++ b/src/native/managed/cdacreader/tests/ContractDescriptor/ContractDescriptorHelpers.cs @@ -0,0 +1,156 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Buffers.Binary; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; + +namespace Microsoft.Diagnostics.DataContractReader.Tests.ContractDescriptor; + +internal unsafe class ContractDescriptorHelpers +{ + public static int Size(bool is64Bit) => is64Bit ? sizeof(ContractDescriptor64) : sizeof(ContractDescriptor32); + + public static void Fill(Span dest, MockTarget.Architecture arch, int jsonDescriptorSize, uint jsonDescriptorAddr, int pointerDataCount, uint pointerDataAddr) + { + if (arch.Is64Bit) + { + ContractDescriptor64.Fill(dest, arch.IsLittleEndian, jsonDescriptorSize, jsonDescriptorAddr, pointerDataCount, pointerDataAddr); + } + else + { + ContractDescriptor32.Fill(dest, arch.IsLittleEndian, jsonDescriptorSize, jsonDescriptorAddr, pointerDataCount, pointerDataAddr); + } + } + + private struct ContractDescriptor32 + { + public ulong Magic = BitConverter.ToUInt64("DNCCDAC\0"u8); + public uint Flags = 0x2 /*32-bit*/ | 0x1; + public uint DescriptorSize; + public uint Descriptor; + public uint PointerDataCount; + public uint Pad0 = 0; + public uint PointerData; + + public ContractDescriptor32() { } + + public static void Fill(Span dest, bool isLittleEndian, int jsonDescriptorSize, uint jsonDescriptorAddr, int pointerDataCount, uint pointerDataAddr) + { + ContractDescriptor32 descriptor = new() + { + DescriptorSize = (uint)jsonDescriptorSize, + Descriptor = jsonDescriptorAddr, + PointerDataCount = (uint)pointerDataCount, + PointerData = pointerDataAddr, + }; + if (BitConverter.IsLittleEndian != isLittleEndian) + descriptor.ReverseEndianness(); + + MemoryMarshal.AsBytes(MemoryMarshal.CreateSpan(ref descriptor, 1)).CopyTo(dest); + } + + private void ReverseEndianness() + { + Magic = BinaryPrimitives.ReverseEndianness(Magic); + Flags = BinaryPrimitives.ReverseEndianness(Flags); + DescriptorSize = BinaryPrimitives.ReverseEndianness(DescriptorSize); + Descriptor = BinaryPrimitives.ReverseEndianness(Descriptor); + PointerDataCount = BinaryPrimitives.ReverseEndianness(PointerDataCount); + Pad0 = BinaryPrimitives.ReverseEndianness(Pad0); + PointerData = BinaryPrimitives.ReverseEndianness(PointerData); + } + } + + private struct ContractDescriptor64 + { + public ulong Magic = BitConverter.ToUInt64("DNCCDAC\0"u8); + public uint Flags = 0x1; + public uint DescriptorSize; + public ulong Descriptor; + public uint PointerDataCount; + public uint Pad0 = 0; + public ulong PointerData; + + public ContractDescriptor64() { } + + public static void Fill(Span dest, bool isLittleEndian, int jsonDescriptorSize, uint jsonDescriptorAddr, int pointerDataCount, uint pointerDataAddr) + { + ContractDescriptor64 descriptor = new() + { + DescriptorSize = (uint)jsonDescriptorSize, + Descriptor = jsonDescriptorAddr, + PointerDataCount = (uint)pointerDataCount, + PointerData = pointerDataAddr, + }; + if (BitConverter.IsLittleEndian != isLittleEndian) + descriptor.ReverseEndianness(); + + MemoryMarshal.AsBytes(MemoryMarshal.CreateSpan(ref descriptor, 1)).CopyTo(dest); + } + + private void ReverseEndianness() + { + Magic = BinaryPrimitives.ReverseEndianness(Magic); + Flags = BinaryPrimitives.ReverseEndianness(Flags); + DescriptorSize = BinaryPrimitives.ReverseEndianness(DescriptorSize); + Descriptor = BinaryPrimitives.ReverseEndianness(Descriptor); + PointerDataCount = BinaryPrimitives.ReverseEndianness(PointerDataCount); + Pad0 = BinaryPrimitives.ReverseEndianness(Pad0); + PointerData = BinaryPrimitives.ReverseEndianness(PointerData); + } + } + + #region JSON formatting + private static string GetTypeJson(string name, Target.TypeInfo info) + { + string ret = string.Empty; + List fields = info.Size is null ? [] : [$"\"!\":{info.Size}"]; + fields.AddRange(info.Fields.Select(f => $"\"{f.Key}\":{(f.Value.TypeName is null ? f.Value.Offset : $"[{f.Value.Offset},\"{f.Value.TypeName}\"]")}")); + return $"\"{name}\":{{{string.Join(',', fields)}}}"; + } + + public static string MakeTypesJson(IDictionary types) + { + return string.Join(',', types.Select(t => GetTypeJson(t.Key.ToString(), t.Value))); + } + + public static string MakeGlobalsJson(IEnumerable<(string Name, ulong Value, string? Type)> globals) + { + return MakeGlobalsJson(globals.Select(g => (g.Name, (ulong?)g.Value, (uint?)null, g.Type))); + } + + public static string MakeGlobalsJson(IEnumerable<(string Name, ulong? Value, uint? IndirectIndex, string? Type)> globals) + { + return string.Join(',', globals.Select(FormatGlobal)); + + static string FormatGlobal((string Name, ulong? Value, uint? IndirectIndex, string? Type) global) + { + if (global.Value is ulong value) + { + return $"\"{global.Name}\": {FormatValue(value, global.Type)}"; + } + else if (global.IndirectIndex is uint index) + { + return $"\"{global.Name}\": {FormatIndirect(index, global.Type)}"; + } + else + { + throw new InvalidOperationException("Global must have a value or indirect index"); + } + + } + static string FormatValue(ulong value, string? type) + { + return type is null ? $"{value}" : $"[{value},\"{type}\"]"; + } + static string FormatIndirect(uint value, string? type) + { + return type is null ? $"[{value}]" : $"[[{value}],\"{type}\"]"; + } + } + + #endregion JSON formatting +} diff --git a/src/native/managed/cdacreader/tests/ContractDescriptorParserTests.cs b/src/native/managed/cdacreader/tests/ContractDescriptor/ParserTests.cs similarity index 98% rename from src/native/managed/cdacreader/tests/ContractDescriptorParserTests.cs rename to src/native/managed/cdacreader/tests/ContractDescriptor/ParserTests.cs index 04b750dd6063b7..a74167437a240a 100644 --- a/src/native/managed/cdacreader/tests/ContractDescriptorParserTests.cs +++ b/src/native/managed/cdacreader/tests/ContractDescriptor/ParserTests.cs @@ -5,9 +5,9 @@ using System.Text.Json; using Xunit; -namespace Microsoft.Diagnostics.DataContractReader.UnitTests; +namespace Microsoft.Diagnostics.DataContractReader.Tests.ContractDescriptor; -public class ContractDescriptorParserTests +public class ParserTests { [Fact] public void ParsesEmptyContract() diff --git a/src/native/managed/cdacreader/tests/TargetTests.cs b/src/native/managed/cdacreader/tests/ContractDescriptor/TargetTests.cs similarity index 93% rename from src/native/managed/cdacreader/tests/TargetTests.cs rename to src/native/managed/cdacreader/tests/ContractDescriptor/TargetTests.cs index b260f42a762355..2c7cc3e5d454a5 100644 --- a/src/native/managed/cdacreader/tests/TargetTests.cs +++ b/src/native/managed/cdacreader/tests/ContractDescriptor/TargetTests.cs @@ -8,28 +8,33 @@ using System.Text; using Xunit; -namespace Microsoft.Diagnostics.DataContractReader.UnitTests; +namespace Microsoft.Diagnostics.DataContractReader.Tests.ContractDescriptor; public unsafe class TargetTests { private static readonly Dictionary TestTypes = new() { // Size and fields - [DataType.Thread] = new(){ + [DataType.Thread] = new() + { Size = 56, Fields = new Dictionary { { "Field1", new(){ Offset = 8, Type = DataType.uint16, TypeName = DataType.uint16.ToString() }}, { "Field2", new(){ Offset = 16, Type = DataType.GCHandle, TypeName = DataType.GCHandle.ToString() }}, { "Field3", new(){ Offset = 32 }} - }}, + } + }, // Fields only - [DataType.ThreadStore] = new(){ + [DataType.ThreadStore] = new() + { Fields = new Dictionary { { "Field1", new(){ Offset = 0, TypeName = "FieldType" }}, { "Field2", new(){ Offset = 8 }} - }}, + } + }, // Size only - [DataType.GCHandle] = new(){ + [DataType.GCHandle] = new() + { Size = 8 } }; @@ -39,7 +44,7 @@ public unsafe class TargetTests public void GetTypeInfo(MockTarget.Architecture arch) { TargetTestHelpers targetTestHelpers = new(arch); - MockMemorySpace.Builder builder = new (targetTestHelpers); + ContractDescriptorBuilder builder = new(targetTestHelpers); builder.SetTypes(TestTypes) .SetGlobals(Array.Empty<(string, ulong, string?)>()) .SetContracts(Array.Empty()); @@ -85,7 +90,7 @@ private static readonly (string Name, ulong Value, string? Type)[] TestGlobals = public void ReadGlobalValue(MockTarget.Architecture arch) { TargetTestHelpers targetTestHelpers = new(arch); - MockMemorySpace.Builder builder = new (targetTestHelpers); + ContractDescriptorBuilder builder = new(targetTestHelpers); builder.SetTypes(new Dictionary()) .SetGlobals(TestGlobals) .SetContracts([]); @@ -101,7 +106,7 @@ public void ReadGlobalValue(MockTarget.Architecture arch) public void ReadIndirectGlobalValue(MockTarget.Architecture arch) { TargetTestHelpers targetTestHelpers = new(arch); - MockMemorySpace.Builder builder = new (targetTestHelpers); + ContractDescriptorBuilder builder = new(targetTestHelpers); builder.SetTypes(new Dictionary()) .SetContracts([]) .SetGlobals(TestGlobals.Select(MakeGlobalToIndirect).ToArray(), @@ -128,7 +133,7 @@ public void ReadIndirectGlobalValue(MockTarget.Architecture arch) public void ReadUtf8String(MockTarget.Architecture arch) { TargetTestHelpers targetTestHelpers = new(arch); - MockMemorySpace.Builder builder = new(targetTestHelpers); + ContractDescriptorBuilder builder = new(targetTestHelpers); string expected = "UTF-8 string ✓"; ulong addr = 0x1000; @@ -150,7 +155,7 @@ public void ReadUtf8String(MockTarget.Architecture arch) public void ReadUtf16String(MockTarget.Architecture arch) { TargetTestHelpers targetTestHelpers = new(arch); - MockMemorySpace.Builder builder = new(targetTestHelpers); + ContractDescriptorBuilder builder = new(targetTestHelpers); string expected = "UTF-16 string ✓"; ulong addr = 0x1000; diff --git a/src/native/managed/cdacreader/tests/DacStreamsTests.cs b/src/native/managed/cdacreader/tests/DacStreamsTests.cs index 83eef79181a460..01ea202342b467 100644 --- a/src/native/managed/cdacreader/tests/DacStreamsTests.cs +++ b/src/native/managed/cdacreader/tests/DacStreamsTests.cs @@ -4,9 +4,11 @@ using System; using System.Collections.Generic; using System.Text; +using Microsoft.Diagnostics.DataContractReader.Contracts; +using Moq; using Xunit; -namespace Microsoft.Diagnostics.DataContractReader.UnitTests; +namespace Microsoft.Diagnostics.DataContractReader.Tests; public class DacStreamsTests { @@ -25,30 +27,24 @@ public class DacStreamsTests [ ]; - private static readonly (string Name, ulong Value, string? Type)[] DacStreamsGlobals = + private static readonly (string Name, ulong Value)[] DacStreamsGlobals = [ - (nameof(Constants.Globals.MiniMetaDataBuffAddress), TestMiniMetaDataBuffGlobalAddress, null), - (nameof(Constants.Globals.MiniMetaDataBuffMaxSize), TestMiniMetaDataBuffGlobalMaxSize, null), + (nameof(Constants.Globals.MiniMetaDataBuffAddress), TestMiniMetaDataBuffGlobalAddress), + (nameof(Constants.Globals.MiniMetaDataBuffMaxSize), TestMiniMetaDataBuffGlobalMaxSize), ]; private static unsafe void DacStreamsContractHelper(MockTarget.Architecture arch, ConfigureContextBuilder configure, Action testCase) { TargetTestHelpers targetTestHelpers = new(arch); - MockMemorySpace.Builder builder = new(targetTestHelpers); - - builder = builder - .SetContracts([nameof(Contracts.DacStreams)]) - .SetTypes(DacStreamsTypes) - .SetGlobals(DacStreamsGlobals); - if (configure != null) { builder = configure(builder); } - bool success = builder.TryCreateTarget(out ContractDescriptorTarget? target); - Assert.True(success); + var target = new TestPlaceholderTarget(arch, builder.GetReadContext().ReadFromTarget, DacStreamsTypes, DacStreamsGlobals); + target.SetContracts(Mock.Of( + c => c.DacStreams == ((IContractFactory)new DacStreamsFactory()).CreateContract(target, 1))); testCase(target); } diff --git a/src/native/managed/cdacreader/tests/ExecutionManager/ExecutionManagerTestBuilder.cs b/src/native/managed/cdacreader/tests/ExecutionManager/ExecutionManagerTestBuilder.cs deleted file mode 100644 index 67f18342b6e5cc..00000000000000 --- a/src/native/managed/cdacreader/tests/ExecutionManager/ExecutionManagerTestBuilder.cs +++ /dev/null @@ -1,479 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System; -using System.Collections.Generic; -using System.Linq; -using Microsoft.Diagnostics.DataContractReader.ExecutionManagerHelpers; - -using InteriorMapValue = Microsoft.Diagnostics.DataContractReader.ExecutionManagerHelpers.RangeSectionMap.InteriorMapValue; - -namespace Microsoft.Diagnostics.DataContractReader.UnitTests.ExecutionManager; - -internal class ExecutionManagerTestBuilder -{ - public const ulong ExecutionManagerCodeRangeMapAddress = 0x000a_fff0; - - const bool UseFunclets = true; - const int RealCodeHeaderSize = 0x08; // must be big enough for the offsets of RealCodeHeader size in ExecutionManagerTestTarget, below - - public struct AllocationRange - { - // elements of the range section map are allocated in this range - public ulong RangeSectionMapStart; - public ulong RangeSectionMapEnd; - // nibble maps for various range section fragments are allocated in this range - public ulong NibbleMapStart; - public ulong NibbleMapEnd; - // "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 { - RangeSectionMapStart = 0x00dd_0000, - RangeSectionMapEnd = 0x00de_0000, - NibbleMapStart = 0x00ee_0000, - NibbleMapEnd = 0x00ef_0000, - ExecutionManagerStart = 0x0033_4000, - ExecutionManagerEnd = 0x0033_5000, - }; - internal class RangeSectionMapTestBuilder - { - const ulong DefaultTopLevelAddress = 0x0000_1000u; // arbitrary - const int EntriesPerMapLevel = 256; // for now its fixed at 256, see codeman.h RangeSectionMap::entriesPerMapLevel - const int BitsPerLevel = 8; - - private readonly TargetPointer _topLevelAddress; - private readonly MockMemorySpace.Builder _builder; - private readonly TargetTestHelpers _targetTestHelpers; - private readonly int _levels; - private readonly int _maxSetBit; - private ulong _nextMapAddress; - public RangeSectionMapTestBuilder(MockTarget.Architecture arch) : this (DefaultTopLevelAddress, new MockMemorySpace.Builder (new TargetTestHelpers(arch))) - { - } - - public RangeSectionMapTestBuilder(TargetPointer topLevelAddress, MockMemorySpace.Builder builder) - { - _topLevelAddress = topLevelAddress; - _builder = builder; - _targetTestHelpers = builder.TargetTestHelpers; - var arch = _targetTestHelpers.Arch; - _levels = arch.Is64Bit ? 5 : 2; - _maxSetBit = arch.Is64Bit ? 56 : 31; // 0 indexed - MockMemorySpace.HeapFragment top = new MockMemorySpace.HeapFragment - { - Address = topLevelAddress, - Data = new byte[EntriesPerMapLevel * _targetTestHelpers.PointerSize], - Name = $"Map Level {_levels}" - }; - _nextMapAddress = topLevelAddress + (ulong)top.Data.Length; - _builder.AddHeapFragment(top); - } - - public TargetPointer TopLevel => _topLevelAddress; - - private int EffectiveBitsForLevel(ulong address, int level) - { - ulong addressBitsUsedInMap = address >> (_maxSetBit + 1 - (_levels * BitsPerLevel)); - ulong addressBitsShifted = addressBitsUsedInMap >> ((level - 1) * BitsPerLevel); - int addressBitsUsedInLevel = checked((int)((EntriesPerMapLevel - 1) & addressBitsShifted)); - return addressBitsUsedInLevel; - } - - // This is how much of the address space is covered by each entry in the last level of the map - private int BytesAtLastLevel => checked (1 << BitsAtLastLevel); - private int BitsAtLastLevel => _maxSetBit - (BitsPerLevel * _levels) + 1; - - private TargetPointer CursorAddress(RangeSectionMap.Cursor cursor) - { - return cursor.LevelMap + (ulong)(cursor.Index * _targetTestHelpers.PointerSize); - } - - private void WritePointer(RangeSectionMap.Cursor cursor, InteriorMapValue value) - { - TargetPointer address = CursorAddress(cursor); - Span dest = _builder.BorrowAddressRange(address, _targetTestHelpers.PointerSize); - _targetTestHelpers.WritePointer(dest, value.RawValue); - } - - private InteriorMapValue LoadCursorValue (RangeSectionMap.Cursor cursor) - { - TargetPointer address = CursorAddress(cursor); - ReadOnlySpan src = _builder.BorrowAddressRange(address, _targetTestHelpers.PointerSize); - return new InteriorMapValue(_targetTestHelpers.ReadPointer(src)); - } - - private MockMemorySpace.HeapFragment AllocateMapLevel(int level) - { - MockMemorySpace.HeapFragment mapLevel = new MockMemorySpace.HeapFragment - { - Address = new TargetPointer(_nextMapAddress), - Data = new byte[EntriesPerMapLevel * _targetTestHelpers.PointerSize], - Name = $"Map Level {level}" - }; - _nextMapAddress += (ulong)mapLevel.Data.Length; - _builder.AddHeapFragment(mapLevel); - return mapLevel; - } - - - // computes the cursor for the next level down from the given cursor - // if the slot for the next level does not exist, it is created - private RangeSectionMap.Cursor GetOrAddLevelSlot(TargetCodePointer address, RangeSectionMap.Cursor cursor, bool collectible = false) - { - int nextLevel = cursor.Level - 1; - int nextIndex = EffectiveBitsForLevel(address, nextLevel); - InteriorMapValue nextLevelMap = LoadCursorValue(cursor); - if (nextLevelMap.IsNull) - { - nextLevelMap = new (AllocateMapLevel(nextLevel).Address); - if (collectible) - { - nextLevelMap = new (nextLevelMap.RawValue | 1); - } - WritePointer(cursor, nextLevelMap); - } - return new RangeSectionMap.Cursor(nextLevelMap.Address, nextLevel, nextIndex); - } - - // ensures that the maps for all the levels for the given address are allocated. - // returns the address of the slot in the last level that corresponds to the given address - RangeSectionMap.Cursor EnsureLevelsForAddress(TargetCodePointer address, bool collectible = false) - { - int topIndex = EffectiveBitsForLevel(address, _levels); - RangeSectionMap.Cursor cursor = new RangeSectionMap.Cursor(TopLevel, _levels, topIndex); - while (!cursor.IsLeaf) - { - cursor = GetOrAddLevelSlot(address, cursor, collectible); - } - return cursor; - } - public void InsertAddressRange(TargetCodePointer start, uint length, ulong value, bool collectible = false) - { - TargetCodePointer cur = start; - ulong end = start.Value + length; - do { - RangeSectionMap.Cursor lastCursor = EnsureLevelsForAddress(cur, collectible); - WritePointer(lastCursor, new InteriorMapValue(value)); - cur = new TargetCodePointer(cur.Value + (ulong)BytesAtLastLevel); // FIXME: round ? - } while (cur.Value < end); - } - - public MockMemorySpace.ReadContext GetReadContext() - { - return _builder.GetReadContext(); - } - } - - public static RangeSectionMapTestBuilder CreateRangeSection(MockTarget.Architecture arch) - { - 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; } - internal (string Name, ulong Value)[] Globals { get; } - - private readonly RangeSectionMapTestBuilder _rsmBuilder; - - private readonly MockMemorySpace.BumpAllocator _rangeSectionMapAllocator; - private readonly MockMemorySpace.BumpAllocator _nibbleMapAllocator; - private readonly MockMemorySpace.BumpAllocator _allocator; - - 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) - { - Version = version; - Builder = builder; - _rsmBuilder = new RangeSectionMapTestBuilder(ExecutionManagerCodeRangeMapAddress, builder); - _rangeSectionMapAllocator = Builder.CreateAllocator(allocationRange.RangeSectionMapStart, allocationRange.RangeSectionMapEnd); - _nibbleMapAllocator = Builder.CreateAllocator(allocationRange.NibbleMapStart, allocationRange.NibbleMapEnd); - _allocator = Builder.CreateAllocator(allocationRange.ExecutionManagerStart, allocationRange.ExecutionManagerEnd); - Types = MockDescriptors.GetTypesForTypeFields( - Builder.TargetTestHelpers, - [ - RangeSectionMapFields, - RangeSectionFragmentFields, - RangeSectionFields, - CodeHeapListNodeFields, - RealCodeHeaderFields, - RuntimeFunctionFields, - ReadyToRunInfoFields(Builder.TargetTestHelpers), - MockDescriptors.ModuleFields, - ]).Concat(MockDescriptors.HashMap.GetTypes(Builder.TargetTestHelpers)) - .ToDictionary(); - Globals = - [ - (nameof(Constants.Globals.ExecutionManagerCodeRangeMapAddress), ExecutionManagerCodeRangeMapAddress), - (nameof(Constants.Globals.StubCodeBlockLast), 0x0Fu), - (nameof(Constants.Globals.FeatureEHFunclets), UseFunclets ? 1 : 0), - ]; - Globals = Globals - .Concat(MockDescriptors.HashMap.GetGlobals(Builder.TargetTestHelpers)) - .ToArray(); - } - - internal NibbleMapTestBuilderBase CreateNibbleMap(ulong codeRangeStart, uint codeRangeSize) - { - NibbleMapTestBuilderBase nibBuilder = Version switch - { - 1 => new NibbleMapTestBuilder_1(codeRangeStart, codeRangeSize, _nibbleMapAllocator, Builder.TargetTestHelpers.Arch), - - // The nibblemap algorithm was changed in version 2 - 2 => new NibbleMapTestBuilder_2(codeRangeStart, codeRangeSize, _nibbleMapAllocator, Builder.TargetTestHelpers.Arch), - _ => throw new InvalidOperationException("Unknown version"), - }; - - Builder.AddHeapFragment(nibBuilder.NibbleMapFragment); - return nibBuilder; - } - - internal readonly struct JittedCodeRange - { - public MockMemorySpace.BumpAllocator Allocator {get ; init;} - public ulong RangeStart => Allocator.RangeStart; - public ulong RangeEnd => Allocator.RangeEnd; - public ulong RangeSize => RangeEnd - RangeStart; - } - - public JittedCodeRange AllocateJittedCodeRange(ulong codeRangeStart, uint codeRangeSize) - { - MockMemorySpace.BumpAllocator allocator = Builder.CreateAllocator(codeRangeStart, codeRangeStart + codeRangeSize, minAlign: 1); - return new JittedCodeRange { Allocator = allocator }; - } - - public TargetPointer AddRangeSection(JittedCodeRange jittedCodeRange, TargetPointer jitManagerAddress, TargetPointer codeHeapListNodeAddress) - { - 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); - // 0x02 = RangeSectionFlags.CodeHeap - Builder.TargetTestHelpers.Write(rs.Slice(tyInfo.Fields[nameof(Data.RangeSection.Flags)].Offset, sizeof(uint)), (uint)0x02); - Builder.TargetTestHelpers.WritePointer(rs.Slice(tyInfo.Fields[nameof(Data.RangeSection.HeapList)].Offset, pointerSize), codeHeapListNodeAddress); - Builder.TargetTestHelpers.WritePointer(rs.Slice(tyInfo.Fields[nameof(Data.RangeSection.JitManager)].Offset, pointerSize), jitManagerAddress); - // FIXME: other fields - - 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 = 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? - _rsmBuilder.InsertAddressRange(jittedCodeRange.RangeStart, (uint)jittedCodeRange.RangeSize, rangeSectionFragment.Address); - Builder.AddHeapFragment(rangeSectionFragment); - int pointerSize = Builder.TargetTestHelpers.PointerSize; - Span rsf = Builder.BorrowAddressRange(rangeSectionFragment.Address, (int)rangeSectionFragmentSize); - Builder.TargetTestHelpers.WritePointer(rsf.Slice(tyInfo.Fields[nameof(Data.RangeSectionFragment.RangeBegin)].Offset, pointerSize), jittedCodeRange.RangeStart); - Builder.TargetTestHelpers.WritePointer(rsf.Slice(tyInfo.Fields[nameof(Data.RangeSectionFragment.RangeEndOpen)].Offset, pointerSize), jittedCodeRange.RangeEnd); - Builder.TargetTestHelpers.WritePointer(rsf.Slice(tyInfo.Fields[nameof(Data.RangeSectionFragment.RangeSection)].Offset, pointerSize), rangeSectionAddress); - /* Next = nullptr */ - // nothing - return rangeSectionFragment.Address; - } - - public TargetPointer AddCodeHeapListNode(TargetPointer next, TargetPointer startAddress, TargetPointer endAddress, TargetPointer mapBase, TargetPointer headerMap) - { - var tyInfo = Types[DataType.CodeHeapListNode]; - uint codeHeapListNodeSize = tyInfo.Size.Value; - MockMemorySpace.HeapFragment codeHeapListNode = _rangeSectionMapAllocator.Allocate (codeHeapListNodeSize, "CodeHeapListNode"); - Builder.AddHeapFragment(codeHeapListNode); - int pointerSize = Builder.TargetTestHelpers.PointerSize; - Span chln = Builder.BorrowAddressRange(codeHeapListNode.Address, (int)codeHeapListNodeSize); - Builder.TargetTestHelpers.WritePointer(chln.Slice(tyInfo.Fields[nameof(Data.CodeHeapListNode.Next)].Offset, pointerSize), next); - Builder.TargetTestHelpers.WritePointer(chln.Slice(tyInfo.Fields[nameof(Data.CodeHeapListNode.StartAddress)].Offset, pointerSize), startAddress); - Builder.TargetTestHelpers.WritePointer(chln.Slice(tyInfo.Fields[nameof(Data.CodeHeapListNode.EndAddress)].Offset, pointerSize), endAddress); - Builder.TargetTestHelpers.WritePointer(chln.Slice(tyInfo.Fields[nameof(Data.CodeHeapListNode.MapBase)].Offset, pointerSize), mapBase); - Builder.TargetTestHelpers.WritePointer(chln.Slice(tyInfo.Fields[nameof(Data.CodeHeapListNode.HeaderMap)].Offset, pointerSize), headerMap); - return codeHeapListNode.Address; - } - - private uint CodeHeaderSize => (uint)Builder.TargetTestHelpers.PointerSize; - - // offset from the start of the code - private uint CodeHeaderOffset => CodeHeaderSize; - - private (MockMemorySpace.HeapFragment fragment, TargetCodePointer codeStart) AllocateJittedMethod(JittedCodeRange jittedCodeRange, uint codeSize, string name = "Method Header & Code") - { - ulong size = codeSize + CodeHeaderOffset; - MockMemorySpace.HeapFragment methodFragment = jittedCodeRange.Allocator.Allocate(size, name); - Builder.AddHeapFragment(methodFragment); - TargetCodePointer codeStart = methodFragment.Address + CodeHeaderOffset; - return (methodFragment, codeStart); - } - - public TargetCodePointer AddJittedMethod(JittedCodeRange jittedCodeRange, uint codeSize, TargetPointer methodDescAddress) - { - (MockMemorySpace.HeapFragment methodFragment, TargetCodePointer codeStart) = AllocateJittedMethod(jittedCodeRange, codeSize); - - 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 = 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; - } -} diff --git a/src/native/managed/cdacreader/tests/ExecutionManager/ExecutionManagerTests.cs b/src/native/managed/cdacreader/tests/ExecutionManager/ExecutionManagerTests.cs index f222e0149604b4..d4ba7eafdc4833 100644 --- a/src/native/managed/cdacreader/tests/ExecutionManager/ExecutionManagerTests.cs +++ b/src/native/managed/cdacreader/tests/ExecutionManager/ExecutionManagerTests.cs @@ -7,11 +7,11 @@ using Microsoft.Diagnostics.DataContractReader.Contracts; using Moq; -namespace Microsoft.Diagnostics.DataContractReader.UnitTests.ExecutionManager; +namespace Microsoft.Diagnostics.DataContractReader.Tests.ExecutionManager; public class ExecutionManagerTests { - private static Target CreateTarget(ExecutionManagerTestBuilder emBuilder) + private static Target CreateTarget(MockDescriptors.ExecutionManager emBuilder) { var arch = emBuilder.Builder.TargetTestHelpers.Arch; TestPlaceholderTarget.ReadFromTargetDelegate reader = emBuilder.Builder.GetReadContext().ReadFromTarget; @@ -28,7 +28,7 @@ private static Target CreateTarget(ExecutionManagerTestBuilder emBuilder) [MemberData(nameof(StdArchAllVersions))] public void GetCodeBlockHandle_Null(int version, MockTarget.Architecture arch) { - ExecutionManagerTestBuilder emBuilder = new (version, arch, ExecutionManagerTestBuilder.DefaultAllocationRange); + MockDescriptors.ExecutionManager emBuilder = new (version, arch, MockDescriptors.ExecutionManager.DefaultAllocationRange); var target = CreateTarget(emBuilder); var em = target.Contracts.ExecutionManager; @@ -41,7 +41,7 @@ public void GetCodeBlockHandle_Null(int version, MockTarget.Architecture arch) [MemberData(nameof(StdArchAllVersions))] public void GetCodeBlockHandle_NoRangeSections(int version, MockTarget.Architecture arch) { - ExecutionManagerTestBuilder emBuilder = new (version, arch, ExecutionManagerTestBuilder.DefaultAllocationRange); + MockDescriptors.ExecutionManager emBuilder = new (version, arch, MockDescriptors.ExecutionManager.DefaultAllocationRange); var target = CreateTarget(emBuilder); var em = target.Contracts.ExecutionManager; @@ -62,7 +62,7 @@ public void GetMethodDesc_OneRangeOneMethod(int version, MockTarget.Architecture TargetPointer expectedMethodDescAddress = new TargetPointer(0x0101_aaa0); - ExecutionManagerTestBuilder emBuilder = new(version, arch, ExecutionManagerTestBuilder.DefaultAllocationRange); + MockDescriptors.ExecutionManager emBuilder = new(version, arch, MockDescriptors.ExecutionManager.DefaultAllocationRange); var jittedCode = emBuilder.AllocateJittedCodeRange(codeRangeStart, codeRangeSize); TargetCodePointer methodStart = emBuilder.AddJittedMethod(jittedCode, methodSize, expectedMethodDescAddress); @@ -107,7 +107,7 @@ public void GetCodeBlockHandle_OneRangeZeroMethod(int version, MockTarget.Archit TargetPointer jitManagerAddress = new (0x000b_ff00); // arbitrary - ExecutionManagerTestBuilder emBuilder = new(version, arch, ExecutionManagerTestBuilder.DefaultAllocationRange); + MockDescriptors.ExecutionManager emBuilder = new(version, arch, MockDescriptors.ExecutionManager.DefaultAllocationRange); var jittedCode = emBuilder.AllocateJittedCodeRange(codeRangeStart, codeRangeSize); NibbleMapTestBuilderBase nibBuilder = emBuilder.CreateNibbleMap(codeRangeStart, codeRangeSize); @@ -142,7 +142,7 @@ public void GetCodeBlockHandle_R2R_NoRuntimeFunctionMatch(int version, MockTarge const uint codeRangeSize = 0xc000u; // arbitrary TargetPointer jitManagerAddress = new(0x000b_ff00); // arbitrary - ExecutionManagerTestBuilder emBuilder = new(version, arch, ExecutionManagerTestBuilder.DefaultAllocationRange); + MockDescriptors.ExecutionManager emBuilder = new(version, arch, MockDescriptors.ExecutionManager.DefaultAllocationRange); var jittedCode = emBuilder.AllocateJittedCodeRange(codeRangeStart, codeRangeSize); uint runtimeFunction = 0x100; @@ -177,7 +177,7 @@ public void GetMethodDesc_R2R_OneRuntimeFunction(int version, MockTarget.Archite TargetPointer expectedMethodDescAddress = new TargetPointer(0x0101_aaa0); - ExecutionManagerTestBuilder emBuilder = new(version, arch, ExecutionManagerTestBuilder.DefaultAllocationRange); + MockDescriptors.ExecutionManager emBuilder = new(version, arch, MockDescriptors.ExecutionManager.DefaultAllocationRange); var jittedCode = emBuilder.AllocateJittedCodeRange(codeRangeStart, codeRangeSize); uint expectedRuntimeFunction = 0x100; @@ -223,7 +223,7 @@ public void GetMethodDesc_R2R_MultipleRuntimeFunctions(int version, MockTarget.A TargetPointer[] methodDescAddresses = [ 0x0101_aaa0, 0x0201_aaa0]; - ExecutionManagerTestBuilder emBuilder = new(version, arch, ExecutionManagerTestBuilder.DefaultAllocationRange); + MockDescriptors.ExecutionManager emBuilder = new(version, arch, MockDescriptors.ExecutionManager.DefaultAllocationRange); var jittedCode = emBuilder.AllocateJittedCodeRange(codeRangeStart, codeRangeSize); uint[] runtimeFunctions = [ 0x100, 0xc00 ]; diff --git a/src/native/managed/cdacreader/tests/ExecutionManager/HashMapTests.cs b/src/native/managed/cdacreader/tests/ExecutionManager/HashMapTests.cs index 53c7b2ffcb8623..5241913a11ee78 100644 --- a/src/native/managed/cdacreader/tests/ExecutionManager/HashMapTests.cs +++ b/src/native/managed/cdacreader/tests/ExecutionManager/HashMapTests.cs @@ -5,7 +5,7 @@ using Microsoft.Diagnostics.DataContractReader.ExecutionManagerHelpers; -namespace Microsoft.Diagnostics.DataContractReader.UnitTests.ExecutionManager; +namespace Microsoft.Diagnostics.DataContractReader.Tests.ExecutionManager; public class HashMapTests { diff --git a/src/native/managed/cdacreader/tests/ExecutionManager/NibbleMapTestBuilder.cs b/src/native/managed/cdacreader/tests/ExecutionManager/NibbleMapTestBuilder.cs index 3e51e6f890cecd..cf023167c01896 100644 --- a/src/native/managed/cdacreader/tests/ExecutionManager/NibbleMapTestBuilder.cs +++ b/src/native/managed/cdacreader/tests/ExecutionManager/NibbleMapTestBuilder.cs @@ -4,7 +4,7 @@ using System; using Microsoft.Diagnostics.DataContractReader.ExecutionManagerHelpers; -namespace Microsoft.Diagnostics.DataContractReader.UnitTests.ExecutionManager; +namespace Microsoft.Diagnostics.DataContractReader.Tests.ExecutionManager; internal abstract class NibbleMapTestBuilderBase { diff --git a/src/native/managed/cdacreader/tests/ExecutionManager/NibbleMapTests.cs b/src/native/managed/cdacreader/tests/ExecutionManager/NibbleMapTests.cs index 6a71fbafcbb67b..44c2d879127b08 100644 --- a/src/native/managed/cdacreader/tests/ExecutionManager/NibbleMapTests.cs +++ b/src/native/managed/cdacreader/tests/ExecutionManager/NibbleMapTests.cs @@ -5,7 +5,7 @@ using Microsoft.Diagnostics.DataContractReader.ExecutionManagerHelpers; -namespace Microsoft.Diagnostics.DataContractReader.UnitTests.ExecutionManager; +namespace Microsoft.Diagnostics.DataContractReader.Tests.ExecutionManager; public class NibbleMapTestsBase { diff --git a/src/native/managed/cdacreader/tests/ExecutionManager/RangeSectionMapTests.cs b/src/native/managed/cdacreader/tests/ExecutionManager/RangeSectionMapTests.cs index 914c5ec93223aa..c98267e76ded0c 100644 --- a/src/native/managed/cdacreader/tests/ExecutionManager/RangeSectionMapTests.cs +++ b/src/native/managed/cdacreader/tests/ExecutionManager/RangeSectionMapTests.cs @@ -6,7 +6,7 @@ using Microsoft.Diagnostics.DataContractReader.ExecutionManagerHelpers; -namespace Microsoft.Diagnostics.DataContractReader.UnitTests.ExecutionManager; +namespace Microsoft.Diagnostics.DataContractReader.Tests.ExecutionManager; public class RangeSectionMapTests { @@ -14,7 +14,7 @@ public class RangeSectionMapTests [ClassData(typeof(MockTarget.StdArch))] public void TestLookupFail(MockTarget.Architecture arch) { - var builder = ExecutionManagerTestBuilder.CreateRangeSection(arch); + var builder = MockDescriptors.ExecutionManager.CreateRangeSection(arch); var target = new TestPlaceholderTarget(arch, builder.GetReadContext().ReadFromTarget); var rsla = RangeSectionMap.Create(target); @@ -28,7 +28,7 @@ public void TestLookupFail(MockTarget.Architecture arch) [ClassData(typeof(MockTarget.StdArch))] public void TestLookupOne(MockTarget.Architecture arch) { - var builder = ExecutionManagerTestBuilder.CreateRangeSection(arch); + var builder = MockDescriptors.ExecutionManager.CreateRangeSection(arch); var inputPC = new TargetCodePointer(0x007f_0000); var length = 0x1000u; var value = 0x0a0a_0a0au; diff --git a/src/native/managed/cdacreader/tests/LoaderTests.cs b/src/native/managed/cdacreader/tests/LoaderTests.cs index 566f69b4cf163f..2e13e784dccf8f 100644 --- a/src/native/managed/cdacreader/tests/LoaderTests.cs +++ b/src/native/managed/cdacreader/tests/LoaderTests.cs @@ -3,9 +3,11 @@ using System; using System.IO; +using Microsoft.Diagnostics.DataContractReader.Contracts; +using Moq; using Xunit; -namespace Microsoft.Diagnostics.DataContractReader.UnitTests; +namespace Microsoft.Diagnostics.DataContractReader.Tests; using MockLoader = MockDescriptors.Loader; @@ -19,9 +21,6 @@ public void GetPath(MockTarget.Architecture arch) TargetTestHelpers helpers = new(arch); MockMemorySpace.Builder builder = new(helpers); MockLoader loader = new(builder); - builder = builder - .SetContracts([nameof(Contracts.Loader)]) - .SetTypes(loader.Types); string expected = $"{AppContext.BaseDirectory}{Path.DirectorySeparatorChar}TestModule.dll"; @@ -29,11 +28,12 @@ public void GetPath(MockTarget.Architecture arch) TargetPointer moduleAddr = loader.AddModule(path: expected); TargetPointer moduleAddrEmptyPath = loader.AddModule(); - bool success = builder.TryCreateTarget(out ContractDescriptorTarget? target); - Assert.True(success); + var target = new TestPlaceholderTarget(arch, builder.GetReadContext().ReadFromTarget, loader.Types); + target.SetContracts(Mock.Of( + c => c.Loader == ((IContractFactory)new LoaderFactory()).CreateContract(target, 1))); // Validate the expected module data - Contracts.ILoader contract = target.Contracts.Loader; + ILoader contract = target.Contracts.Loader; Assert.NotNull(contract); { Contracts.ModuleHandle handle = contract.GetModuleHandle(moduleAddr); @@ -55,9 +55,6 @@ public void GetFileName(MockTarget.Architecture arch) TargetTestHelpers helpers = new(arch); MockMemorySpace.Builder builder = new(helpers); MockLoader loader = new(builder); - builder = builder - .SetContracts([nameof(Contracts.Loader)]) - .SetTypes(loader.Types); string expected = $"TestModule.dll"; @@ -65,8 +62,9 @@ public void GetFileName(MockTarget.Architecture arch) TargetPointer moduleAddr = loader.AddModule(fileName: expected); TargetPointer moduleAddrEmptyName = loader.AddModule(); - bool success = builder.TryCreateTarget(out ContractDescriptorTarget? target); - Assert.True(success); + var target = new TestPlaceholderTarget(arch, builder.GetReadContext().ReadFromTarget, loader.Types); + target.SetContracts(Mock.Of( + c => c.Loader == ((IContractFactory)new LoaderFactory()).CreateContract(target, 1))); // Validate the expected module data Contracts.ILoader contract = target.Contracts.Loader; diff --git a/src/native/managed/cdacreader/tests/MethodDescTests.cs b/src/native/managed/cdacreader/tests/MethodDescTests.cs index db9fe7d0de5fa4..7499eefce1019e 100644 --- a/src/native/managed/cdacreader/tests/MethodDescTests.cs +++ b/src/native/managed/cdacreader/tests/MethodDescTests.cs @@ -3,9 +3,10 @@ using System; using Microsoft.Diagnostics.DataContractReader.Contracts; +using Moq; using Xunit; -namespace Microsoft.Diagnostics.DataContractReader.UnitTests; +namespace Microsoft.Diagnostics.DataContractReader.Tests; public class MethodDescTests { @@ -17,22 +18,19 @@ private static void MethodDescHelper(MockTarget.Architecture arch, Action( + c => c.RuntimeTypeSystem == ((IContractFactory)new RuntimeTypeSystemFactory()).CreateContract(target, 1) + && c.Loader == ((IContractFactory)new LoaderFactory()).CreateContract(target, 1))); + testCase(target); } diff --git a/src/native/managed/cdacreader/tests/MethodTableTests.cs b/src/native/managed/cdacreader/tests/MethodTableTests.cs index 233debe72b95a9..6b19790ae1924b 100644 --- a/src/native/managed/cdacreader/tests/MethodTableTests.cs +++ b/src/native/managed/cdacreader/tests/MethodTableTests.cs @@ -2,10 +2,12 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; +using Microsoft.Diagnostics.DataContractReader.Contracts; using Microsoft.Diagnostics.DataContractReader.RuntimeTypeSystemHelpers; +using Moq; using Xunit; -namespace Microsoft.Diagnostics.DataContractReader.UnitTests; +namespace Microsoft.Diagnostics.DataContractReader.Tests; using MockRTS = MockDescriptors.RuntimeTypeSystem; @@ -15,17 +17,13 @@ private static void RTSContractHelper(MockTarget.Architecture arch, Action( + c => c.RuntimeTypeSystem == ((IContractFactory)new RuntimeTypeSystemFactory()).CreateContract(target, 1))); testCase(target); } diff --git a/src/native/managed/cdacreader/tests/MockDescriptors/MockDescriptors.CodeVersions.cs b/src/native/managed/cdacreader/tests/MockDescriptors/MockDescriptors.CodeVersions.cs index 05df0bace8d6e5..f97f099433e9e3 100644 --- a/src/native/managed/cdacreader/tests/MockDescriptors/MockDescriptors.CodeVersions.cs +++ b/src/native/managed/cdacreader/tests/MockDescriptors/MockDescriptors.CodeVersions.cs @@ -5,7 +5,7 @@ using System.Collections.Generic; using Microsoft.Diagnostics.DataContractReader.Contracts; -namespace Microsoft.Diagnostics.DataContractReader.UnitTests; +namespace Microsoft.Diagnostics.DataContractReader.Tests; internal partial class MockDescriptors { diff --git a/src/native/managed/cdacreader/tests/MockDescriptors/MockDescriptors.ExecutionManager.cs b/src/native/managed/cdacreader/tests/MockDescriptors/MockDescriptors.ExecutionManager.cs new file mode 100644 index 00000000000000..98ca626103a044 --- /dev/null +++ b/src/native/managed/cdacreader/tests/MockDescriptors/MockDescriptors.ExecutionManager.cs @@ -0,0 +1,487 @@ +// 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; +using Microsoft.Diagnostics.DataContractReader.ExecutionManagerHelpers; +using Microsoft.Diagnostics.DataContractReader.Tests.ExecutionManager; + +using InteriorMapValue = Microsoft.Diagnostics.DataContractReader.ExecutionManagerHelpers.RangeSectionMap.InteriorMapValue; + +namespace Microsoft.Diagnostics.DataContractReader.Tests; + +internal partial class MockDescriptors +{ + internal class ExecutionManager + { + public const ulong ExecutionManagerCodeRangeMapAddress = 0x000a_fff0; + + const int RealCodeHeaderSize = 0x08; // must be big enough for the offsets of RealCodeHeader size in ExecutionManagerTestTarget, below + + public struct AllocationRange + { + // elements of the range section map are allocated in this range + public ulong RangeSectionMapStart; + public ulong RangeSectionMapEnd; + // nibble maps for various range section fragments are allocated in this range + public ulong NibbleMapStart; + public ulong NibbleMapEnd; + // "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 + { + RangeSectionMapStart = 0x00dd_0000, + RangeSectionMapEnd = 0x00de_0000, + NibbleMapStart = 0x00ee_0000, + NibbleMapEnd = 0x00ef_0000, + ExecutionManagerStart = 0x0033_4000, + ExecutionManagerEnd = 0x0033_5000, + }; + internal class RangeSectionMapTestBuilder + { + const ulong DefaultTopLevelAddress = 0x0000_1000u; // arbitrary + const int EntriesPerMapLevel = 256; // for now its fixed at 256, see codeman.h RangeSectionMap::entriesPerMapLevel + const int BitsPerLevel = 8; + + private readonly TargetPointer _topLevelAddress; + private readonly MockMemorySpace.Builder _builder; + private readonly TargetTestHelpers _targetTestHelpers; + private readonly int _levels; + private readonly int _maxSetBit; + private ulong _nextMapAddress; + public RangeSectionMapTestBuilder(MockTarget.Architecture arch) : this(DefaultTopLevelAddress, new MockMemorySpace.Builder(new TargetTestHelpers(arch))) + { + } + + public RangeSectionMapTestBuilder(TargetPointer topLevelAddress, MockMemorySpace.Builder builder) + { + _topLevelAddress = topLevelAddress; + _builder = builder; + _targetTestHelpers = builder.TargetTestHelpers; + var arch = _targetTestHelpers.Arch; + _levels = arch.Is64Bit ? 5 : 2; + _maxSetBit = arch.Is64Bit ? 56 : 31; // 0 indexed + MockMemorySpace.HeapFragment top = new MockMemorySpace.HeapFragment + { + Address = topLevelAddress, + Data = new byte[EntriesPerMapLevel * _targetTestHelpers.PointerSize], + Name = $"Map Level {_levels}" + }; + _nextMapAddress = topLevelAddress + (ulong)top.Data.Length; + _builder.AddHeapFragment(top); + } + + public TargetPointer TopLevel => _topLevelAddress; + + private int EffectiveBitsForLevel(ulong address, int level) + { + ulong addressBitsUsedInMap = address >> _maxSetBit + 1 - _levels * BitsPerLevel; + ulong addressBitsShifted = addressBitsUsedInMap >> (level - 1) * BitsPerLevel; + int addressBitsUsedInLevel = checked((int)(EntriesPerMapLevel - 1 & addressBitsShifted)); + return addressBitsUsedInLevel; + } + + // This is how much of the address space is covered by each entry in the last level of the map + private int BytesAtLastLevel => checked(1 << BitsAtLastLevel); + private int BitsAtLastLevel => _maxSetBit - BitsPerLevel * _levels + 1; + + private TargetPointer CursorAddress(RangeSectionMap.Cursor cursor) + { + return cursor.LevelMap + (ulong)(cursor.Index * _targetTestHelpers.PointerSize); + } + + private void WritePointer(RangeSectionMap.Cursor cursor, InteriorMapValue value) + { + TargetPointer address = CursorAddress(cursor); + Span dest = _builder.BorrowAddressRange(address, _targetTestHelpers.PointerSize); + _targetTestHelpers.WritePointer(dest, value.RawValue); + } + + private InteriorMapValue LoadCursorValue(RangeSectionMap.Cursor cursor) + { + TargetPointer address = CursorAddress(cursor); + ReadOnlySpan src = _builder.BorrowAddressRange(address, _targetTestHelpers.PointerSize); + return new InteriorMapValue(_targetTestHelpers.ReadPointer(src)); + } + + private MockMemorySpace.HeapFragment AllocateMapLevel(int level) + { + MockMemorySpace.HeapFragment mapLevel = new MockMemorySpace.HeapFragment + { + Address = new TargetPointer(_nextMapAddress), + Data = new byte[EntriesPerMapLevel * _targetTestHelpers.PointerSize], + Name = $"Map Level {level}" + }; + _nextMapAddress += (ulong)mapLevel.Data.Length; + _builder.AddHeapFragment(mapLevel); + return mapLevel; + } + + + // computes the cursor for the next level down from the given cursor + // if the slot for the next level does not exist, it is created + private RangeSectionMap.Cursor GetOrAddLevelSlot(TargetCodePointer address, RangeSectionMap.Cursor cursor, bool collectible = false) + { + int nextLevel = cursor.Level - 1; + int nextIndex = EffectiveBitsForLevel(address, nextLevel); + InteriorMapValue nextLevelMap = LoadCursorValue(cursor); + if (nextLevelMap.IsNull) + { + nextLevelMap = new(AllocateMapLevel(nextLevel).Address); + if (collectible) + { + nextLevelMap = new(nextLevelMap.RawValue | 1); + } + WritePointer(cursor, nextLevelMap); + } + return new RangeSectionMap.Cursor(nextLevelMap.Address, nextLevel, nextIndex); + } + + // ensures that the maps for all the levels for the given address are allocated. + // returns the address of the slot in the last level that corresponds to the given address + RangeSectionMap.Cursor EnsureLevelsForAddress(TargetCodePointer address, bool collectible = false) + { + int topIndex = EffectiveBitsForLevel(address, _levels); + RangeSectionMap.Cursor cursor = new RangeSectionMap.Cursor(TopLevel, _levels, topIndex); + while (!cursor.IsLeaf) + { + cursor = GetOrAddLevelSlot(address, cursor, collectible); + } + return cursor; + } + public void InsertAddressRange(TargetCodePointer start, uint length, ulong value, bool collectible = false) + { + TargetCodePointer cur = start; + ulong end = start.Value + length; + do + { + RangeSectionMap.Cursor lastCursor = EnsureLevelsForAddress(cur, collectible); + WritePointer(lastCursor, new InteriorMapValue(value)); + cur = new TargetCodePointer(cur.Value + (ulong)BytesAtLastLevel); // FIXME: round ? + } while (cur.Value < end); + } + + public MockMemorySpace.ReadContext GetReadContext() + { + return _builder.GetReadContext(); + } + } + + public static RangeSectionMapTestBuilder CreateRangeSection(MockTarget.Architecture arch) + { + 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; } + internal (string Name, ulong Value)[] Globals { get; } + + private readonly RangeSectionMapTestBuilder _rsmBuilder; + + private readonly MockMemorySpace.BumpAllocator _rangeSectionMapAllocator; + private readonly MockMemorySpace.BumpAllocator _nibbleMapAllocator; + private readonly MockMemorySpace.BumpAllocator _allocator; + + internal ExecutionManager(int version, MockTarget.Architecture arch, AllocationRange allocationRange) + : this(version, new MockMemorySpace.Builder(new TargetTestHelpers(arch)), allocationRange) + { } + + internal ExecutionManager(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); + _allocator = Builder.CreateAllocator(allocationRange.ExecutionManagerStart, allocationRange.ExecutionManagerEnd); + Types = MockDescriptors.GetTypesForTypeFields( + Builder.TargetTestHelpers, + [ + RangeSectionMapFields, + RangeSectionFragmentFields, + RangeSectionFields, + CodeHeapListNodeFields, + RealCodeHeaderFields, + RuntimeFunctionFields, + ReadyToRunInfoFields(Builder.TargetTestHelpers), + MockDescriptors.ModuleFields, + ]).Concat(MockDescriptors.HashMap.GetTypes(Builder.TargetTestHelpers)) + .ToDictionary(); + + // Tests are currently always set to use funclets + bool useFunclets = true; + Globals = + [ + (nameof(Constants.Globals.ExecutionManagerCodeRangeMapAddress), ExecutionManagerCodeRangeMapAddress), + (nameof(Constants.Globals.StubCodeBlockLast), 0x0Fu), + (nameof(Constants.Globals.FeatureEHFunclets), useFunclets ? 1u : 0u), + ]; + Globals = Globals + .Concat(MockDescriptors.HashMap.GetGlobals(Builder.TargetTestHelpers)) + .ToArray(); + } + + internal NibbleMapTestBuilderBase CreateNibbleMap(ulong codeRangeStart, uint codeRangeSize) + { + NibbleMapTestBuilderBase nibBuilder = Version switch + { + 1 => new NibbleMapTestBuilder_1(codeRangeStart, codeRangeSize, _nibbleMapAllocator, Builder.TargetTestHelpers.Arch), + + // The nibblemap algorithm was changed in version 2 + 2 => new NibbleMapTestBuilder_2(codeRangeStart, codeRangeSize, _nibbleMapAllocator, Builder.TargetTestHelpers.Arch), + _ => throw new InvalidOperationException("Unknown version"), + }; + + Builder.AddHeapFragment(nibBuilder.NibbleMapFragment); + return nibBuilder; + } + + internal readonly struct JittedCodeRange + { + public MockMemorySpace.BumpAllocator Allocator { get; init; } + public ulong RangeStart => Allocator.RangeStart; + public ulong RangeEnd => Allocator.RangeEnd; + public ulong RangeSize => RangeEnd - RangeStart; + } + + public JittedCodeRange AllocateJittedCodeRange(ulong codeRangeStart, uint codeRangeSize) + { + MockMemorySpace.BumpAllocator allocator = Builder.CreateAllocator(codeRangeStart, codeRangeStart + codeRangeSize, minAlign: 1); + return new JittedCodeRange { Allocator = allocator }; + } + + public TargetPointer AddRangeSection(JittedCodeRange jittedCodeRange, TargetPointer jitManagerAddress, TargetPointer codeHeapListNodeAddress) + { + 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); + // 0x02 = RangeSectionFlags.CodeHeap + Builder.TargetTestHelpers.Write(rs.Slice(tyInfo.Fields[nameof(Data.RangeSection.Flags)].Offset, sizeof(uint)), (uint)0x02); + Builder.TargetTestHelpers.WritePointer(rs.Slice(tyInfo.Fields[nameof(Data.RangeSection.HeapList)].Offset, pointerSize), codeHeapListNodeAddress); + Builder.TargetTestHelpers.WritePointer(rs.Slice(tyInfo.Fields[nameof(Data.RangeSection.JitManager)].Offset, pointerSize), jitManagerAddress); + // FIXME: other fields + + 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 = 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? + _rsmBuilder.InsertAddressRange(jittedCodeRange.RangeStart, (uint)jittedCodeRange.RangeSize, rangeSectionFragment.Address); + Builder.AddHeapFragment(rangeSectionFragment); + int pointerSize = Builder.TargetTestHelpers.PointerSize; + Span rsf = Builder.BorrowAddressRange(rangeSectionFragment.Address, (int)rangeSectionFragmentSize); + Builder.TargetTestHelpers.WritePointer(rsf.Slice(tyInfo.Fields[nameof(Data.RangeSectionFragment.RangeBegin)].Offset, pointerSize), jittedCodeRange.RangeStart); + Builder.TargetTestHelpers.WritePointer(rsf.Slice(tyInfo.Fields[nameof(Data.RangeSectionFragment.RangeEndOpen)].Offset, pointerSize), jittedCodeRange.RangeEnd); + Builder.TargetTestHelpers.WritePointer(rsf.Slice(tyInfo.Fields[nameof(Data.RangeSectionFragment.RangeSection)].Offset, pointerSize), rangeSectionAddress); + /* Next = nullptr */ + // nothing + return rangeSectionFragment.Address; + } + + public TargetPointer AddCodeHeapListNode(TargetPointer next, TargetPointer startAddress, TargetPointer endAddress, TargetPointer mapBase, TargetPointer headerMap) + { + var tyInfo = Types[DataType.CodeHeapListNode]; + uint codeHeapListNodeSize = tyInfo.Size.Value; + MockMemorySpace.HeapFragment codeHeapListNode = _rangeSectionMapAllocator.Allocate(codeHeapListNodeSize, "CodeHeapListNode"); + Builder.AddHeapFragment(codeHeapListNode); + int pointerSize = Builder.TargetTestHelpers.PointerSize; + Span chln = Builder.BorrowAddressRange(codeHeapListNode.Address, (int)codeHeapListNodeSize); + Builder.TargetTestHelpers.WritePointer(chln.Slice(tyInfo.Fields[nameof(Data.CodeHeapListNode.Next)].Offset, pointerSize), next); + Builder.TargetTestHelpers.WritePointer(chln.Slice(tyInfo.Fields[nameof(Data.CodeHeapListNode.StartAddress)].Offset, pointerSize), startAddress); + Builder.TargetTestHelpers.WritePointer(chln.Slice(tyInfo.Fields[nameof(Data.CodeHeapListNode.EndAddress)].Offset, pointerSize), endAddress); + Builder.TargetTestHelpers.WritePointer(chln.Slice(tyInfo.Fields[nameof(Data.CodeHeapListNode.MapBase)].Offset, pointerSize), mapBase); + Builder.TargetTestHelpers.WritePointer(chln.Slice(tyInfo.Fields[nameof(Data.CodeHeapListNode.HeaderMap)].Offset, pointerSize), headerMap); + return codeHeapListNode.Address; + } + + private uint CodeHeaderSize => (uint)Builder.TargetTestHelpers.PointerSize; + + // offset from the start of the code + private uint CodeHeaderOffset => CodeHeaderSize; + + private (MockMemorySpace.HeapFragment fragment, TargetCodePointer codeStart) AllocateJittedMethod(JittedCodeRange jittedCodeRange, uint codeSize, string name = "Method Header & Code") + { + ulong size = codeSize + CodeHeaderOffset; + MockMemorySpace.HeapFragment methodFragment = jittedCodeRange.Allocator.Allocate(size, name); + Builder.AddHeapFragment(methodFragment); + TargetCodePointer codeStart = methodFragment.Address + CodeHeaderOffset; + return (methodFragment, codeStart); + } + + public TargetCodePointer AddJittedMethod(JittedCodeRange jittedCodeRange, uint codeSize, TargetPointer methodDescAddress) + { + (MockMemorySpace.HeapFragment methodFragment, TargetCodePointer codeStart) = AllocateJittedMethod(jittedCodeRange, codeSize); + + 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 = 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; + } + } +} diff --git a/src/native/managed/cdacreader/tests/MockDescriptors/MockDescriptors.HashMap.cs b/src/native/managed/cdacreader/tests/MockDescriptors/MockDescriptors.HashMap.cs index 499c5da0ef7b3d..84230ad874276b 100644 --- a/src/native/managed/cdacreader/tests/MockDescriptors/MockDescriptors.HashMap.cs +++ b/src/native/managed/cdacreader/tests/MockDescriptors/MockDescriptors.HashMap.cs @@ -5,7 +5,7 @@ using System.Collections.Generic; using System.Linq; -namespace Microsoft.Diagnostics.DataContractReader.UnitTests; +namespace Microsoft.Diagnostics.DataContractReader.Tests; internal partial class MockDescriptors { diff --git a/src/native/managed/cdacreader/tests/MockDescriptors/MockDescriptors.Loader.cs b/src/native/managed/cdacreader/tests/MockDescriptors/MockDescriptors.Loader.cs index b9fd37a86a5259..303cc468df3adc 100644 --- a/src/native/managed/cdacreader/tests/MockDescriptors/MockDescriptors.Loader.cs +++ b/src/native/managed/cdacreader/tests/MockDescriptors/MockDescriptors.Loader.cs @@ -5,7 +5,7 @@ using System.Collections.Generic; using System.Text; -namespace Microsoft.Diagnostics.DataContractReader.UnitTests; +namespace Microsoft.Diagnostics.DataContractReader.Tests; internal partial class MockDescriptors { diff --git a/src/native/managed/cdacreader/tests/MockDescriptors/MockDescriptors.MethodDescriptors.cs b/src/native/managed/cdacreader/tests/MockDescriptors/MockDescriptors.MethodDescriptors.cs index 4bba2e77b23ec8..e36010a0683acc 100644 --- a/src/native/managed/cdacreader/tests/MockDescriptors/MockDescriptors.MethodDescriptors.cs +++ b/src/native/managed/cdacreader/tests/MockDescriptors/MockDescriptors.MethodDescriptors.cs @@ -5,7 +5,7 @@ using System.Collections.Generic; using System.Linq; -namespace Microsoft.Diagnostics.DataContractReader.UnitTests; +namespace Microsoft.Diagnostics.DataContractReader.Tests; internal partial class MockDescriptors { @@ -44,7 +44,7 @@ public class MethodDescriptors internal readonly Loader LoaderBuilder; internal Dictionary Types { get; } - internal (string Name, ulong Value, string? Type)[] Globals { get; } + internal (string Name, ulong Value)[] Globals { get; } internal MockMemorySpace.BumpAllocator MethodDescChunkAllocator { get; set; } @@ -59,7 +59,7 @@ internal MethodDescriptors(RuntimeTypeSystem rtsBuilder, Loader loaderBuilder) Types = GetTypes(); Globals = rtsBuilder.Globals.Concat( [ - new(nameof(Constants.Globals.MethodDescTokenRemainderBitCount), TokenRemainderBitCount, "uint8"), + new(nameof(Constants.Globals.MethodDescTokenRemainderBitCount), TokenRemainderBitCount), ]).ToArray(); } diff --git a/src/native/managed/cdacreader/tests/MockDescriptors/MockDescriptors.Object.cs b/src/native/managed/cdacreader/tests/MockDescriptors/MockDescriptors.Object.cs index 17be6f46637aea..918726dfb50a5d 100644 --- a/src/native/managed/cdacreader/tests/MockDescriptors/MockDescriptors.Object.cs +++ b/src/native/managed/cdacreader/tests/MockDescriptors/MockDescriptors.Object.cs @@ -8,7 +8,7 @@ using System.Runtime.InteropServices; using Microsoft.Diagnostics.DataContractReader.RuntimeTypeSystemHelpers; -namespace Microsoft.Diagnostics.DataContractReader.UnitTests; +namespace Microsoft.Diagnostics.DataContractReader.Tests; internal partial class MockDescriptors { @@ -38,7 +38,7 @@ public class Object internal TargetPointer TestStringMethodTableAddress { get; private set; } internal Dictionary Types { get; } - internal (string Name, ulong Value, string? Type)[] Globals { get; } + internal (string Name, ulong Value)[] Globals { get; } public Object(RuntimeTypeSystem rtsBuilder) : this(rtsBuilder, (DefaultAllocationRangeStart, DefaultAllocationRangeEnd)) @@ -57,12 +57,12 @@ public Object(RuntimeTypeSystem rtsBuilder, (ulong Start, ulong End) allocationR AddGlobalPointers(); Globals = rtsBuilder.Globals.Concat( [ - (nameof(Constants.Globals.ObjectToMethodTableUnmask), TestObjectToMethodTableUnmask, "uint8"), - (nameof(Constants.Globals.StringMethodTable), TestStringMethodTableGlobalAddress, null), - (nameof(Constants.Globals.ArrayBoundsZero), TestArrayBoundsZeroGlobalAddress, null), - (nameof(Constants.Globals.SyncTableEntries), TestSyncTableEntriesGlobalAddress, null), - (nameof(Constants.Globals.ObjectHeaderSize), Builder.TargetTestHelpers.ObjHeaderSize, "uint32"), - (nameof(Constants.Globals.SyncBlockValueToObjectOffset), TestSyncBlockValueToObjectOffset, "uint16"), + (nameof(Constants.Globals.ObjectToMethodTableUnmask), TestObjectToMethodTableUnmask), + (nameof(Constants.Globals.StringMethodTable), TestStringMethodTableGlobalAddress), + (nameof(Constants.Globals.ArrayBoundsZero), TestArrayBoundsZeroGlobalAddress), + (nameof(Constants.Globals.SyncTableEntries), TestSyncTableEntriesGlobalAddress), + (nameof(Constants.Globals.ObjectHeaderSize), Builder.TargetTestHelpers.ObjHeaderSize), + (nameof(Constants.Globals.SyncBlockValueToObjectOffset), TestSyncBlockValueToObjectOffset), ]).ToArray(); } diff --git a/src/native/managed/cdacreader/tests/MockDescriptors/MockDescriptors.RuntimeTypeSystem.cs b/src/native/managed/cdacreader/tests/MockDescriptors/MockDescriptors.RuntimeTypeSystem.cs index 059d3e014cfd06..fea2c8ec173344 100644 --- a/src/native/managed/cdacreader/tests/MockDescriptors/MockDescriptors.RuntimeTypeSystem.cs +++ b/src/native/managed/cdacreader/tests/MockDescriptors/MockDescriptors.RuntimeTypeSystem.cs @@ -4,7 +4,7 @@ using System; using System.Collections.Generic; -namespace Microsoft.Diagnostics.DataContractReader.UnitTests; +namespace Microsoft.Diagnostics.DataContractReader.Tests; internal partial class MockDescriptors { @@ -32,7 +32,7 @@ public class RuntimeTypeSystem internal readonly MockMemorySpace.Builder Builder; internal Dictionary Types { get; } - internal (string Name, ulong Value, string? Type)[] Globals { get; } + internal (string Name, ulong Value)[] Globals { get; } internal MockMemorySpace.BumpAllocator TypeSystemAllocator { get; } @@ -52,8 +52,8 @@ public RuntimeTypeSystem(MockMemorySpace.Builder builder, (ulong Start, ulong En AddGlobalPointers(); Globals = [ - (nameof(Constants.Globals.FreeObjectMethodTable), TestFreeObjectMethodTableGlobalAddress, null), - (nameof(Constants.Globals.MethodDescAlignment), GetMethodDescAlignment(Builder.TargetTestHelpers), nameof(DataType.uint64)), + (nameof(Constants.Globals.FreeObjectMethodTable), TestFreeObjectMethodTableGlobalAddress), + (nameof(Constants.Globals.MethodDescAlignment), GetMethodDescAlignment(Builder.TargetTestHelpers)), ]; } diff --git a/src/native/managed/cdacreader/tests/MockDescriptors/MockDescriptors.Thread.cs b/src/native/managed/cdacreader/tests/MockDescriptors/MockDescriptors.Thread.cs index aa0a239c41c29f..8b47fa9cb894a1 100644 --- a/src/native/managed/cdacreader/tests/MockDescriptors/MockDescriptors.Thread.cs +++ b/src/native/managed/cdacreader/tests/MockDescriptors/MockDescriptors.Thread.cs @@ -4,7 +4,7 @@ using System; using System.Collections.Generic; -namespace Microsoft.Diagnostics.DataContractReader.UnitTests; +namespace Microsoft.Diagnostics.DataContractReader.Tests; internal partial class MockDescriptors { @@ -15,12 +15,13 @@ public class Thread private const ulong DefaultAllocationRangeEnd = 0x0004_0000; internal Dictionary Types { get; } - internal (string Name, ulong Value, string? Type)[] Globals { get; } + internal (string Name, ulong Value)[] Globals { get; } internal TargetPointer FinalizerThreadAddress { get; } internal TargetPointer GCThreadAddress { get; } - private readonly MockMemorySpace.Builder _builder; + internal MockMemorySpace.Builder Builder { get; } + private readonly MockMemorySpace.BumpAllocator _allocator; private readonly TargetPointer _threadStoreAddress; @@ -34,8 +35,8 @@ public Thread(MockMemorySpace.Builder builder) public Thread(MockMemorySpace.Builder builder, (ulong Start, ulong End) allocationRange) { - _builder = builder; - _allocator = _builder.CreateAllocator(allocationRange.Start, allocationRange.End); + Builder = builder; + _allocator = Builder.CreateAllocator(allocationRange.Start, allocationRange.End); TargetTestHelpers helpers = builder.TargetTestHelpers; @@ -45,29 +46,29 @@ public Thread(MockMemorySpace.Builder builder, (ulong Start, ulong End) allocati MockMemorySpace.HeapFragment threadStoreGlobal = _allocator.Allocate((ulong)helpers.PointerSize, "[global pointer] ThreadStore"); MockMemorySpace.HeapFragment threadStore = _allocator.Allocate(Types[DataType.ThreadStore].Size.Value, "ThreadStore"); helpers.WritePointer(threadStoreGlobal.Data, threadStore.Address); - _builder.AddHeapFragments([threadStoreGlobal, threadStore]); + Builder.AddHeapFragments([threadStoreGlobal, threadStore]); _threadStoreAddress = threadStore.Address; // Add finalizer thread and set global to point at it MockMemorySpace.HeapFragment finalizerThreadGlobal = _allocator.Allocate((ulong)helpers.PointerSize, "[global pointer] Finalizer thread"); MockMemorySpace.HeapFragment finalizerThread = _allocator.Allocate(Types[DataType.Thread].Size.Value, "Finalizer thread"); helpers.WritePointer(finalizerThreadGlobal.Data, finalizerThread.Address); - _builder.AddHeapFragments([finalizerThreadGlobal, finalizerThread]); + Builder.AddHeapFragments([finalizerThreadGlobal, finalizerThread]); FinalizerThreadAddress = finalizerThread.Address; // Add GC thread and set global to point at it MockMemorySpace.HeapFragment gcThreadGlobal = _allocator.Allocate((ulong)helpers.PointerSize, "[global pointer] GC thread"); MockMemorySpace.HeapFragment gcThread = _allocator.Allocate(Types[DataType.Thread].Size.Value, "GC thread"); helpers.WritePointer(gcThreadGlobal.Data, gcThread.Address); - _builder.AddHeapFragments([gcThreadGlobal, gcThread]); + Builder.AddHeapFragments([gcThreadGlobal, gcThread]); GCThreadAddress = gcThread.Address; Globals = [ - (nameof(Constants.Globals.ThreadStore), threadStoreGlobal.Address, null), - (nameof(Constants.Globals.FinalizerThread), finalizerThreadGlobal.Address, null), - (nameof(Constants.Globals.GCThread), gcThreadGlobal.Address, null), - (nameof(Constants.Globals.FeatureEHFunclets), UseFunclets ? 1 : 0, null), + (nameof(Constants.Globals.ThreadStore), threadStoreGlobal.Address), + (nameof(Constants.Globals.FinalizerThread), finalizerThreadGlobal.Address), + (nameof(Constants.Globals.GCThread), gcThreadGlobal.Address), + (nameof(Constants.Globals.FeatureEHFunclets), UseFunclets ? 1 : 0), ]; } @@ -84,9 +85,9 @@ public Thread(MockMemorySpace.Builder builder, (ulong Start, ulong End) allocati internal void SetThreadCounts(int threadCount, int unstartedCount, int backgroundCount, int pendingCount, int deadCount) { - TargetTestHelpers helpers = _builder.TargetTestHelpers; + TargetTestHelpers helpers = Builder.TargetTestHelpers; Target.TypeInfo typeInfo = Types[DataType.ThreadStore]; - Span data = _builder.BorrowAddressRange(_threadStoreAddress, (int)typeInfo.Size.Value); + Span data = Builder.BorrowAddressRange(_threadStoreAddress, (int)typeInfo.Size.Value); helpers.Write( data.Slice(typeInfo.Fields[nameof(Data.ThreadStore.ThreadCount)].Offset), threadCount); @@ -106,7 +107,7 @@ internal void SetThreadCounts(int threadCount, int unstartedCount, int backgroun internal TargetPointer AddThread(uint id, TargetNUInt osId) { - TargetTestHelpers helpers = _builder.TargetTestHelpers; + TargetTestHelpers helpers = Builder.TargetTestHelpers; Target.TypeInfo typeInfo = Types[DataType.Thread]; if (UseFunclets) throw new NotImplementedException("todo for funclets: allocate the ExceptionInfo separately"); @@ -119,9 +120,8 @@ internal TargetPointer AddThread(uint id, TargetNUInt osId) helpers.WriteNUInt( data.Slice(typeInfo.Fields[nameof(Data.Thread.OSId)].Offset), osId); - _builder.AddHeapFragment(thread); + Builder.AddHeapFragment(thread); - // Add exception info for the thread // Add exception info for the thread // TODO: [cdac] Handle when UseFunclets is true - see NotImplementedException thrown above TargetPointer exceptionInfoAddress = thread.Address + Types[DataType.ExceptionInfo].Size.Value; @@ -134,7 +134,7 @@ internal TargetPointer AddThread(uint id, TargetNUInt osId) { // Set the next link for the previously added thread to the newly added one helpers.WritePointer( - _builder.BorrowAddressRange(_previousThread + threadLinkOffset, helpers.PointerSize), + Builder.BorrowAddressRange(_previousThread + threadLinkOffset, helpers.PointerSize), thread.Address + threadLinkOffset); } else @@ -142,7 +142,7 @@ internal TargetPointer AddThread(uint id, TargetNUInt osId) // Set the first thread link in the thread store ulong firstThreadLinkAddr = _threadStoreAddress + (ulong)Types[DataType.ThreadStore].Fields[nameof(Data.ThreadStore.FirstThreadLink)].Offset; helpers.WritePointer( - _builder.BorrowAddressRange(firstThreadLinkAddr, helpers.PointerSize), + Builder.BorrowAddressRange(firstThreadLinkAddr, helpers.PointerSize), thread.Address + threadLinkOffset); } diff --git a/src/native/managed/cdacreader/tests/MockDescriptors/MockDescriptors.cs b/src/native/managed/cdacreader/tests/MockDescriptors/MockDescriptors.cs index cb5bd5271708cf..50d3c76135c768 100644 --- a/src/native/managed/cdacreader/tests/MockDescriptors/MockDescriptors.cs +++ b/src/native/managed/cdacreader/tests/MockDescriptors/MockDescriptors.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; -namespace Microsoft.Diagnostics.DataContractReader.UnitTests; +namespace Microsoft.Diagnostics.DataContractReader.Tests; internal partial class MockDescriptors { diff --git a/src/native/managed/cdacreader/tests/MockMemorySpace.BumpAllocator.cs b/src/native/managed/cdacreader/tests/MockMemorySpace.BumpAllocator.cs index 17b052c09ca347..46e1130141b2cc 100644 --- a/src/native/managed/cdacreader/tests/MockMemorySpace.BumpAllocator.cs +++ b/src/native/managed/cdacreader/tests/MockMemorySpace.BumpAllocator.cs @@ -9,7 +9,7 @@ using System.Text; using Xunit; -namespace Microsoft.Diagnostics.DataContractReader.UnitTests; +namespace Microsoft.Diagnostics.DataContractReader.Tests; /// /// Helper for creating a mock memory space for testing. diff --git a/src/native/managed/cdacreader/tests/MockMemorySpace.cs b/src/native/managed/cdacreader/tests/MockMemorySpace.cs index 59a4026bc2454f..a6a7e26781a1ed 100644 --- a/src/native/managed/cdacreader/tests/MockMemorySpace.cs +++ b/src/native/managed/cdacreader/tests/MockMemorySpace.cs @@ -9,24 +9,16 @@ using System.Text; using Xunit; -namespace Microsoft.Diagnostics.DataContractReader.UnitTests; +namespace Microsoft.Diagnostics.DataContractReader.Tests; /// /// Helper for creating a mock memory space for testing. /// /// -/// Use MockMemorySpace.CreateContext to create a mostly empty context for reading from the target. -/// Use MockMemorySpace.ContextBuilder to create a context with additional MockMemorySpace.HeapFragment data. +/// Use MockMemorySpace.Builder to create a context with MockMemorySpace.HeapFragment data. /// internal unsafe static partial class MockMemorySpace { - // These addresses are arbitrary and are used to store the contract descriptor components. - // They should not overlap with any other heap fragment addresses. - private const ulong ContractDescriptorAddr = 0xaaaaaaaa; - // TODO: remove the references to these from TargetTestHelpers.ContractDescriptorFill - internal const uint JsonDescriptorAddr = 0xdddddddd; - internal const uint ContractPointerDataAddr = 0xeeeeeeee; - internal struct HeapFragment { public ulong Address; @@ -39,15 +31,9 @@ internal struct HeapFragment /// internal class Builder { - private bool _created = false; private readonly List _heapFragments = new(); private readonly List _allocators = new(); - private IReadOnlyCollection _contracts; - private IDictionary _types; - private IReadOnlyCollection<(string Name, ulong? Value, uint? IndirectIndex, string? TypeName)> _globals; - private IReadOnlyCollection _indirectValues; - private TargetTestHelpers _targetTestHelpers; public Builder(TargetTestHelpers targetTestHelpers) @@ -67,49 +53,8 @@ internal Span BorrowAddressRange(ulong address, int length) throw new InvalidOperationException($"No fragment includes addresses from 0x{address:x} with length {length}"); } - // TODO: contracts with versions - public Builder SetContracts(IReadOnlyCollection contracts) - { - if (_created) - throw new InvalidOperationException("Context already created"); - _contracts = contracts; - return this; - } - - public Builder SetTypes(IDictionary types) - { - if (_created) - throw new InvalidOperationException("Context already created"); - _types = types; - return this; - } - - public Builder SetGlobals(IReadOnlyCollection<(string Name, ulong Value, string? TypeName)> globals) - { - if (_created) - throw new InvalidOperationException("Context already created"); - if (_globals != null) - throw new InvalidOperationException("Globals already set"); - _globals = globals.Select(g => (g.Name, (ulong?)g.Value, (uint?)null, g.TypeName)).ToArray(); - _indirectValues = null; - return this; - } - - public Builder SetGlobals(IReadOnlyCollection<(string Name, ulong? Value, uint? IndirectIndex, string? TypeName)> globals, IReadOnlyCollection indirectValues) - { - if (_created) - throw new InvalidOperationException("Context already created"); - if (_globals != null) - throw new InvalidOperationException("Globals already set"); - _globals = globals; - _indirectValues = indirectValues; - return this; - } - public Builder AddHeapFragment(HeapFragment fragment) { - if (_created) - throw new InvalidOperationException("Context already created"); if (fragment.Data is null || fragment.Data.Length == 0) throw new InvalidOperationException($"Fragment '{fragment.Name}' data is empty"); if (!FragmentFits(fragment)) @@ -128,98 +73,6 @@ public Builder AddHeapFragments(IEnumerable fragments) return this; } - private HeapFragment CreateContractDescriptor(int jsonLength, int pointerDataCount) - { - byte[] descriptor = new byte[_targetTestHelpers.ContractDescriptorSize]; - _targetTestHelpers.ContractDescriptorFill(descriptor, jsonLength, pointerDataCount); - return new HeapFragment - { - Address = ContractDescriptorAddr, - Data = descriptor, - Name = "ContractDescriptor" - }; - } - - private string MakeContractsJson() - { - if (_contracts.Count == 0) - return string.Empty; - StringBuilder sb = new (); - foreach (var c in _contracts) - { - sb.Append($"\"{c}\": 1,"); - } - Debug.Assert(sb.Length > 0); - sb.Length--; // remove trailing comma - return sb.ToString(); - } - - private (HeapFragment json, HeapFragment pointerData) CreateDataDescriptor() - { - string metadataTypesJson = _types is not null ? TargetTestHelpers.MakeTypesJson(_types) : string.Empty; - string metadataGlobalsJson = _globals is not null ? TargetTestHelpers.MakeGlobalsJson(_globals) : string.Empty; - string interpolatedContracts = _contracts is not null ? MakeContractsJson() : string.Empty; - byte[] jsonBytes = Encoding.UTF8.GetBytes($$""" - { - "version": 0, - "baseline": "empty", - "contracts": { {{ interpolatedContracts }} }, - "types": { {{metadataTypesJson}} }, - "globals": { {{metadataGlobalsJson}} } - } - """); - HeapFragment json = new () { - Address = JsonDescriptorAddr, - Data = jsonBytes, - Name = "JsonDescriptor" - }; - - HeapFragment pointerData; - if (_indirectValues != null) - { - int pointerSize = _targetTestHelpers.PointerSize; - byte[] pointerDataBytes = new byte[_indirectValues.Count * pointerSize]; - int offset = 0; - foreach (var value in _indirectValues) - { - _targetTestHelpers.WritePointer(pointerDataBytes.AsSpan(offset, pointerSize), value); - offset += pointerSize; - } - pointerData =new HeapFragment { - Address = ContractPointerDataAddr, - Data = pointerDataBytes, - Name = "PointerData" - }; - } else { - pointerData = new HeapFragment - { - Address = ContractPointerDataAddr, - Data = Array.Empty(), - Name = "PointerData" - }; - } - return (json, pointerData); - } - - private ulong CreateDescriptorFragments() - { - if (_created) - throw new InvalidOperationException("Context already created"); - (var json, var pointerData) = CreateDataDescriptor(); - - int pointerDataCount = pointerData.Data is null ? 0 : pointerData.Data.Length / _targetTestHelpers.PointerSize; - - HeapFragment descriptor = CreateContractDescriptor(json.Data.Length, pointerDataCount); - - AddHeapFragment(descriptor); - AddHeapFragment(json); - if (pointerData.Data.Length > 0) - AddHeapFragment(pointerData); - - _created = true; - return descriptor.Address; - } - internal ReadContext GetReadContext() { ReadContext context = new ReadContext @@ -246,20 +99,9 @@ private bool FragmentFits(HeapFragment f) return true; } - public bool TryCreateTarget([NotNullWhen(true)] out ContractDescriptorTarget? target) - { - if (_created) - throw new InvalidOperationException("Context already created"); - ulong contractDescriptorAddress = CreateDescriptorFragments(); - ReadContext context = GetReadContext(); - return ContractDescriptorTarget.TryCreate(contractDescriptorAddress, context.ReadFromTarget, out target); - } - // Get an allocator for a range of addresses to simplify creating heap fragments public BumpAllocator CreateAllocator(ulong start, ulong end, int minAlign = 16) { - if (_created) - throw new InvalidOperationException("Context already created"); BumpAllocator allocator = new BumpAllocator(start, end) { MinAlign = minAlign }; foreach (var a in _allocators) { diff --git a/src/native/managed/cdacreader/tests/MockTarget.cs b/src/native/managed/cdacreader/tests/MockTarget.cs index ff313b861fb2e2..2950929cf8b08b 100644 --- a/src/native/managed/cdacreader/tests/MockTarget.cs +++ b/src/native/managed/cdacreader/tests/MockTarget.cs @@ -2,9 +2,8 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Collections.Generic; -using Xunit; -namespace Microsoft.Diagnostics.DataContractReader.UnitTests; +namespace Microsoft.Diagnostics.DataContractReader.Tests; public class MockTarget { diff --git a/src/native/managed/cdacreader/tests/ObjectTests.cs b/src/native/managed/cdacreader/tests/ObjectTests.cs index 7cdd2884fe4ed7..7df1b2b104942f 100644 --- a/src/native/managed/cdacreader/tests/ObjectTests.cs +++ b/src/native/managed/cdacreader/tests/ObjectTests.cs @@ -2,9 +2,11 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; +using Microsoft.Diagnostics.DataContractReader.Contracts; +using Moq; using Xunit; -namespace Microsoft.Diagnostics.DataContractReader.UnitTests; +namespace Microsoft.Diagnostics.DataContractReader.Tests; using MockObject = MockDescriptors.Object; @@ -17,15 +19,14 @@ private static void ObjectContractHelper(MockTarget.Architecture arch, Action( + c => c.Object == ((IContractFactory)new ObjectFactory()).CreateContract(target, 1) + && c.RuntimeTypeSystem == ((IContractFactory)new RuntimeTypeSystemFactory()).CreateContract(target, 1))); + testCase(target); } diff --git a/src/native/managed/cdacreader/tests/PrecodeStubsTests.cs b/src/native/managed/cdacreader/tests/PrecodeStubsTests.cs index c8ad543c5e2227..3d007d2a6bf5d7 100644 --- a/src/native/managed/cdacreader/tests/PrecodeStubsTests.cs +++ b/src/native/managed/cdacreader/tests/PrecodeStubsTests.cs @@ -9,7 +9,7 @@ using System; using System.Reflection; -namespace Microsoft.Diagnostics.DataContractReader.UnitTests; +namespace Microsoft.Diagnostics.DataContractReader.Tests; public class PrecodeStubsTests { diff --git a/src/native/managed/cdacreader/tests/TargetTestHelpers.cs b/src/native/managed/cdacreader/tests/TargetTestHelpers.cs index 4496edd8305a9a..e231cca54942f8 100644 --- a/src/native/managed/cdacreader/tests/TargetTestHelpers.cs +++ b/src/native/managed/cdacreader/tests/TargetTestHelpers.cs @@ -4,11 +4,9 @@ using System; using System.Buffers.Binary; using System.Collections.Generic; -using System.Linq; -using System.Runtime.InteropServices; using System.Text; -namespace Microsoft.Diagnostics.DataContractReader.UnitTests; +namespace Microsoft.Diagnostics.DataContractReader.Tests; internal unsafe class TargetTestHelpers { public MockTarget.Architecture Arch { get; init; } @@ -19,162 +17,8 @@ public TargetTestHelpers(MockTarget.Architecture arch) } public int PointerSize => Arch.Is64Bit ? sizeof(ulong) : sizeof(uint); - public ulong MaxSignedTargetAddress => (ulong)(Arch.Is64Bit ? long.MaxValue : int.MaxValue); - public int ContractDescriptorSize => ContractDescriptor.Size(Arch.Is64Bit); - - - #region Contract and data descriptor creation - - public void ContractDescriptorFill(Span dest, int jsonDescriptorSize, int pointerDataCount) - { - ContractDescriptor.Fill(dest, Arch, jsonDescriptorSize, pointerDataCount); - } - - internal static class ContractDescriptor - { - public static int Size(bool is64Bit) => is64Bit ? sizeof(ContractDescriptor64) : sizeof(ContractDescriptor32); - - public static void Fill(Span dest, MockTarget.Architecture arch, int jsonDescriptorSize, int pointerDataCount) - { - if (arch.Is64Bit) - { - ContractDescriptor64.Fill(dest, arch.IsLittleEndian, jsonDescriptorSize, pointerDataCount); - } - else - { - ContractDescriptor32.Fill(dest, arch.IsLittleEndian, jsonDescriptorSize, pointerDataCount); - } - } - - private struct ContractDescriptor32 - { - public ulong Magic = BitConverter.ToUInt64("DNCCDAC\0"u8); - public uint Flags = 0x2 /*32-bit*/ | 0x1; - public uint DescriptorSize; - public uint Descriptor = MockMemorySpace.JsonDescriptorAddr; - public uint PointerDataCount; - public uint Pad0 = 0; - public uint PointerData = MockMemorySpace.ContractPointerDataAddr; - - public ContractDescriptor32() { } - - public static void Fill(Span dest, bool isLittleEndian, int jsonDescriptorSize, int pointerDataCount) - { - ContractDescriptor32 descriptor = new() - { - DescriptorSize = (uint)jsonDescriptorSize, - PointerDataCount = (uint)pointerDataCount, - }; - if (BitConverter.IsLittleEndian != isLittleEndian) - descriptor.ReverseEndianness(); - - MemoryMarshal.AsBytes(MemoryMarshal.CreateSpan(ref descriptor, 1)).CopyTo(dest); - } - - private void ReverseEndianness() - { - Magic = BinaryPrimitives.ReverseEndianness(Magic); - Flags = BinaryPrimitives.ReverseEndianness(Flags); - DescriptorSize = BinaryPrimitives.ReverseEndianness(DescriptorSize); - Descriptor = BinaryPrimitives.ReverseEndianness(Descriptor); - PointerDataCount = BinaryPrimitives.ReverseEndianness(PointerDataCount); - Pad0 = BinaryPrimitives.ReverseEndianness(Pad0); - PointerData = BinaryPrimitives.ReverseEndianness(PointerData); - } - } - - private struct ContractDescriptor64 - { - public ulong Magic = BitConverter.ToUInt64("DNCCDAC\0"u8); - public uint Flags = 0x1; - public uint DescriptorSize; - public ulong Descriptor = MockMemorySpace.JsonDescriptorAddr; - public uint PointerDataCount; - public uint Pad0 = 0; - public ulong PointerData = MockMemorySpace.ContractPointerDataAddr; - - public ContractDescriptor64() { } - - public static void Fill(Span dest, bool isLittleEndian, int jsonDescriptorSize, int pointerDataCount) - { - ContractDescriptor64 descriptor = new() - { - DescriptorSize = (uint)jsonDescriptorSize, - PointerDataCount = (uint)pointerDataCount, - }; - if (BitConverter.IsLittleEndian != isLittleEndian) - descriptor.ReverseEndianness(); - - MemoryMarshal.AsBytes(MemoryMarshal.CreateSpan(ref descriptor, 1)).CopyTo(dest); - } - - private void ReverseEndianness() - { - Magic = BinaryPrimitives.ReverseEndianness(Magic); - Flags = BinaryPrimitives.ReverseEndianness(Flags); - DescriptorSize = BinaryPrimitives.ReverseEndianness(DescriptorSize); - Descriptor = BinaryPrimitives.ReverseEndianness(Descriptor); - PointerDataCount = BinaryPrimitives.ReverseEndianness(PointerDataCount); - Pad0 = BinaryPrimitives.ReverseEndianness(Pad0); - PointerData = BinaryPrimitives.ReverseEndianness(PointerData); - } - } - } - - #endregion Contract and data descriptor creation - - #region Data descriptor json formatting - private static string GetTypeJson(string name, Target.TypeInfo info) - { - string ret = string.Empty; - List fields = info.Size is null ? [] : [$"\"!\":{info.Size}"]; - fields.AddRange(info.Fields.Select(f => $"\"{f.Key}\":{(f.Value.TypeName is null ? f.Value.Offset : $"[{f.Value.Offset},\"{f.Value.TypeName}\"]")}")); - return $"\"{name}\":{{{string.Join(',', fields)}}}"; - } - - public static string MakeTypesJson(IDictionary types) - { - return string.Join(',', types.Select(t => GetTypeJson(t.Key.ToString(), t.Value))); - } - - public static string MakeGlobalsJson(IEnumerable<(string Name, ulong Value, string? Type)> globals) - { - return MakeGlobalsJson (globals.Select(g => (g.Name, (ulong?)g.Value, (uint?)null, g.Type))); - } - public static string MakeGlobalsJson(IEnumerable<(string Name, ulong? Value, uint? IndirectIndex, string? Type)> globals) - { - return string.Join(',', globals.Select(FormatGlobal)); - - static string FormatGlobal((string Name, ulong? Value, uint? IndirectIndex, string? Type) global) - { - if (global.Value is ulong value) - { - return $"\"{global.Name}\": {FormatValue(value, global.Type)}"; - } - else if (global.IndirectIndex is uint index) - { - return $"\"{global.Name}\": {FormatIndirect(index, global.Type)}"; - } - else - { - throw new InvalidOperationException("Global must have a value or indirect index"); - } - - } - static string FormatValue(ulong value, string? type) - { - return type is null ? $"{value}" : $"[{value},\"{type}\"]"; - } - static string FormatIndirect(uint value, string? type) - { - return type is null ? $"[{value}]" : $"[[{value}],\"{type}\"]"; - } - } - - #endregion Data descriptor json formatting - #region Mock memory initialization internal uint ObjHeaderSize => (uint)(Arch.Is64Bit ? 2 * sizeof(uint) /*alignpad + syncblock*/: sizeof(uint) /* syncblock */); @@ -316,7 +160,7 @@ internal int SizeOfTypeInfo(Target.TypeInfo info) #endregion Mock memory initialization - private int AlignUp(int offset, int align) + private static int AlignUp(int offset, int align) { return (offset + align - 1) & ~(align - 1); } diff --git a/src/native/managed/cdacreader/tests/TestPlaceholderTarget.cs b/src/native/managed/cdacreader/tests/TestPlaceholderTarget.cs index 11f68f27847c97..843266f69f370c 100644 --- a/src/native/managed/cdacreader/tests/TestPlaceholderTarget.cs +++ b/src/native/managed/cdacreader/tests/TestPlaceholderTarget.cs @@ -6,9 +6,10 @@ using System.Diagnostics.CodeAnalysis; using System.Numerics; using System.Runtime.CompilerServices; +using System.Text; using Moq; -namespace Microsoft.Diagnostics.DataContractReader.UnitTests; +namespace Microsoft.Diagnostics.DataContractReader.Tests; /// /// A mock implementation of Target that has basic implementations of getting types/globals and reading data @@ -61,9 +62,34 @@ public override TargetPointer ReadGlobalPointer(string name) public override TargetPointer ReadPointer(ulong address) => DefaultReadPointer(address); public override TargetCodePointer ReadCodePointer(ulong address) => DefaultReadCodePointer(address); - public override void ReadBuffer(ulong address, Span buffer) => throw new NotImplementedException(); + public override void ReadBuffer(ulong address, Span buffer) + { + if (_dataReader(address, buffer) < 0) + throw new InvalidOperationException($"Failed to read {buffer.Length} bytes at 0x{address:x8}."); + } + public override string ReadUtf8String(ulong address) => throw new NotImplementedException(); - public override string ReadUtf16String(ulong address) => throw new NotImplementedException(); + public override string ReadUtf16String(ulong address) + { + // Read characters until we find the null terminator + ulong end = address; + while (Read(end) != 0) + { + end += sizeof(char); + } + + int length = (int)(end - address); + if (length == 0) + return string.Empty; + + Span span = new byte[length]; + ReadBuffer(address, span); + string result = IsLittleEndian + ? Encoding.Unicode.GetString(span) + : Encoding.BigEndianUnicode.GetString(span); + return result; + } + public override TargetNUInt ReadNUInt(ulong address) => DefaultReadNUInt(address); public override T ReadGlobal(string name) { @@ -218,8 +244,6 @@ public DefaultDataCache(Target target) protected T DefaultGetOrAdd(TargetPointer address) where T : Data.IData { - if (address == TargetPointer.Null) - throw new ArgumentNullException(nameof(address)); if (TryGet(address, out T? result)) return result; diff --git a/src/native/managed/cdacreader/tests/ThreadTests.cs b/src/native/managed/cdacreader/tests/ThreadTests.cs index 83b40bee67d4a3..e6d5b26e67b68a 100644 --- a/src/native/managed/cdacreader/tests/ThreadTests.cs +++ b/src/native/managed/cdacreader/tests/ThreadTests.cs @@ -1,14 +1,24 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using Moq; using Xunit; -namespace Microsoft.Diagnostics.DataContractReader.UnitTests; +using Microsoft.Diagnostics.DataContractReader.Contracts; -using MockThread = MockDescriptors.Thread; +namespace Microsoft.Diagnostics.DataContractReader.Tests; public unsafe class ThreadTests { + private static Target CreateTarget(MockDescriptors.Thread thread) + { + MockTarget.Architecture arch = thread.Builder.TargetTestHelpers.Arch; + var target = new TestPlaceholderTarget(arch, thread.Builder.GetReadContext().ReadFromTarget, thread.Types, thread.Globals); + target.SetContracts(Mock.Of( + c => c.Thread == ((IContractFactory)new ThreadFactory()).CreateContract(target, 1))); + return target; + } + [Theory] [ClassData(typeof(MockTarget.StdArch))] public void GetThreadStoreData(MockTarget.Architecture arch) @@ -16,11 +26,7 @@ public void GetThreadStoreData(MockTarget.Architecture arch) // Set up the target TargetTestHelpers helpers = new(arch); MockMemorySpace.Builder builder = new(helpers); - MockThread thread = new(builder); - builder = builder - .SetContracts([nameof(Contracts.Thread)]) - .SetTypes(thread.Types) - .SetGlobals(thread.Globals); + MockDescriptors.Thread thread = new(builder); int threadCount = 15; int unstartedCount = 1; @@ -36,20 +42,19 @@ public void GetThreadStoreData(MockTarget.Architecture arch) pendingCount, deadCount); - bool success = builder.TryCreateTarget(out ContractDescriptorTarget? target); - Assert.True(success); + Target target = CreateTarget(thread); // Validate the expected thread counts - Contracts.IThread contract = target.Contracts.Thread; + IThread contract = target.Contracts.Thread; Assert.NotNull(contract); - Contracts.ThreadStoreCounts counts = contract.GetThreadCounts(); + ThreadStoreCounts counts = contract.GetThreadCounts(); Assert.Equal(unstartedCount, counts.UnstartedThreadCount); Assert.Equal(backgroundCount, counts.BackgroundThreadCount); Assert.Equal(pendingCount, counts.PendingThreadCount); Assert.Equal(deadCount, counts.DeadThreadCount); - Contracts.ThreadStoreData data = contract.GetThreadStoreData(); + ThreadStoreData data = contract.GetThreadStoreData(); Assert.Equal(threadCount, data.ThreadCount); Assert.Equal(thread.FinalizerThreadAddress, data.FinalizerThread); Assert.Equal(thread.GCThreadAddress, data.GCThread); @@ -62,11 +67,7 @@ public void GetThreadData(MockTarget.Architecture arch) // Set up the target TargetTestHelpers helpers = new(arch); MockMemorySpace.Builder builder = new(helpers); - MockThread thread = new(builder); - builder = builder - .SetContracts([nameof(Contracts.Thread)]) - .SetTypes(thread.Types) - .SetGlobals(thread.Globals); + MockDescriptors.Thread thread = new(builder); uint id = 1; TargetNUInt osId = new TargetNUInt(1234); @@ -74,14 +75,13 @@ public void GetThreadData(MockTarget.Architecture arch) // Add thread TargetPointer addr = thread.AddThread(id, osId); - bool success = builder.TryCreateTarget(out ContractDescriptorTarget? target); - Assert.True(success); + Target target = CreateTarget(thread); // Validate the expected thread counts - Contracts.IThread contract = target.Contracts.Thread; + IThread contract = target.Contracts.Thread; Assert.NotNull(contract); - Contracts.ThreadData data= contract.GetThreadData(addr); + ThreadData data= contract.GetThreadData(addr); Assert.Equal(id, data.Id); Assert.Equal(osId, data.OSId); } @@ -93,11 +93,7 @@ public void IterateThreads(MockTarget.Architecture arch) // Set up the target TargetTestHelpers helpers = new(arch); MockMemorySpace.Builder builder = new(helpers); - MockThread thread = new(builder); - builder = builder - .SetContracts([nameof(Contracts.Thread)]) - .SetTypes(thread.Types) - .SetGlobals(thread.Globals); + MockDescriptors.Thread thread = new(builder); // Add threads uint expectedCount = 10; @@ -107,11 +103,10 @@ public void IterateThreads(MockTarget.Architecture arch) thread.AddThread(i, new TargetNUInt(i + osIdStart)); } - bool success = builder.TryCreateTarget(out ContractDescriptorTarget? target); - Assert.True(success); + Target target = CreateTarget(thread); // Validate the expected thread counts - Contracts.IThread contract = target.Contracts.Thread; + IThread contract = target.Contracts.Thread; Assert.NotNull(contract); TargetPointer currentThread = contract.GetThreadStoreData().FirstThread; @@ -119,7 +114,7 @@ public void IterateThreads(MockTarget.Architecture arch) while (currentThread != TargetPointer.Null) { count++; - Contracts.ThreadData threadData = contract.GetThreadData(currentThread); + ThreadData threadData = contract.GetThreadData(currentThread); Assert.Equal(count, threadData.Id); Assert.Equal(count + osIdStart, threadData.OSId.Value); currentThread = threadData.NextThread;