|
// 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;
using System.Diagnostics.CodeAnalysis;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text;
using Microsoft.Diagnostics.DataContractReader.Data;
using Microsoft.Diagnostics.DataContractReader.Contracts;
using System.Collections.Frozen;
namespace Microsoft.Diagnostics.DataContractReader;
/// <summary>
/// Representation of the target under inspection
/// </summary>
/// <remarks>
/// This class provides APIs used by contracts for reading from the target and getting type and globals
/// information based on the target's contract descriptor. Like the contracts themselves in the cdac,
/// these are throwing APIs. Any callers at the boundaries (for example, unmanaged entry points, COM)
/// should handle any exceptions.
/// </remarks>
public sealed unsafe class ContractDescriptorTarget : Target
{
private const int StackAllocByteThreshold = 1024;
private readonly struct Configuration
{
public bool IsLittleEndian { get; init; }
public int PointerSize { get; init; }
}
private readonly Configuration _config;
private readonly DataTargetDelegates _dataTargetDelegates;
private readonly List<Descriptor> _descriptors = [];
// Addresses of sub-descriptor pointer slots whose value was null the last time we read
// them. Re-checked on Flush. This relies on the following invariant: a sub-descriptor
// pointer slot only ever transitions null -> real-address. Once a slot holds a
// non-null sub-descriptor address it never changes again, so we never need to
// re-validate already-loaded sub-descriptors and we can safely drop a slot from this.
private readonly List<TargetPointer> _pendingSubDescriptors = [];
private IReadOnlyDictionary<string, string> _contracts = new Dictionary<string, string>();
private IReadOnlyDictionary<string, GlobalValue> _globals = new Dictionary<string, GlobalValue>();
private IReadOnlyDictionary<string, TypeInfo> _types = new Dictionary<string, TypeInfo>();
public override ContractRegistry Contracts { get; }
public override DataCache ProcessedData { get; }
public delegate int ReadFromTargetDelegate(ulong address, Span<byte> bufferToFill);
public delegate int WriteToTargetDelegate(ulong address, Span<byte> bufferToWrite);
public delegate int GetTargetThreadContextDelegate(uint threadId, uint contextFlags, Span<byte> bufferToFill);
public delegate int AllocVirtualDelegate(ulong size, out ulong allocatedAddress);
private static readonly UTF8Encoding strictUTF8Encoding = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true);
private static readonly UTF8Encoding looseUTF8Encoding = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: false);
/// <summary>
/// Create a new target instance from a contract descriptor embedded in the target memory.
/// </summary>
/// <param name="contractDescriptor">The offset of the contract descriptor in the target memory</param>
/// <param name="readFromTarget">A callback to read memory blocks at a given address from the target</param>
/// <param name="writeToTarget">A callback to write memory blocks at a given address to the target</param>
/// <param name="getThreadContext">A callback to fetch a thread's context</param>
/// <param name="allocVirtual">A callback to allocate virtual memory in the target</param>
/// <param name="contractRegistrations">Registration actions that populate the contract registry (e.g., <see cref="Contracts.CoreCLRContracts.Register"/>)</param>
/// <param name="target">The target object.</param>
/// <returns>If a target instance could be created, <c>true</c>; otherwise, <c>false</c>.</returns>
public static bool TryCreate(
ulong contractDescriptor,
ReadFromTargetDelegate readFromTarget,
WriteToTargetDelegate writeToTarget,
GetTargetThreadContextDelegate getThreadContext,
AllocVirtualDelegate allocVirtual,
Action<ContractRegistry>[] contractRegistrations,
[NotNullWhen(true)] out ContractDescriptorTarget? target)
{
DataTargetDelegates dataTargetDelegates = new DataTargetDelegates(readFromTarget, writeToTarget, getThreadContext, allocVirtual);
if (TryReadContractDescriptor(
contractDescriptor,
dataTargetDelegates,
out Descriptor descriptor))
{
target = new ContractDescriptorTarget(descriptor, dataTargetDelegates, contractRegistrations);
return true;
}
target = null;
return false;
}
/// <summary>
/// Create a new target instance from an externally-provided contract descriptor.
/// </summary>
/// <param name="contractDescriptor">The contract descriptor to use for this target</param>
/// <param name="globalPointerValues">The values for any global pointers specified in the contract descriptor.</param>
/// <param name="readFromTarget">A callback to read memory blocks at a given address from the target</param>
/// <param name="writeToTarget">A callback to write memory blocks at a given address to the target</param>
/// <param name="getThreadContext">A callback to fetch a thread's context</param>
/// <param name="allocVirtual">A callback to allocate virtual memory in the target</param>
/// <param name="isLittleEndian">Whether the target is little-endian</param>
/// <param name="pointerSize">The size of a pointer in bytes in the target process.</param>
/// <param name="contractRegistrations">Registration actions that populate the contract registry (e.g., <see cref="Contracts.CoreCLRContracts.Register"/>)</param>
/// <returns>The target object.</returns>
public static ContractDescriptorTarget Create(
ContractDescriptorParser.ContractDescriptor contractDescriptor,
TargetPointer[] globalPointerValues,
ReadFromTargetDelegate readFromTarget,
WriteToTargetDelegate writeToTarget,
GetTargetThreadContextDelegate getThreadContext,
AllocVirtualDelegate allocVirtual,
bool isLittleEndian,
int pointerSize,
Action<ContractRegistry>[]? contractRegistrations = null)
{
return new ContractDescriptorTarget(
new Descriptor
{
Config = new Configuration { IsLittleEndian = isLittleEndian, PointerSize = pointerSize },
ContractDescriptor = contractDescriptor,
PointerData = globalPointerValues
},
new DataTargetDelegates(readFromTarget, writeToTarget, getThreadContext, allocVirtual),
contractRegistrations ?? []);
}
private ContractDescriptorTarget(Descriptor mainDescriptor, DataTargetDelegates dataTargetDelegates, Action<ContractRegistry>[] contractRegistrations)
{
Contracts = new CachingContractRegistry(this, this.TryGetContractVersion, contractRegistrations);
ProcessedData = new DataCache(this);
_config = mainDescriptor.Config;
_dataTargetDelegates = dataTargetDelegates;
AddDescriptor(mainDescriptor);
BuildDescriptors(forceBuild: true);
}
public override void Flush()
{
base.Flush();
BuildDescriptors();
}
private void AddDescriptor(Descriptor descriptor)
{
_descriptors.Add(descriptor);
foreach (TargetPointer pSubDescriptor in GetSubDescriptors(descriptor))
{
if (pSubDescriptor == TargetPointer.Null)
continue;
_pendingSubDescriptors.Add(pSubDescriptor);
}
}
private void BuildDescriptors(bool forceBuild = false)
{
// First pass - find if we have any new descriptors
// if not, we can exit early without needing to rebuild
int initialDescriptorCount = _descriptors.Count;
int loopDescriptorCount;
do
{
loopDescriptorCount = _descriptors.Count;
for (int i = _pendingSubDescriptors.Count - 1; i >= 0; i--)
{
TargetPointer pendingSubDescriptor = _pendingSubDescriptors[i];
if (TryReadPointer(pendingSubDescriptor, out TargetPointer subDescriptorAddress)
&& subDescriptorAddress != TargetPointer.Null)
{
_pendingSubDescriptors.RemoveAt(i);
if (TryReadContractDescriptor(
subDescriptorAddress.Value,
_dataTargetDelegates,
out Descriptor subDescriptor))
{
AddDescriptor(subDescriptor);
}
}
}
} while (_descriptors.Count > loopDescriptorCount);
if (_descriptors.Count == initialDescriptorCount && !forceBuild)
// No new descriptors were found, and we're not forcing a build, so return early
return;
// Second pass - parse all descriptors and update contracts, globals, and types.
Dictionary<string, string> contracts = [];
Dictionary<string, GlobalValue> globals = [];
Dictionary<string, TypeInfo> types = [];
HashSet<string> seenTypeNames = [];
HashSet<string> seenGlobalNames = [];
// Set pointer type size
types[DataType.pointer.ToString()] = new TypeInfo { Size = (uint)_config.PointerSize };
foreach (Descriptor descriptor in _descriptors)
{
if (descriptor.Config.IsLittleEndian != _config.IsLittleEndian ||
descriptor.Config.PointerSize != _config.PointerSize)
throw new InvalidOperationException("All descriptors must have the same endianness and pointer size.");
// Read contracts and add to map
foreach ((string name, string version) in descriptor.ContractDescriptor.Contracts ?? [])
{
if (contracts.ContainsKey(name))
{
throw new InvalidOperationException($"Duplicate contract name '{name}' found in contract descriptor.");
}
contracts[name] = version;
}
// Read types and map to known data types
if (descriptor.ContractDescriptor.Types is not null)
{
foreach ((string name, ContractDescriptorParser.TypeDescriptor type) in descriptor.ContractDescriptor.Types)
{
Dictionary<string, Target.FieldInfo> fieldInfos = [];
if (type.Fields is not null)
{
foreach ((string fieldName, ContractDescriptorParser.FieldDescriptor field) in type.Fields)
{
fieldInfos[fieldName] = new Target.FieldInfo()
{
Offset = field.Offset,
TypeName = field.Type
};
}
}
Target.TypeInfo typeInfo = new() { Size = type.Size, Fields = fieldInfos };
if (seenTypeNames.Contains(name))
{
throw new InvalidOperationException($"Duplicate type name '{name}' found in contract descriptor.");
}
seenTypeNames.Add(name);
types[name] = typeInfo;
}
}
// Read globals and map indirect values to pointer data
if (descriptor.ContractDescriptor.Globals is not null)
{
foreach ((string name, ContractDescriptorParser.GlobalDescriptor global) in descriptor.ContractDescriptor.Globals)
{
if (seenGlobalNames.Contains(name))
throw new InvalidOperationException($"Duplicate global name '{name}' found in contract descriptor.");
seenGlobalNames.Add(name);
if (global.Indirect)
{
if (global.NumericValue.Value >= (ulong)descriptor.PointerData.Length)
throw new InvalidOperationException($"Invalid pointer data index {global.NumericValue.Value}.");
globals[name] = new GlobalValue
{
NumericValue = descriptor.PointerData[global.NumericValue.Value].Value,
StringValue = global.StringValue,
Type = global.Type
};
}
else // direct
{
globals[name] = new GlobalValue
{
NumericValue = global.NumericValue,
StringValue = global.StringValue,
Type = global.Type
};
}
}
}
}
_contracts = contracts.ToFrozenDictionary();
_globals = globals.ToFrozenDictionary();
_types = types.ToFrozenDictionary();
}
private struct GlobalValue
{
public ulong? NumericValue;
public string? StringValue;
public string? Type;
}
private struct Descriptor
{
public Configuration Config { get; init; }
public ContractDescriptorParser.ContractDescriptor ContractDescriptor { get; init; }
public TargetPointer[] PointerData { get; init; }
}
private static IEnumerable<TargetPointer> GetSubDescriptors(Descriptor descriptor)
{
foreach (KeyValuePair<string, ContractDescriptorParser.GlobalDescriptor> subDescriptor in descriptor.ContractDescriptor?.SubDescriptors ?? [])
{
if (subDescriptor.Value.Indirect)
{
if (subDescriptor.Value.NumericValue.Value >= (ulong)descriptor.PointerData.Length)
throw new InvalidOperationException($"Invalid pointer data index {subDescriptor.Value.NumericValue.Value}.");
yield return descriptor.PointerData[(int)subDescriptor.Value.NumericValue];
}
}
}
// See docs/design/datacontracts/contract-descriptor.md
private static bool TryReadContractDescriptor(
ulong address,
DataTargetDelegates dataTargetDelegates,
out Descriptor descriptor)
{
descriptor = default;
// Magic - uint64_t
Span<byte> buffer = stackalloc byte[sizeof(ulong)];
if (dataTargetDelegates.ReadFromTarget(address, buffer) < 0)
return false;
address += sizeof(ulong);
ReadOnlySpan<byte> magicLE = "DNCCDAC\0"u8;
ReadOnlySpan<byte> magicBE = "\0CADCCND"u8;
bool isLittleEndian = buffer.SequenceEqual(magicLE);
if (!isLittleEndian && !buffer.SequenceEqual(magicBE))
return false;
// Flags - uint32_t
if (!TryRead(address, isLittleEndian, dataTargetDelegates, out uint flags))
return false;
address += sizeof(uint);
// Bit 1 represents the pointer size. 0 = 64-bit, 1 = 32-bit.
int pointerSize = (int)(flags & 0x2) == 0 ? sizeof(ulong) : sizeof(uint);
Configuration config = new Configuration { IsLittleEndian = isLittleEndian, PointerSize = pointerSize };
// Descriptor size - uint32_t
if (!TryRead(address, config.IsLittleEndian, dataTargetDelegates, out uint descriptorSize))
return false;
address += sizeof(uint);
// Descriptor - char*
if (!TryReadPointer(address, config, dataTargetDelegates, out TargetPointer descriptorAddr))
return false;
address += (uint)pointerSize;
// Pointer data count - uint32_t
if (!TryRead(address, config.IsLittleEndian, dataTargetDelegates, out uint pointerDataCount))
return false;
address += sizeof(uint);
// Padding - uint32_t
address += sizeof(uint);
// Pointer data - uintptr_t*
if (!TryReadPointer(address, config, dataTargetDelegates, out TargetPointer pointerDataAddr))
return false;
// Read descriptor
Span<byte> descriptorBuffer = descriptorSize <= StackAllocByteThreshold
? stackalloc byte[(int)descriptorSize]
: new byte[(int)descriptorSize];
if (dataTargetDelegates.ReadFromTarget(descriptorAddr.Value, descriptorBuffer) < 0)
return false;
ContractDescriptorParser.ContractDescriptor? contractDescriptor = ContractDescriptorParser.ParseCompact(descriptorBuffer);
if (contractDescriptor is null)
return false;
// Read pointer data
TargetPointer[] pointerData = new TargetPointer[pointerDataCount];
for (int i = 0; i < pointerDataCount; i++)
{
if (!TryReadPointer(pointerDataAddr.Value + (uint)(i * pointerSize), config, dataTargetDelegates, out pointerData[i]))
return false;
}
descriptor = new Descriptor
{
Config = config,
ContractDescriptor = contractDescriptor,
PointerData = pointerData
};
return true;
}
public override int PointerSize => _config.PointerSize;
public override bool IsLittleEndian => _config.IsLittleEndian;
public override bool TryGetThreadContext(ulong threadId, uint contextFlags, Span<byte> buffer)
{
// Underlying API only supports 32-bit thread IDs, mask off top 32 bits
int hr = _dataTargetDelegates.GetThreadContext((uint)(threadId & uint.MaxValue), contextFlags, buffer);
return hr == 0;
}
/// <summary>
/// Read a value from the target in target endianness
/// </summary>
/// <typeparam name="T">Type of value to read</typeparam>
/// <param name="address">Address to start reading from</param>
/// <returns>Value read from the target</returns>
/// <exception cref="VirtualReadException">Thrown when the read operation fails</exception>
public override T Read<T>(ulong address)
{
if (!TryRead(address, _config.IsLittleEndian, _dataTargetDelegates, out T value))
throw new VirtualReadException($"Failed to read {typeof(T)} at 0x{address:x8}.");
return value;
}
/// <summary>
/// Read a value from the target in little endianness
/// </summary>
/// <typeparam name="T">Type of value to read</typeparam>
/// <param name="address">Address to start reading from</param>
/// <returns>Value read from the target</returns>
public override T ReadLittleEndian<T>(ulong address)
{
if (!TryRead(address, true, _dataTargetDelegates, out T value))
throw new VirtualReadException($"Failed to read {typeof(T)} at 0x{address:x8}.");
return value;
}
/// <summary>
/// Read a value from the target in target endianness
/// </summary>
/// <typeparam name="T">Type of value to read</typeparam>
/// <param name="address">Address to start reading from</param>
/// <returns>True if read succeeds, false otherwise.</returns>
public override bool TryRead<T>(ulong address, out T value)
{
value = default;
if (!TryRead(address, _config.IsLittleEndian, _dataTargetDelegates, out T readValue))
return false;
value = readValue;
return true;
}
private static bool TryRead<T>(ulong address, bool isLittleEndian, DataTargetDelegates dataTargetDelegates, out T value) where T : unmanaged, IBinaryInteger<T>, IMinMaxValue<T>
{
value = default;
Span<byte> buffer = stackalloc byte[sizeof(T)];
if (dataTargetDelegates.ReadFromTarget(address, buffer) < 0)
return false;
return isLittleEndian
? T.TryReadLittleEndian(buffer, !IsSigned<T>(), out value)
: T.TryReadBigEndian(buffer, !IsSigned<T>(), out value);
}
/// <summary>
/// Write a value to the target in target endianness
/// </summary>
/// <typeparam name="T">Type of value to write</typeparam>
/// <param name="address">Address to start writing to</param>
public override void Write<T>(ulong address, T value)
{
if (!TryWrite(address, _config.IsLittleEndian, _dataTargetDelegates, value))
throw new InvalidOperationException($"Failed to write {typeof(T)} at 0x{address:x8}.");
}
public override void WritePointer(ulong address, TargetPointer value)
{
if (_config.PointerSize == 8)
Write<ulong>(address, value.Value);
else
Write<uint>(address, checked((uint)value.Value));
}
public override void WriteNUInt(ulong address, TargetNUInt value)
{
if (_config.PointerSize == 8)
Write<ulong>(address, value.Value);
else
Write<uint>(address, checked((uint)value.Value));
}
private static bool TryWrite<T>(ulong address, bool isLittleEndian, DataTargetDelegates dataTargetDelegates, T value) where T : unmanaged, IBinaryInteger<T>, IMinMaxValue<T>
{
Span<byte> buffer = stackalloc byte[sizeof(T)];
int bytesWritten = default;
bool success = isLittleEndian
? value.TryWriteLittleEndian(buffer, out bytesWritten)
: value.TryWriteBigEndian(buffer, out bytesWritten);
if (!success || bytesWritten != buffer.Length || dataTargetDelegates.WriteToTarget(address, buffer) < 0)
return false;
return true;
}
private static T Read<T>(ReadOnlySpan<byte> bytes, bool isLittleEndian) where T : unmanaged, IBinaryInteger<T>, IMinMaxValue<T>
{
if (sizeof(T) != bytes.Length)
throw new ArgumentException(nameof(bytes));
T value;
if (isLittleEndian)
{
T.TryReadLittleEndian(bytes, !IsSigned<T>(), out value);
}
else
{
T.TryReadBigEndian(bytes, !IsSigned<T>(), out value);
}
return value;
}
public override void ReadBuffer(ulong address, Span<byte> buffer)
{
if (!TryReadBuffer(address, buffer))
throw new VirtualReadException($"Failed to read {buffer.Length} bytes at 0x{address:x8}.");
}
private bool TryReadBuffer(ulong address, Span<byte> buffer)
{
return _dataTargetDelegates.ReadFromTarget(address, buffer) >= 0;
}
public override void WriteBuffer(ulong address, Span<byte> buffer)
{
if (!TryWriteBuffer(address, buffer))
throw new InvalidOperationException($"Failed to write {buffer.Length} bytes at 0x{address:x8}.");
}
public override TargetPointer AllocateMemory(uint size)
{
int hr = _dataTargetDelegates.AllocVirtual(size, out ulong allocatedAddress);
if (hr < 0)
throw Marshal.GetExceptionForHR(hr) ?? new InvalidOperationException($"Failed to allocate {size} bytes in the target process (HRESULT: 0x{hr:x8}).");
if (allocatedAddress == 0)
throw new OutOfMemoryException($"Failed to allocate {size} bytes in the target process (AllocVirtual returned S_OK but no address).");
return new TargetPointer(allocatedAddress);
}
private bool TryWriteBuffer(ulong address, Span<byte> buffer)
{
return _dataTargetDelegates.WriteToTarget(address, buffer) >= 0;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static bool IsSigned<T>() where T : struct, INumberBase<T>, IMinMaxValue<T>
{
return T.IsNegative(T.MinValue);
}
/// <summary>
/// Read a pointer from the target in target endianness
/// </summary>
/// <param name="address">Address to start reading from</param>
/// <returns>Pointer read from the target</returns>
public override TargetPointer ReadPointer(ulong address)
{
if (!TryReadPointer(address, _config, _dataTargetDelegates, out TargetPointer pointer))
throw new VirtualReadException($"Failed to read pointer at 0x{address:x8}.");
return pointer;
}
public override bool TryReadPointer(ulong address, out TargetPointer value)
=> TryReadPointer(address, _config, _dataTargetDelegates, out value);
public override TargetPointer ReadPointerFromSpan(ReadOnlySpan<byte> bytes)
{
if (_config.PointerSize == sizeof(uint))
{
return new TargetPointer(Read<uint>(bytes.Slice(0, sizeof(uint)), _config.IsLittleEndian));
}
else
{
return new TargetPointer(Read<ulong>(bytes.Slice(0, sizeof(ulong)), _config.IsLittleEndian));
}
}
public override TargetCodePointer ReadCodePointer(ulong address)
{
TypeInfo codePointerTypeInfo = this.GetTypeInfo(DataType.CodePointer);
if (codePointerTypeInfo.Size is sizeof(uint))
{
return new TargetCodePointer(Read<uint>(address));
}
else if (codePointerTypeInfo.Size is sizeof(ulong))
{
return new TargetCodePointer(Read<ulong>(address));
}
throw new VirtualReadException($"Failed to read code pointer at 0x{address:x8} because CodePointer size is not 4 or 8");
}
public override bool TryReadCodePointer(ulong address, out TargetCodePointer value)
{
TypeInfo codePointerTypeInfo = this.GetTypeInfo(DataType.CodePointer);
if (codePointerTypeInfo.Size is sizeof(uint))
{
if (TryRead<uint>(address, out uint val))
{
value = new TargetCodePointer(val);
return true;
}
}
else if (codePointerTypeInfo.Size is sizeof(ulong))
{
if (TryRead<ulong>(address, out ulong val))
{
value = new TargetCodePointer(val);
return true;
}
}
value = default;
return false;
}
public void ReadPointers(ulong address, Span<TargetPointer> buffer)
{
// TODO(cdac) - This could do a single read, and then swizzle in place if it is useful for performance
for (int i = 0; i < buffer.Length; i++)
{
buffer[i] = ReadPointer(address);
checked
{
address += (ulong)_config.PointerSize;
}
}
}
/// <summary>
/// Read a null-terminated UTF-8 string from the target
/// </summary>
/// <param name="address">Address to start reading from</param>
/// <param name="strict">Whether to throw on invalid UTF-8 sequences. If false, invalid sequences will be replaced with the replacement character.</param>
/// <returns>String read from the target</returns>
public override string ReadUtf8String(ulong address, bool strict = false)
{
// Read characters until we find the null terminator
ulong end = address;
while (Read<byte>(end) != 0)
{
end += sizeof(byte);
}
int length = (int)(end - address);
if (length == 0)
return string.Empty;
Span<byte> span = length <= StackAllocByteThreshold
? stackalloc byte[length]
: new byte[length];
ReadBuffer(address, span);
return strict ? strictUTF8Encoding.GetString(span) : looseUTF8Encoding.GetString(span);
}
/// <summary>
/// Read a null-terminated UTF-16 string from the target in target endianness
/// </summary>
/// <param name="address">Address to start reading from</param>
/// <returns>String read from the target</returns>
public override string ReadUtf16String(ulong address)
{
// Read characters until we find the null terminator
ulong end = address;
while (Read<char>(end) != 0)
{
end += sizeof(char);
}
int length = (int)(end - address);
if (length == 0)
return string.Empty;
Span<byte> span = length <= StackAllocByteThreshold
? stackalloc byte[length]
: new byte[length];
ReadBuffer(address, span);
string result = _config.IsLittleEndian
? Encoding.Unicode.GetString(span)
: Encoding.BigEndianUnicode.GetString(span);
return result;
}
/// <summary>
/// Read a native unsigned integer from the target in target endianness
/// </summary>
/// <param name="address">Address to start reading from</param>
/// <returns>Value read from the target</returns>
public override TargetNUInt ReadNUInt(ulong address)
{
if (!TryReadNUInt(address, _config, _dataTargetDelegates, out ulong value))
throw new VirtualReadException($"Failed to read nuint at 0x{address:x8}.");
return new TargetNUInt(value);
}
private static bool TryReadPointer(ulong address, Configuration config, DataTargetDelegates dataTargetDelegates, out TargetPointer pointer)
{
pointer = TargetPointer.Null;
if (!TryReadNUInt(address, config, dataTargetDelegates, out ulong value))
return false;
pointer = new TargetPointer(value);
return true;
}
private static bool TryReadNUInt(ulong address, Configuration config, DataTargetDelegates dataTargetDelegates, out ulong value)
{
value = 0;
if (config.PointerSize == sizeof(uint)
&& TryRead(address, config.IsLittleEndian, dataTargetDelegates, out uint value32))
{
value = value32;
return true;
}
else if (config.PointerSize == sizeof(ulong)
&& TryRead(address, config.IsLittleEndian, dataTargetDelegates, out ulong value64))
{
value = value64;
return true;
}
return false;
}
public static bool IsAligned(ulong value, int alignment)
=> (value & (ulong)(alignment - 1)) == 0;
public bool IsAlignedToPointerSize(uint value)
=> IsAligned(value, _config.PointerSize);
public bool IsAlignedToPointerSize(ulong value)
=> IsAligned(value, _config.PointerSize);
public override bool IsAlignedToPointerSize(TargetPointer pointer)
=> IsAligned(pointer.Value, _config.PointerSize);
#region reading globals
public override bool TryReadGlobal<T>(string name, [NotNullWhen(true)] out T? value)
=> TryReadGlobal<T>(name, out value, out _);
public bool TryReadGlobal<T>(string name, [NotNullWhen(true)] out T? value, out string? type) where T : struct, INumber<T>
{
value = null;
type = null;
if (!_globals.TryGetValue(name, out GlobalValue global) || global.NumericValue is null)
{
// Not found or does not contain a numeric value
return false;
}
type = global.Type;
value = T.CreateChecked(global.NumericValue.Value);
return true;
}
public override T ReadGlobal<T>(string name)
=> ReadGlobal<T>(name, out _);
public T ReadGlobal<T>(string name, out string? type) where T : struct, INumber<T>
{
if (!TryReadGlobal(name, out T? value, out type))
throw new InvalidOperationException($"Failed to read global {typeof(T)} '{name}'.");
return value.Value;
}
public override bool TryReadGlobalPointer(string name, [NotNullWhen(true)] out TargetPointer? value)
=> TryReadGlobalPointer(name, out value, out _);
public bool TryReadGlobalPointer(string name, [NotNullWhen(true)] out TargetPointer? value, out string? type)
{
value = null;
if (!TryReadGlobal(name, out ulong? innerValue, out type))
return false;
value = new TargetPointer(innerValue.Value);
return true;
}
public override TargetPointer ReadGlobalPointer(string name)
=> ReadGlobalPointer(name, out _);
public TargetPointer ReadGlobalPointer(string name, out string? type)
{
if (!TryReadGlobalPointer(name, out TargetPointer? value, out type))
throw new InvalidOperationException($"Failed to read global pointer '{name}'.");
return value.Value;
}
public override string ReadGlobalString(string name)
=> ReadStringGlobal(name, out _);
public string ReadStringGlobal(string name, out string? type)
{
if (!TryReadStringGlobal(name, out string? value, out type))
throw new InvalidOperationException($"Failed to read string global '{name}'.");
return value;
}
public override bool TryReadGlobalString(string name, [NotNullWhen(true)] out string? value)
=> TryReadStringGlobal(name, out value, out _);
public bool TryReadStringGlobal(string name, [NotNullWhen(true)] out string? value, out string? type)
{
value = null;
type = null;
if (!_globals.TryGetValue(name, out GlobalValue global) || global.StringValue is null)
{
// Not found or does not contain a string value
return false;
}
type = global.Type;
value = global.StringValue;
return true;
}
#endregion
public override Target.TypeInfo GetTypeInfo(string type)
{
if (_types.TryGetValue(type, out Target.TypeInfo typeInfo))
return typeInfo;
throw new InvalidOperationException($"Failed to get type info for '{type}'");
}
internal bool TryGetContractVersion(string contractName, [NotNullWhen(true)] out string? version)
{
return _contracts.TryGetValue(contractName, out version);
}
/// <summary>
/// Store of addresses that have already been read into corresponding data models.
/// This is simply used to avoid re-processing data on every request.
/// </summary>
public sealed class DataCache : Target.IDataCache
{
private readonly ContractDescriptorTarget _target;
private readonly Dictionary<(ulong, Type), object?> _readDataByAddress = [];
public DataCache(ContractDescriptorTarget target)
{
_target = target;
}
public T GetOrAdd<T>(TargetPointer address) where T : IData<T>
{
if (TryGet(address, out T? result))
return result;
T constructed = T.Create(_target, address);
if (_readDataByAddress.TryAdd((address, typeof(T)), constructed))
return constructed;
bool found = TryGet(address, out result);
Debug.Assert(found);
return result!;
}
public bool TryGet<T>(ulong address, [NotNullWhen(true)] out T? data)
{
data = default;
if (!_readDataByAddress.TryGetValue((address, typeof(T)), out object? dataObj))
return false;
if (dataObj is T dataMaybe)
{
data = dataMaybe;
return true;
}
return false;
}
public void Clear()
{
_readDataByAddress.Clear();
}
}
private readonly struct DataTargetDelegates(
ReadFromTargetDelegate readFromTarget,
WriteToTargetDelegate writeToTarget,
GetTargetThreadContextDelegate getThreadContext,
AllocVirtualDelegate allocVirtual)
{
public int ReadFromTarget(ulong address, Span<byte> buffer)
{
return readFromTarget(address, buffer);
}
public int ReadFromTarget(ulong address, byte* buffer, uint bytesToRead)
=> readFromTarget(address, new Span<byte>(buffer, checked((int)bytesToRead)));
public int GetThreadContext(uint threadId, uint contextFlags, Span<byte> buffer)
{
return getThreadContext(threadId, contextFlags, buffer);
}
public int WriteToTarget(ulong address, Span<byte> buffer)
{
return writeToTarget(address, buffer);
}
public int AllocVirtual(ulong size, out ulong allocatedAddress)
{
return allocVirtual(size, out allocatedAddress);
}
}
}
|