From 2d335f34af696feda5fc3e80f98c991b3aeb93d0 Mon Sep 17 00:00:00 2001 From: Elinor Fung Date: Thu, 25 Apr 2024 20:30:44 -0700 Subject: [PATCH] [cdac] Read/store globals from contract descriptor (#101450) - Map indirect global values to corresponding values in `pointer_data` array from contract descriptor - Add `[Try]Read`, `[Try]ReadPointer`, `[Try]ReadGlobal` and `[Try]ReadGlobalPointer` to `Target` - Make cDAC implementation of `ISOSDacInterface9.GetBreakingChangeVersion` read value from globals - Create test helpers for mocking out reading from the target and providing a contract descriptor for different bitness/endianness - Add unit tests for reading global values (direct and indirect) --- .../datacontracts/contract-descriptor.md | 4 +- src/coreclr/debug/daccess/cdac.cpp | 30 +- src/coreclr/debug/daccess/cdac.h | 9 +- src/coreclr/debug/daccess/daccess.cpp | 2 +- .../managed/cdacreader/src/Constants.cs | 13 + .../cdacreader/src/Legacy/SOSDacImpl.cs | 3 +- src/native/managed/cdacreader/src/Target.cs | 142 +++++-- .../managed/cdacreader/tests/TargetTests.cs | 358 ++++++++++++++++++ 8 files changed, 507 insertions(+), 54 deletions(-) create mode 100644 src/native/managed/cdacreader/src/Constants.cs create mode 100644 src/native/managed/cdacreader/tests/TargetTests.cs diff --git a/docs/design/datacontracts/contract-descriptor.md b/docs/design/datacontracts/contract-descriptor.md index e63ae11b4ae93..fbd58eb33eb9a 100644 --- a/docs/design/datacontracts/contract-descriptor.md +++ b/docs/design/datacontracts/contract-descriptor.md @@ -45,7 +45,7 @@ reserved bits should be written as zero. Diagnostic tooling may ignore non-zero The `descriptor` is a pointer to a UTF-8 JSON string described in [data descriptor physical layout](./data_descriptor.md#Physical_JSON_descriptor). The total number of bytes is given by `descriptor_size`. -The auxiliary data for the JSON descriptor is stored at the location `aux_data` in `aux_data_count` pointer-sized slots. +The auxiliary data for the JSON descriptor is stored at the location `pointer_data` in `pointer_data_count` pointer-sized slots. ### Architecture properties @@ -83,7 +83,7 @@ a JSON integer constant. "globals": { "FEATURE_COMINTEROP": 0, - "s_pThreadStore": [ 0 ] // indirect from aux data offset 0 + "s_pThreadStore": [ 0 ] // indirect from pointer data offset 0 }, "contracts": {"Thread": 1, "GCHandle": 1, "ThreadStore": 1} } diff --git a/src/coreclr/debug/daccess/cdac.cpp b/src/coreclr/debug/daccess/cdac.cpp index a4146709ec723..b7bb7585e6bcf 100644 --- a/src/coreclr/debug/daccess/cdac.cpp +++ b/src/coreclr/debug/daccess/cdac.cpp @@ -41,27 +41,31 @@ CDAC CDAC::Create(uint64_t descriptorAddr, ICorDebugDataTarget* target) { HMODULE cdacLib; if (!TryLoadCDACLibrary(&cdacLib)) - return CDAC::Invalid(); + return {}; - return CDAC{cdacLib, descriptorAddr, target}; + decltype(&cdac_reader_init) init = reinterpret_cast(::GetProcAddress(cdacLib, "cdac_reader_init")); + _ASSERTE(init != nullptr); + + intptr_t handle; + if (init(descriptorAddr, &ReadFromTargetCallback, target, &handle) != 0) + { + ::FreeLibrary(cdacLib); + return {}; + } + + return CDAC{cdacLib, handle, target}; } -CDAC::CDAC(HMODULE module, uint64_t descriptorAddr, ICorDebugDataTarget* target) - : m_module(module) +CDAC::CDAC(HMODULE module, intptr_t handle, ICorDebugDataTarget* target) + : m_module{module} + , m_cdac_handle{handle} , m_target{target} { - if (m_module == NULL) - { - m_cdac_handle = 0; - return; - } + _ASSERTE(m_module != NULL && m_cdac_handle != 0 && m_target != NULL); m_target->AddRef(); - decltype(&cdac_reader_init) init = reinterpret_cast(::GetProcAddress(m_module, "cdac_reader_init")); decltype(&cdac_reader_get_sos_interface) getSosInterface = reinterpret_cast(::GetProcAddress(m_module, "cdac_reader_get_sos_interface")); - _ASSERTE(init != nullptr && getSosInterface != nullptr); - - init(descriptorAddr, &ReadFromTargetCallback, m_target, &m_cdac_handle); + _ASSERTE(getSosInterface != nullptr); getSosInterface(m_cdac_handle, &m_sos); } diff --git a/src/coreclr/debug/daccess/cdac.h b/src/coreclr/debug/daccess/cdac.h index d5c0eecb0f737..51078ffcf2e46 100644 --- a/src/coreclr/debug/daccess/cdac.h +++ b/src/coreclr/debug/daccess/cdac.h @@ -9,12 +9,9 @@ class CDAC final public: // static static CDAC Create(uint64_t descriptorAddr, ICorDebugDataTarget *pDataTarget); - static CDAC Invalid() - { - return CDAC{nullptr, 0, nullptr}; - } - public: + CDAC() = default; + CDAC(const CDAC&) = delete; CDAC& operator=(const CDAC&) = delete; @@ -56,7 +53,7 @@ class CDAC final IUnknown* SosInterface(); private: - CDAC(HMODULE module, uint64_t descriptorAddr, ICorDebugDataTarget* target); + CDAC(HMODULE module, intptr_t handle, ICorDebugDataTarget* target); private: HMODULE m_module; diff --git a/src/coreclr/debug/daccess/daccess.cpp b/src/coreclr/debug/daccess/daccess.cpp index 971b8b90980b3..37e7d37edd9f9 100644 --- a/src/coreclr/debug/daccess/daccess.cpp +++ b/src/coreclr/debug/daccess/daccess.cpp @@ -3038,7 +3038,7 @@ class DacStreamManager //---------------------------------------------------------------------------- ClrDataAccess::ClrDataAccess(ICorDebugDataTarget * pTarget, ICLRDataTarget * pLegacyTarget/*=0*/) - : m_cdac{CDAC::Invalid()} + : m_cdac{} { SUPPORTS_DAC_HOST_ONLY; // ctor does no marshalling - don't check with DacCop diff --git a/src/native/managed/cdacreader/src/Constants.cs b/src/native/managed/cdacreader/src/Constants.cs new file mode 100644 index 0000000000000..0fbcaa0a246c7 --- /dev/null +++ b/src/native/managed/cdacreader/src/Constants.cs @@ -0,0 +1,13 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.Diagnostics.DataContractReader; + +internal static class Constants +{ + internal static class Globals + { + // See src/coreclr/debug/runtimeinfo/datadescriptor.h + internal const string SOSBreakingChangeVersion = nameof(SOSBreakingChangeVersion); + } +} diff --git a/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs b/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs index b0640c44efc98..261aa034c3f89 100644 --- a/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs +++ b/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs @@ -35,8 +35,7 @@ public SOSDacImpl(Target target) public int GetBreakingChangeVersion() { - // TODO: Return non-hard-coded version - return 4; + return _target.ReadGlobal(Constants.Globals.SOSBreakingChangeVersion); } public unsafe int GetCCWData(ulong ccw, void* data) => HResults.E_NOTIMPL; diff --git a/src/native/managed/cdacreader/src/Target.cs b/src/native/managed/cdacreader/src/Target.cs index 049a9f80ed226..c2e09d9d0fa11 100644 --- a/src/native/managed/cdacreader/src/Target.cs +++ b/src/native/managed/cdacreader/src/Target.cs @@ -4,6 +4,8 @@ using System; using System.Buffers.Binary; using System.Collections.Generic; +using System.Numerics; +using System.Runtime.CompilerServices; namespace Microsoft.Diagnostics.DataContractReader; @@ -15,7 +17,7 @@ public struct TargetPointer public TargetPointer(ulong value) => Value = value; } -internal sealed unsafe class Target +public sealed unsafe class Target { private const int StackAllocByteThreshold = 1024; @@ -29,7 +31,7 @@ private readonly struct Configuration private readonly Reader _reader; private readonly IReadOnlyDictionary _contracts = new Dictionary(); - private readonly TargetPointer[] _pointerData = []; + private readonly IReadOnlyDictionary _globals = new Dictionary(); public static bool TryCreate(ulong contractDescriptor, delegate* unmanaged readFromTarget, void* readContext, out Target? target) { @@ -49,11 +51,30 @@ private Target(Configuration config, ContractDescriptorParser.ContractDescriptor _config = config; _reader = reader; - // TODO: [cdac] Read globals and types + // TODO: [cdac] Read types // note: we will probably want to store the globals and types into a more usable form _contracts = descriptor.Contracts ?? []; - _pointerData = pointerData; + // Read globals and map indirect values to pointer data + if (descriptor.Globals is not null) + { + Dictionary globals = []; + foreach ((string name, ContractDescriptorParser.GlobalDescriptor global) in descriptor.Globals) + { + ulong value = global.Value; + if (global.Indirect) + { + if (value >= (ulong)pointerData.Length) + throw new InvalidOperationException($"Invalid pointer data index {value}."); + + value = pointerData[value].Value; + } + + globals[name] = (value, global.Type); + } + + _globals = globals; + } } // See docs/design/datacontracts/contract-descriptor.md @@ -81,7 +102,7 @@ private static bool TryReadContractDescriptor( return false; // Flags - uint32_t - if (!TryReadUInt32(address, isLittleEndian, reader, out uint flags)) + if (!TryRead(address, isLittleEndian, reader, out uint flags)) return false; address += sizeof(uint); @@ -92,7 +113,7 @@ private static bool TryReadContractDescriptor( config = new Configuration { IsLittleEndian = isLittleEndian, PointerSize = pointerSize }; // Descriptor size - uint32_t - if (!TryReadUInt32(address, config.IsLittleEndian, reader, out uint descriptorSize)) + if (!TryRead(address, config.IsLittleEndian, reader, out uint descriptorSize)) return false; address += sizeof(uint); @@ -104,7 +125,7 @@ private static bool TryReadContractDescriptor( address += (uint)pointerSize; // Pointer data count - uint32_t - if (!TryReadUInt32(address, config.IsLittleEndian, reader, out uint pointerDataCount)) + if (!TryRead(address, config.IsLittleEndian, reader, out uint pointerDataCount)) return false; address += sizeof(uint); @@ -138,30 +159,33 @@ private static bool TryReadContractDescriptor( return true; } - public uint ReadUInt32(ulong address) + public T Read(ulong address, out T value) where T : unmanaged, IBinaryInteger, IMinMaxValue { - if (!TryReadUInt32(address, out uint value)) - throw new InvalidOperationException($"Failed to read uint32 at 0x{address:x8}."); + if (!TryRead(address, out value)) + throw new InvalidOperationException($"Failed to read {typeof(T)} at 0x{address:x8}."); return value; } - public bool TryReadUInt32(ulong address, out uint value) - => TryReadUInt32(address, _config.IsLittleEndian, _reader, out value); + public bool TryRead(ulong address, out T value) where T : unmanaged, IBinaryInteger, IMinMaxValue + => TryRead(address, _config.IsLittleEndian, _reader, out value); - private static bool TryReadUInt32(ulong address, bool isLittleEndian, Reader reader, out uint value) + private static bool TryRead(ulong address, bool isLittleEndian, Reader reader, out T value) where T : unmanaged, IBinaryInteger, IMinMaxValue { - value = 0; - - Span buffer = stackalloc byte[sizeof(uint)]; + value = default; + Span buffer = stackalloc byte[sizeof(T)]; if (reader.ReadFromTarget(address, buffer) < 0) return false; - value = isLittleEndian - ? BinaryPrimitives.ReadUInt32LittleEndian(buffer) - : BinaryPrimitives.ReadUInt32BigEndian(buffer); + return isLittleEndian + ? T.TryReadLittleEndian(buffer, !IsSigned(), out value) + : T.TryReadBigEndian(buffer, !IsSigned(), out value); + } - return true; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool IsSigned() where T : struct, INumberBase, IMinMaxValue + { + return T.IsNegative(T.MinValue); } public TargetPointer ReadPointer(ulong address) @@ -183,21 +207,79 @@ private static bool TryReadPointer(ulong address, Configuration config, Reader r if (reader.ReadFromTarget(address, buffer) < 0) return false; - if (config.PointerSize == sizeof(uint)) + if (config.PointerSize == sizeof(uint) + && TryRead(address, config.IsLittleEndian, reader, out uint value32)) { - pointer = new TargetPointer( - config.IsLittleEndian - ? BinaryPrimitives.ReadUInt32LittleEndian(buffer) - : BinaryPrimitives.ReadUInt32BigEndian(buffer)); + pointer = new TargetPointer(value32); + return true; } - else if (config.PointerSize == sizeof(ulong)) + else if (config.PointerSize == sizeof(ulong) + && TryRead(address, config.IsLittleEndian, reader, out ulong value64)) { - pointer = new TargetPointer( - config.IsLittleEndian - ? BinaryPrimitives.ReadUInt64LittleEndian(buffer) - : BinaryPrimitives.ReadUInt64BigEndian(buffer)); + pointer = new TargetPointer(value64); + return true; } + return false; + } + + public T ReadGlobal(string name) where T : struct, INumber + { + if (!TryReadGlobal(name, out T value)) + throw new InvalidOperationException($"Failed to read global {typeof(T)} '{name}'."); + + return value; + } + + public bool TryReadGlobal(string name, out T value) where T : struct, INumber, INumberBase + { + value = default; + if (!_globals.TryGetValue(name, out (ulong Value, string? Type) global)) + return false; + + // TODO: [cdac] Move type validation out of the read such that it does not have to happen for every read + if (global.Type is not null) + { + string? expectedType = Type.GetTypeCode(typeof(T)) switch + { + TypeCode.SByte => "int8", + TypeCode.Byte => "uint8", + TypeCode.Int16 => "int16", + TypeCode.UInt16 => "uint16", + TypeCode.Int32 => "int32", + TypeCode.UInt32 => "uint32", + TypeCode.Int64 => "int64", + TypeCode.UInt64 => "uint64", + _ => null, + }; + if (expectedType is null || global.Type != expectedType) + { + return false; + } + } + + value = T.CreateChecked(global.Value); + return true; + } + + public TargetPointer ReadGlobalPointer(string name) + { + if (!TryReadGlobalPointer(name, out TargetPointer pointer)) + throw new InvalidOperationException($"Failed to read global pointer '{name}'."); + + return pointer; + } + + public bool TryReadGlobalPointer(string name, out TargetPointer pointer) + { + pointer = TargetPointer.Null; + if (!_globals.TryGetValue(name, out (ulong Value, string? Type) global)) + return false; + + if (global.Type is not null && Array.IndexOf(["pointer", "nint", "nuint"], global.Type) == -1) + return false; + + pointer = new TargetPointer(global.Value); return true; } diff --git a/src/native/managed/cdacreader/tests/TargetTests.cs b/src/native/managed/cdacreader/tests/TargetTests.cs new file mode 100644 index 0000000000000..5a8569c1b557c --- /dev/null +++ b/src/native/managed/cdacreader/tests/TargetTests.cs @@ -0,0 +1,358 @@ +// 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.Linq; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Text; +using Xunit; + +namespace Microsoft.Diagnostics.DataContractReader.UnitTests; + +public unsafe class TargetTests +{ + private const ulong ContractDescriptorAddr = 0xaaaaaaaa; + private const uint JsonDescriptorAddr = 0xdddddddd; + private const uint PointerDataAddr = 0xeeeeeeee; + + private static readonly (string Name, ulong Value, string? Type)[] TestGlobals = + [ + ("value", (ulong)sbyte.MaxValue, null), + ("int8Value", 0x12, "int8"), + ("uint8Value", 0x12, "uint8"), + ("int16Value", 0x1234, "int16"), + ("uint16Value", 0x1234, "uint16"), + ("int32Value", 0x12345678, "int32"), + ("uint32Value", 0x12345678, "uint32"), + ("int64Value", 0x123456789abcdef0, "int64"), + ("uint64Value", 0x123456789abcdef0, "uint64"), + ("nintValue", 0xabcdef0, "nint"), + ("nuintValue", 0xabcdef0, "nuint"), + ("pointerValue", 0xabcdef0, "pointer"), + ]; + + [Theory] + [InlineData(true, true)] + [InlineData(true, false)] + [InlineData(false, true)] + [InlineData(false, false)] + public void ReadGlobalValue(bool isLittleEndian, bool is64Bit) + { + string globalsJson = string.Join(',', TestGlobals.Select(i => $"\"{i.Name}\": {(i.Type is null ? i.Value.ToString() : $"[{i.Value}, \"{i.Type}\"]")}")); + byte[] json = Encoding.UTF8.GetBytes($$""" + { + "version": 0, + "baseline": "empty", + "contracts": {}, + "types": {}, + "globals": { {{globalsJson}} } + } + """); + Span descriptor = stackalloc byte[ContractDescriptor.Size(is64Bit)]; + ContractDescriptor.Fill(descriptor, isLittleEndian, is64Bit, json.Length, 0); + fixed (byte* jsonPtr = json) + { + ReadContext context = new ReadContext + { + ContractDescriptor = (byte*)Unsafe.AsPointer(ref MemoryMarshal.GetReference(descriptor)), + ContractDescriptorLength = descriptor.Length, + JsonDescriptor = jsonPtr, + JsonDescriptorLength = json.Length, + }; + + bool success = Target.TryCreate(ContractDescriptorAddr, &ReadFromTarget, &context, out Target? target); + Assert.True(success); + + ValidateGlobals(target, TestGlobals); + } + } + + [Theory] + [InlineData(true, true)] + [InlineData(true, false)] + [InlineData(false, true)] + [InlineData(false, false)] + public void ReadIndirectGlobalValue(bool isLittleEndian, bool is64Bit) + { + int pointerSize = is64Bit ? sizeof(ulong) : sizeof(uint); + Span pointerData = stackalloc byte[TestGlobals.Length * pointerSize]; + for (int i = 0; i < TestGlobals.Length; i++) + { + var (_, value, _) = TestGlobals[i]; + WritePointer(pointerData.Slice(i * pointerSize), value, isLittleEndian, pointerSize); + } + + string globalsJson = string.Join(',', TestGlobals.Select((g, i) => $"\"{g.Name}\": {(g.Type is null ? $"[{i}]" : $"[[{i}], \"{g.Type}\"]")}")); + byte[] json = Encoding.UTF8.GetBytes($$""" + { + "version": 0, + "baseline": "empty", + "contracts": {}, + "types": {}, + "globals": { {{globalsJson}} } + } + """); + Span descriptor = stackalloc byte[ContractDescriptor.Size(is64Bit)]; + ContractDescriptor.Fill(descriptor, isLittleEndian, is64Bit, json.Length, pointerData.Length / pointerSize); + fixed (byte* jsonPtr = json) + { + ReadContext context = new ReadContext + { + ContractDescriptor = (byte*)Unsafe.AsPointer(ref MemoryMarshal.GetReference(descriptor)), + ContractDescriptorLength = descriptor.Length, + JsonDescriptor = jsonPtr, + JsonDescriptorLength = json.Length, + PointerData = (byte*)Unsafe.AsPointer(ref MemoryMarshal.GetReference(pointerData)), + PointerDataLength = pointerData.Length + }; + + bool success = Target.TryCreate(ContractDescriptorAddr, &ReadFromTarget, &context, out Target? target); + Assert.True(success); + + // Indirect values are pointer-sized, so max 32-bits for a 32-bit target + var expected = is64Bit + ? TestGlobals + : TestGlobals.Select(g => (g.Name, g.Value & 0xffffffff, g.Type)).ToArray(); + + ValidateGlobals(target, expected); + } + } + + private static void WritePointer(Span dest, ulong value, bool isLittleEndian, int pointerSize) + { + if (pointerSize == sizeof(ulong)) + { + if (isLittleEndian) + { + BinaryPrimitives.WriteUInt64LittleEndian(dest, value); + } + else + { + BinaryPrimitives.WriteUInt64BigEndian(dest, value); + } + } + else if (pointerSize == sizeof(uint)) + { + if (isLittleEndian) + { + BinaryPrimitives.WriteUInt32LittleEndian(dest, (uint)value); + } + else + { + BinaryPrimitives.WriteUInt32BigEndian(dest, (uint)value); + } + } + } + + private static void ValidateGlobals( + Target target, + (string Name, ulong Value, string? Type)[] globals, + [CallerMemberName] string caller = "", + [CallerFilePath] string filePath = "", + [CallerLineNumber] int lineNumber = 0) + { + foreach (var (name, value, type) in globals) + { + // Validate that each global can/cannot be read successfully based on its type + // and that it matches the expected value if successfully read + { + bool success = target.TryReadGlobal(name, out sbyte actual); + AssertEqualsWithCallerInfo(type is null || type == "int8", success); + if (success) + AssertEqualsWithCallerInfo((sbyte)value, actual); + } + { + bool success = target.TryReadGlobal(name, out byte actual); + AssertEqualsWithCallerInfo(type is null || type == "uint8", success); + if (success) + AssertEqualsWithCallerInfo(value, actual); + } + { + bool success = target.TryReadGlobal(name, out short actual); + AssertEqualsWithCallerInfo(type is null || type == "int16", success); + if (success) + AssertEqualsWithCallerInfo((short)value, actual); + } + { + bool success = target.TryReadGlobal(name, out ushort actual); + AssertEqualsWithCallerInfo(type is null || type == "uint16", success); + if (success) + AssertEqualsWithCallerInfo(value, actual); + } + { + bool success = target.TryReadGlobal(name, out int actual); + AssertEqualsWithCallerInfo(type is null || type == "int32", success); + if (success) + AssertEqualsWithCallerInfo((int)value, actual); + } + { + bool success = target.TryReadGlobal(name, out uint actual); + AssertEqualsWithCallerInfo(type is null || type == "uint32", success); + if (success) + AssertEqualsWithCallerInfo(value, actual); + } + { + bool success = target.TryReadGlobal(name, out long actual); + AssertEqualsWithCallerInfo(type is null || type == "int64", success); + if (success) + AssertEqualsWithCallerInfo((long)value, actual); + } + { + bool success = target.TryReadGlobal(name, out ulong actual); + AssertEqualsWithCallerInfo(type is null || type == "uint64", success); + if (success) + AssertEqualsWithCallerInfo(value, actual); + } + { + bool success = target.TryReadGlobalPointer(name, out TargetPointer actual); + AssertEqualsWithCallerInfo(type is null || type == "pointer" || type == "nint" || type == "nuint", success); + if (success) + AssertEqualsWithCallerInfo(value, actual.Value); + } + } + + void AssertEqualsWithCallerInfo(T expected, T actual) where T : unmanaged + { + Assert.True(expected.Equals(actual), $"Expected: {expected}. Actual: {actual}. [test case: {caller} in {filePath}:{lineNumber}]"); + } + } + + [UnmanagedCallersOnly] + private static int ReadFromTarget(ulong address, byte* buffer, uint length, void* context) + { + ReadContext* readContext = (ReadContext*)context; + var span = new Span(buffer, (int)length); + + // Populate the span with the requested portion of the contract descriptor + if (address >= ContractDescriptorAddr && address <= ContractDescriptorAddr + (ulong)readContext->ContractDescriptorLength - length) + { + ulong offset = address - ContractDescriptorAddr; + new ReadOnlySpan(readContext->ContractDescriptor + offset, (int)length).CopyTo(span); + return 0; + } + + // Populate the span with the JSON descriptor - this assumes the product will read it all at once. + if (address == JsonDescriptorAddr) + { + new ReadOnlySpan(readContext->JsonDescriptor, readContext->JsonDescriptorLength).CopyTo(span); + return 0; + } + + // Populate the span with the requested portion of the pointer data + if (address >= PointerDataAddr && address <= PointerDataAddr + (ulong)readContext->PointerDataLength - length) + { + ulong offset = address - PointerDataAddr; + new ReadOnlySpan(readContext->PointerData + offset, (int)length).CopyTo(span); + return 0; + } + + return -1; + } + + // Used by ReadFromTarget to return the appropriate bytes + private struct ReadContext + { + public byte* ContractDescriptor; + public int ContractDescriptorLength; + + public byte* JsonDescriptor; + public int JsonDescriptorLength; + + public byte* PointerData; + public int PointerDataLength; + } + + private static class ContractDescriptor + { + public static int Size(bool is64Bit) => is64Bit ? sizeof(ContractDescriptor64) : sizeof(ContractDescriptor32); + + public static void Fill(Span dest, bool isLittleEndian, bool is64Bit, int jsonDescriptorSize, int pointerDataCount) + { + if (is64Bit) + { + ContractDescriptor64.Fill(dest, isLittleEndian, jsonDescriptorSize, pointerDataCount); + } + else + { + ContractDescriptor32.Fill(dest, isLittleEndian, jsonDescriptorSize, pointerDataCount); + } + } + + private struct ContractDescriptor32 + { + public ulong Magic = BitConverter.ToUInt64("DNCCDAC\0"u8); + public uint Flags = 0x2 /*32-bit*/ | 0x1; + public uint DescriptorSize; + public uint Descriptor = JsonDescriptorAddr; + public uint PointerDataCount; + public uint Pad0 = 0; + public uint PointerData = PointerDataAddr; + + public ContractDescriptor32() { } + + public static void Fill(Span dest, bool isLittleEndian, int jsonDescriptorSize, int pointerDataCount) + { + ContractDescriptor32 descriptor = new() + { + DescriptorSize = (uint)jsonDescriptorSize, + PointerDataCount = (uint)pointerDataCount, + }; + if (BitConverter.IsLittleEndian != isLittleEndian) + descriptor.ReverseEndianness(); + + MemoryMarshal.AsBytes(MemoryMarshal.CreateSpan(ref descriptor, 1)).CopyTo(dest); + } + + private void ReverseEndianness() + { + Magic = BinaryPrimitives.ReverseEndianness(Magic); + Flags = BinaryPrimitives.ReverseEndianness(Flags); + DescriptorSize = BinaryPrimitives.ReverseEndianness(DescriptorSize); + Descriptor = BinaryPrimitives.ReverseEndianness(Descriptor); + PointerDataCount = BinaryPrimitives.ReverseEndianness(PointerDataCount); + Pad0 = BinaryPrimitives.ReverseEndianness(Pad0); + PointerData = BinaryPrimitives.ReverseEndianness(PointerData); + } + } + + private struct ContractDescriptor64 + { + public ulong Magic = BitConverter.ToUInt64("DNCCDAC\0"u8); + public uint Flags = 0x1; + public uint DescriptorSize; + public ulong Descriptor = JsonDescriptorAddr; + public uint PointerDataCount; + public uint Pad0 = 0; + public ulong PointerData = PointerDataAddr; + + public ContractDescriptor64() { } + + public static void Fill(Span dest, bool isLittleEndian, int jsonDescriptorSize, int pointerDataCount) + { + ContractDescriptor64 descriptor = new() + { + DescriptorSize = (uint)jsonDescriptorSize, + PointerDataCount = (uint)pointerDataCount, + }; + if (BitConverter.IsLittleEndian != isLittleEndian) + descriptor.ReverseEndianness(); + + MemoryMarshal.AsBytes(MemoryMarshal.CreateSpan(ref descriptor, 1)).CopyTo(dest); + } + + private void ReverseEndianness() + { + Magic = BinaryPrimitives.ReverseEndianness(Magic); + Flags = BinaryPrimitives.ReverseEndianness(Flags); + DescriptorSize = BinaryPrimitives.ReverseEndianness(DescriptorSize); + Descriptor = BinaryPrimitives.ReverseEndianness(Descriptor); + PointerDataCount = BinaryPrimitives.ReverseEndianness(PointerDataCount); + Pad0 = BinaryPrimitives.ReverseEndianness(Pad0); + PointerData = BinaryPrimitives.ReverseEndianness(PointerData); + } + } + } + +}