// 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.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Reflection.Metadata;
using System.Reflection.Metadata.Ecma335;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.Marshalling;
using Microsoft.Diagnostics.DataContractReader.Contracts;
namespace Microsoft.Diagnostics.DataContractReader.Legacy;
/// <summary>
/// Describes a resolved physical location for a variable value.
/// A variable can span up to 2 locations (e.g., split across register and stack).
/// </summary>
public readonly struct NativeVarLocation
{
public ulong AddressOrValue { get; init; }
public ulong Size { get; init; }
public bool IsRegisterValue { get; init; }
}
[GeneratedComClass]
public sealed unsafe partial class ClrDataValue : IXCLRDataValue
{
private readonly Target _target;
private readonly TargetPointer _threadAddress;
private readonly IXCLRDataValue? _legacyImpl;
private readonly uint _flags;
private readonly ITypeHandle? _typeHandle;
private readonly TargetPointer _baseAddress;
private readonly ulong _totalSize;
private readonly NativeVarLocation[] _locations;
public ClrDataValue(
Target target,
TargetPointer threadAddress,
uint flags,
ITypeHandle? typeHandle,
TargetPointer baseAddress,
NativeVarLocation[] locations,
IXCLRDataValue? legacyImpl)
{
_target = target;
_threadAddress = threadAddress;
_legacyImpl = legacyImpl;
_flags = flags;
_typeHandle = typeHandle;
_baseAddress = baseAddress;
_locations = locations;
if (_locations.Length > 0 && (_flags & (uint)ClrDataValueFlag.IS_REFERENCE) != 0)
{
_totalSize = (ulong)_target.PointerSize;
}
else
{
_totalSize = 0;
foreach (NativeVarLocation loc in _locations)
{
_totalSize += loc.Size;
}
}
}
int IXCLRDataValue.GetFlags(uint* flags)
{
int hr = HResults.S_OK;
try
{
*flags = _flags;
}
catch (System.Exception ex)
{
hr = ex.HResult;
}
#if DEBUG
if (_legacyImpl is not null)
{
uint flagsLocal;
int hrLocal = _legacyImpl.GetFlags(&flagsLocal);
Debug.ValidateHResult(hr, hrLocal);
if (hr >= 0)
Debug.Assert(*flags == flagsLocal, $"GetFlags cDAC: 0x{*flags:X}, DAC: 0x{flagsLocal:X}");
}
#endif
return hr;
}
int IXCLRDataValue.GetAddress(ClrDataAddress* address)
{
int hr = HResults.S_OK;
try
{
*address = 0;
if (_locations.Length != 1 || _locations[0].IsRegisterValue)
{
throw new InvalidCastException(); // E_NOINTERFACE
}
*address = _locations[0].AddressOrValue;
}
catch (System.Exception ex)
{
hr = ex.HResult;
}
#if DEBUG
if (_legacyImpl is not null)
{
ClrDataAddress addressLocal;
int hrLocal = _legacyImpl.GetAddress(&addressLocal);
Debug.ValidateHResult(hr, hrLocal);
if (hr >= 0)
Debug.Assert((ulong)*address == (ulong)addressLocal, $"GetAddress cDAC: 0x{(ulong)*address:X}, DAC: 0x{(ulong)addressLocal:X}");
}
#endif
return hr;
}
int IXCLRDataValue.GetSize(ulong* size)
{
int hr = HResults.S_OK;
try
{
if (_totalSize == 0)
{
*size = 0;
throw new InvalidCastException(); // E_NOINTERFACE
}
*size = _totalSize;
}
catch (System.Exception ex)
{
hr = ex.HResult;
}
#if DEBUG
if (_legacyImpl is not null)
{
ulong sizeLocal;
int hrLocal = _legacyImpl.GetSize(&sizeLocal);
Debug.ValidateHResult(hr, hrLocal);
if (hr >= 0)
Debug.Assert(*size == sizeLocal, $"GetSize cDAC: {*size}, DAC: {sizeLocal}");
}
#endif
return hr;
}
int IXCLRDataValue.GetBytes(uint bufLen, uint* dataSize, byte* buffer)
{
int hr = HResults.S_OK;
try
{
if (_totalSize == 0)
throw new InvalidCastException(); // E_NOINTERFACE
if (dataSize is not null)
*dataSize = (uint)_totalSize;
if (bufLen < _totalSize)
throw Marshal.GetExceptionForHR(/*ERROR_BUFFER_OVERFLOW*/ CorDbgHResults.ERROR_BUFFER_OVERFLOW)!;
byte* dst = buffer;
foreach (NativeVarLocation loc in _locations)
{
if (loc.IsRegisterValue)
{
int size = (int)loc.Size;
ulong value = loc.AddressOrValue;
for (int i = 0; i < size; i++)
{
dst[i] = (byte)(value & 0xFF);
value >>= 8;
}
}
else
{
Span<byte> memBytes = new(dst, (int)loc.Size);
_target.ReadBuffer(loc.AddressOrValue, memBytes);
}
dst += loc.Size;
}
}
catch (System.Exception ex)
{
hr = ex.HResult;
}
#if DEBUG
if (_legacyImpl is not null)
{
byte[] legacyBuf = new byte[bufLen];
uint legacyDataSize;
int hrLocal;
fixed (byte* pLegacy = legacyBuf)
{
hrLocal = _legacyImpl.GetBytes(bufLen, &legacyDataSize, pLegacy);
}
Debug.ValidateHResult(hr, hrLocal);
if (hr >= 0 && hrLocal >= 0)
{
if (dataSize is not null)
Debug.Assert(*dataSize == legacyDataSize, $"GetBytes dataSize cDAC: {*dataSize}, DAC: {legacyDataSize}");
int compareLen = (int)Math.Min(_totalSize, legacyDataSize);
for (int i = 0; i < compareLen; i++)
Debug.Assert(buffer[i] == legacyBuf[i], $"GetBytes mismatch at byte {i}: cDAC: 0x{buffer[i]:X2}, DAC: 0x{legacyBuf[i]:X2}");
}
}
#endif
return hr;
}
int IXCLRDataValue.SetBytes(uint bufLen, uint* dataSize, byte* buffer) => HResults.E_NOTIMPL;
int IXCLRDataValue.GetType(DacComNullableByRef<IXCLRDataTypeInstance> typeInstance)
{
int hr = HResults.S_OK;
IXCLRDataTypeInstance? legacyType = null;
int hrLocal = HResults.S_OK;
if (_legacyImpl is not null)
{
DacComNullableByRef<IXCLRDataTypeInstance> legacyTypeOut = new(isNullRef: false);
hrLocal = _legacyImpl.GetType(legacyTypeOut);
if (hrLocal >= 0)
legacyType = legacyTypeOut.Interface;
}
try
{
if ((_flags & (uint)ClrDataValueFlag.IS_REFERENCE) != 0)
{
typeInstance.Interface = null;
hr = HResults.S_FALSE;
}
else if (_typeHandle is null)
{
hr = HResults.E_NOTIMPL;
}
else
{
typeInstance.Interface = new ClrDataTypeInstance(_target, _typeHandle, legacyType);
}
}
catch (System.Exception ex)
{
hr = ex.HResult;
}
#if DEBUG
if (_legacyImpl is not null)
Debug.ValidateHResult(hr, hrLocal);
#endif
return hr;
}
int IXCLRDataValue.GetNumFields(uint* numFields) => HResults.E_NOTIMPL;
int IXCLRDataValue.GetFieldByIndex(
uint index,
DacComNullableByRef<IXCLRDataValue> field,
uint bufLen,
uint* nameLen,
char* nameBuf,
uint* token)
=> HResults.E_NOTIMPL;
int IXCLRDataValue.Request(uint reqCode, uint inBufferSize, byte* inBuffer, uint outBufferSize, byte* outBuffer)
{
int hr = HResults.S_OK;
try
{
if (reqCode != (uint)CLRDataGeneralRequest.CLRDATA_REQUEST_REVISION
|| inBufferSize != 0
|| inBuffer is not null
|| outBufferSize != sizeof(uint))
{
throw new ArgumentException("Invalid request parameters.");
}
if (outBuffer is null)
throw new NullReferenceException("The output buffer is null.");
*(uint*)outBuffer = 3;
}
catch (System.Exception ex)
{
hr = ex.HResult;
}
#if DEBUG
if (_legacyImpl is not null)
{
uint revisionLocal = 0;
int hrLocal = _legacyImpl.Request(
reqCode,
inBufferSize,
inBuffer,
outBufferSize,
outBuffer is null ? null : (byte*)&revisionLocal);
Debug.ValidateHResult(hr, hrLocal);
if (hr == HResults.S_OK)
Debug.Assert(*(uint*)outBuffer == revisionLocal);
}
#endif
return hr;
}
int IXCLRDataValue.GetNumFields2(uint flags, IXCLRDataTypeInstance? fromType, uint* numFields)
{
int hr = HResults.S_OK;
try
{
*numFields = 0;
ValidateFieldFlags(flags);
*numFields = (_flags & (uint)ClrDataValueFlag.IS_REFERENCE) != 0
? 0
: checked((uint)GetFields(flags, fromType).Count);
}
catch (System.Exception ex)
{
hr = ex.HResult;
}
#if DEBUG
if (_legacyImpl is not null)
{
uint numFieldsLocal = 0;
int hrLocal = _legacyImpl.GetNumFields2(flags, GetLegacyType(fromType), &numFieldsLocal);
Debug.ValidateHResult(hr, hrLocal);
if (hr == HResults.S_OK)
Debug.Assert(*numFields == numFieldsLocal, $"GetNumFields2 cDAC: {*numFields}, DAC: {numFieldsLocal}");
}
#endif
return hr;
}
int IXCLRDataValue.StartEnumFields(uint flags, IXCLRDataTypeInstance? fromType, ulong* handle)
=> StartEnumFields(null, (uint)CLRDataByNameFlag.CLRDATA_BYNAME_CASE_SENSITIVE, flags, fromType, handle);
int IXCLRDataValue.EnumField(
ulong* handle,
DacComNullableByRef<IXCLRDataValue> field,
uint nameBufLen,
uint* nameLen,
char* nameBuf,
uint* token)
=> EnumField(handle, field, nameBufLen, nameLen, nameBuf, token, byName: false);
int IXCLRDataValue.EndEnumFields(ulong handle) => EndEnumFields(handle, byName: false);
int IXCLRDataValue.StartEnumFieldsByName(char* name, uint nameFlags, uint fieldFlags, IXCLRDataTypeInstance? fromType, ulong* handle)
=> StartEnumFields(name is null ? null : new string(name), nameFlags, fieldFlags, fromType, handle);
int IXCLRDataValue.EnumFieldByName(ulong* handle, DacComNullableByRef<IXCLRDataValue> field, uint* token)
=> EnumField(handle, field, 0, null, null, token, byName: true);
int IXCLRDataValue.EndEnumFieldsByName(ulong handle) => EndEnumFields(handle, byName: true);
int IXCLRDataValue.GetFieldByToken(
uint token,
DacComNullableByRef<IXCLRDataValue> field,
uint bufLen,
uint* nameLen,
char* nameBuf)
=> HResults.E_NOTIMPL;
int IXCLRDataValue.GetAssociatedValue(DacComNullableByRef<IXCLRDataValue> assocValue)
{
int hr = HResults.S_OK;
IXCLRDataValue? legacyValue = null;
int hrLocal = HResults.S_OK;
if (_legacyImpl is not null)
{
DacComNullableByRef<IXCLRDataValue> legacyValueOut = new(isNullRef: false);
hrLocal = _legacyImpl.GetAssociatedValue(legacyValueOut);
if (hrLocal >= 0)
legacyValue = legacyValueOut.Interface;
}
try
{
if (_locations.Length == 0
|| (_flags & (uint)ClrDataValueFlag.IS_REFERENCE) == 0
|| _typeHandle is null)
{
throw new InvalidCastException();
}
NativeVarLocation currentLocation = _locations[0];
ulong address = currentLocation.IsRegisterValue
? currentLocation.AddressOrValue
: _target.ReadPointer(new TargetPointer(currentLocation.AddressOrValue)).Value;
uint flags = GetTypeFieldValueFlags(_typeHandle, null, _flags & (uint)ClrDataValueFlag.ALL_LOCATIONS, isDeref: true);
NativeVarLocation location = new()
{
AddressOrValue = address,
Size = _target.Contracts.RuntimeTypeSystem.GetBaseSize(_typeHandle),
IsRegisterValue = false,
};
assocValue.Interface = new ClrDataValue(_target, _threadAddress, flags, _typeHandle, address, [location], legacyValue);
}
catch (System.Exception ex)
{
hr = ex.HResult;
}
#if DEBUG
if (_legacyImpl is not null)
Debug.ValidateHResult(hr, hrLocal);
#endif
return hr;
}
int IXCLRDataValue.GetAssociatedType(DacComNullableByRef<IXCLRDataTypeInstance> assocType)
{
int hr = HResults.S_OK;
IXCLRDataTypeInstance? legacyType = null;
int hrLocal = HResults.S_OK;
if (_legacyImpl is not null)
{
DacComNullableByRef<IXCLRDataTypeInstance> legacyTypeOut = new(isNullRef: false);
hrLocal = _legacyImpl.GetAssociatedType(legacyTypeOut);
if (hrLocal >= 0)
legacyType = legacyTypeOut.Interface;
}
try
{
ITypeHandle? typeHandle = null;
if ((_flags & (uint)ClrDataValueFlag.IS_REFERENCE) != 0)
{
typeHandle = _typeHandle;
}
else if ((_flags & (uint)ClrDataValueFlag.IS_ARRAY) != 0)
{
IRuntimeTypeSystem rts = _target.Contracts.RuntimeTypeSystem;
TargetPointer methodTable = _target.Contracts.Object.GetMethodTableAddress(_baseAddress);
typeHandle = rts.GetTypeParam(rts.GetTypeHandle(methodTable));
}
if (typeHandle is null)
throw new InvalidCastException();
assocType.Interface = new ClrDataTypeInstance(_target, typeHandle, legacyType);
}
catch (System.Exception ex)
{
hr = ex.HResult;
}
#if DEBUG
if (_legacyImpl is not null)
Debug.ValidateHResult(hr, hrLocal);
#endif
return hr;
}
int IXCLRDataValue.GetString(uint bufLen, uint* strLen, char* str)
{
int hr = HResults.S_OK;
try
{
if ((_flags & (uint)ClrDataValueFlag.IS_STRING) == 0)
throw new ArgumentException();
string value = _target.Contracts.Object.GetStringValue(_baseAddress);
OutputBufferHelpers.CopyStringToBuffer(str, bufLen, strLen, value);
if (str is null || bufLen < value.Length + 1)
hr = HResults.S_FALSE;
}
catch (System.Exception ex)
{
hr = ex.HResult;
}
#if DEBUG
if (_legacyImpl is not null)
{
uint strLenLocal = 0;
char[] strLocal = new char[bufLen > 0 ? bufLen : 1];
int hrLocal;
fixed (char* strLocalPtr = strLocal)
hrLocal = _legacyImpl.GetString(bufLen, &strLenLocal, str is null ? null : strLocalPtr);
Debug.ValidateHResult(hr, hrLocal);
if (strLen is not null)
Debug.Assert(*strLen == strLenLocal, $"GetString length cDAC: {*strLen}, DAC: {strLenLocal}");
if (str is not null && hr >= 0)
{
fixed (char* strLocalPtr = strLocal)
Debug.Assert(new string(str) == new string(strLocalPtr));
}
}
#endif
return hr;
}
int IXCLRDataValue.GetArrayProperties(uint* rank, uint* totalElements, uint numDim, [Out, MarshalUsing(CountElementName = nameof(numDim))] uint[] dims, uint numBases, [Out, MarshalUsing(CountElementName = nameof(numBases))] int[] bases)
{
int hr = HResults.S_OK;
try
{
if ((_flags & (uint)ClrDataValueFlag.IS_ARRAY) == 0)
throw new ArgumentException();
IRuntimeTypeSystem rts = _target.Contracts.RuntimeTypeSystem;
ITypeHandle arrayType = rts.GetTypeHandle(_target.Contracts.Object.GetMethodTableAddress(_baseAddress));
rts.IsArray(arrayType, out uint arrayRank);
_target.Contracts.Object.GetArrayData(_baseAddress, out uint count, out _, out _, out uint[] dimensionLengths, out int[] lowerBoundsValues);
if (rank is not null)
*rank = arrayRank;
if (totalElements is not null)
*totalElements = count;
for (uint i = 0; i < Math.Min(numDim, arrayRank); i++)
dims[i] = dimensionLengths[i];
for (uint i = 0; i < Math.Min(numBases, arrayRank); i++)
bases[i] = lowerBoundsValues[i];
}
catch (System.Exception ex)
{
hr = ex.HResult;
}
#if DEBUG
if (_legacyImpl is not null)
{
uint rankLocal = 0;
uint totalElementsLocal = 0;
uint[] dimsLocal = new uint[numDim];
int[] basesLocal = new int[numBases];
int hrLocal = _legacyImpl.GetArrayProperties(rank is null ? null : &rankLocal, totalElements is null ? null : &totalElementsLocal, numDim, dimsLocal, numBases, basesLocal);
Debug.ValidateHResult(hr, hrLocal);
if (hr == HResults.S_OK)
{
Debug.Assert(rank is null || *rank == rankLocal);
Debug.Assert(totalElements is null || *totalElements == totalElementsLocal);
Debug.Assert(dims.AsSpan().SequenceEqual(dimsLocal));
Debug.Assert(bases.AsSpan().SequenceEqual(basesLocal));
}
}
#endif
return hr;
}
int IXCLRDataValue.GetArrayElement(uint numInd, [In, MarshalUsing(CountElementName = nameof(numInd))] int[] indices, DacComNullableByRef<IXCLRDataValue> value)
{
int hr = HResults.S_OK;
IXCLRDataValue? legacyValue = null;
int hrLocal = HResults.S_OK;
if (_legacyImpl is not null)
{
DacComNullableByRef<IXCLRDataValue> legacyValueOut = new(isNullRef: false);
hrLocal = _legacyImpl.GetArrayElement(numInd, indices, legacyValueOut);
if (hrLocal >= 0)
legacyValue = legacyValueOut.Interface;
}
try
{
if ((_flags & (uint)ClrDataValueFlag.IS_ARRAY) == 0)
throw new ArgumentException();
IRuntimeTypeSystem rts = _target.Contracts.RuntimeTypeSystem;
ITypeHandle arrayType = rts.GetTypeHandle(_target.Contracts.Object.GetMethodTableAddress(_baseAddress));
if (!rts.IsArray(arrayType, out uint rank) || numInd != rank)
throw new ArgumentException();
ITypeHandle elementType = rts.GetTypeParam(arrayType);
TargetPointer data = _target.Contracts.Object.GetArrayData(_baseAddress, out _, out _, out _, out uint[] dimensionLengths, out int[] lowerBoundsValues);
ulong offset = data.Value;
ulong dimensionSize = rts.GetComponentSize(arrayType);
for (uint dimension = rank; dimension-- > 0;)
{
int lowerBound = lowerBoundsValues[dimension];
uint dimensionLength = dimensionLengths[dimension];
if (indices[dimension] < lowerBound)
throw new ArgumentException();
uint index = checked((uint)(indices[dimension] - lowerBound));
if (index >= dimensionLength)
throw new ArgumentException();
offset = checked(offset + (dimensionSize * index));
dimensionSize = checked(dimensionSize * dimensionLength);
}
NativeVarLocation location = new()
{
AddressOrValue = offset,
Size = GetTypeSize(elementType),
IsRegisterValue = false,
};
uint flags = GetTypeFieldValueFlags(elementType, null, 0, isDeref: false);
value.Interface = new ClrDataValue(_target, _threadAddress, flags, elementType, offset, [location], legacyValue);
}
catch (System.Exception ex)
{
hr = ex.HResult;
}
#if DEBUG
if (_legacyImpl is not null)
Debug.ValidateHResult(hr, hrLocal);
#endif
return hr;
}
private sealed class FieldEnumeration : IEnum<FieldEntry>
{
public IEnumerator<FieldEntry> Enumerator { get; }
public nuint LegacyHandle { get; set; }
public FieldEnumeration(IEnumerable<FieldEntry> fields, nuint legacyHandle)
{
Enumerator = fields.GetEnumerator();
LegacyHandle = legacyHandle;
}
}
private readonly record struct FieldEntry(TargetPointer FieldDesc, bool IsInherited);
private int StartEnumFields(string? name, uint nameFlags, uint fieldFlags, IXCLRDataTypeInstance? fromType, ulong* handle)
{
int hr = HResults.S_OK;
int hrLocal = HResults.S_OK;
ulong legacyHandle = 0;
if (_legacyImpl is not null)
{
IXCLRDataTypeInstance? legacyFromType = GetLegacyType(fromType);
if (name is null)
{
hrLocal = _legacyImpl.StartEnumFields(fieldFlags, legacyFromType, &legacyHandle);
}
else
{
fixed (char* namePtr = name)
hrLocal = _legacyImpl.StartEnumFieldsByName(namePtr, nameFlags, fieldFlags, legacyFromType, &legacyHandle);
}
}
try
{
if (handle is null)
throw new ArgumentNullException(nameof(handle));
*handle = 0;
if (nameFlags > (uint)CLRDataByNameFlag.CLRDATA_BYNAME_CASE_INSENSITIVE)
throw new ArgumentException(nameof(nameFlags));
ValidateFieldFlags(fieldFlags);
List<FieldEntry> fields = GetFields(fieldFlags, fromType);
if (name is not null)
{
int separatorIndex = name.LastIndexOf('.');
string memberName = separatorIndex >= 0 ? name[(separatorIndex + 1)..] : name;
StringComparison comparison = nameFlags == (uint)CLRDataByNameFlag.CLRDATA_BYNAME_CASE_INSENSITIVE
? StringComparison.OrdinalIgnoreCase
: StringComparison.Ordinal;
fields = fields.Where(entry => string.Equals(GetFieldMetadata(entry.FieldDesc).Name, memberName, comparison)).ToList();
}
FieldEnumeration enumeration = new(fields, (nuint)legacyHandle);
*handle = (ulong)((IEnum<FieldEntry>)enumeration).GetHandle();
}
catch (System.Exception ex)
{
hr = ex.HResult;
if (_legacyImpl is not null && hrLocal == HResults.S_OK)
{
if (name is null)
_legacyImpl.EndEnumFields(legacyHandle);
else
_legacyImpl.EndEnumFieldsByName(legacyHandle);
}
}
#if DEBUG
if (_legacyImpl is not null)
Debug.ValidateHResult(hr, hrLocal);
#endif
return hr;
}
private int EnumField(ulong* handle, DacComNullableByRef<IXCLRDataValue> field, uint nameBufLen, uint* nameLen, char* nameBuf, uint* token, bool byName)
{
int hr = HResults.S_OK;
FieldEnumeration? enumeration = null;
int hrLocal = HResults.S_OK;
IXCLRDataValue? legacyField = null;
uint nameLenLocal = 0;
uint tokenLocal = 0;
char[] nameBufLocal = new char[nameBufLen > 0 ? nameBufLen : 1];
try
{
if (handle is null || *handle == 0)
throw new ArgumentException("Invalid field handle.", nameof(handle));
GCHandle gcHandle = GCHandle.FromIntPtr((IntPtr)(*handle));
if (gcHandle.Target is not FieldEnumeration fields)
throw new ArgumentException("Invalid field handle.", nameof(handle));
enumeration = fields;
if (_legacyImpl is not null && enumeration.LegacyHandle != 0)
{
ulong legacyHandle = (ulong)enumeration.LegacyHandle;
DacComNullableByRef<IXCLRDataValue> legacyFieldOut = new(isNullRef: false);
fixed (char* nameBufLocalPtr = nameBufLocal)
{
hrLocal = byName
? _legacyImpl.EnumFieldByName(&legacyHandle, legacyFieldOut, &tokenLocal)
: _legacyImpl.EnumField(&legacyHandle, legacyFieldOut, nameBufLen, &nameLenLocal, nameBuf is null ? null : nameBufLocalPtr, &tokenLocal);
}
enumeration.LegacyHandle = (nuint)legacyHandle;
if (hrLocal >= 0)
legacyField = legacyFieldOut.Interface;
}
if (!enumeration.Enumerator.MoveNext())
{
hr = HResults.S_FALSE;
}
else
{
FieldEntry entry = enumeration.Enumerator.Current;
(string fieldName, uint fieldToken, FieldDefinition fieldDefinition, ITypeHandle enclosingType) = GetFieldMetadata(entry.FieldDesc);
OutputBufferHelpers.CopyStringToBuffer(nameBuf, nameBufLen, nameLen, fieldName);
if (nameBuf is not null && nameBufLen != 0 && nameBufLen < fieldName.Length + 1)
{
hr = unchecked((int)0x8007007A);
}
else
{
if (token is not null)
*token = fieldToken;
if (!field.IsNullRef)
field.Interface = CreateFieldValue(entry, fieldDefinition, enclosingType, legacyField);
}
}
}
catch (System.Exception ex)
{
hr = ex.HResult;
}
#if DEBUG
if (_legacyImpl is not null && enumeration is not null)
{
Debug.ValidateHResult(hr, hrLocal, HResultValidationMode.AllowCdacSuccess);
if (hr == HResults.S_OK && hrLocal >= 0)
{
Debug.Assert(token is null || *token == tokenLocal);
Debug.Assert(nameLen is null || *nameLen == nameLenLocal);
Debug.Assert(nameBuf is null || new ReadOnlySpan<char>(nameBuf, checked((int)nameLenLocal)).SequenceEqual(nameBufLocal.AsSpan(0, checked((int)nameLenLocal))));
}
}
#endif
return hr;
}
private int EndEnumFields(ulong handle, bool byName)
{
int hr = HResults.S_OK;
nuint legacyHandle = 0;
try
{
if (handle != 0)
{
GCHandle gcHandle = GCHandle.FromIntPtr((IntPtr)handle);
if (gcHandle.Target is not FieldEnumeration enumeration)
throw new ArgumentException("Invalid field handle.", nameof(handle));
legacyHandle = enumeration.LegacyHandle;
((IEnum<FieldEntry>)enumeration).Dispose();
gcHandle.Free();
}
}
catch (System.Exception ex)
{
hr = ex.HResult;
}
int hrLocal = HResults.S_OK;
if (_legacyImpl is not null && legacyHandle != 0)
{
hrLocal = byName
? _legacyImpl.EndEnumFieldsByName((ulong)legacyHandle)
: _legacyImpl.EndEnumFields((ulong)legacyHandle);
}
#if DEBUG
if (_legacyImpl is not null && legacyHandle != 0)
Debug.ValidateHResult(hr, hrLocal);
#endif
return hr;
}
private static void ValidateFieldFlags(uint flags)
{
if ((flags & ~(uint)ClrDataValueFlag.ALL_FIELDS) != 0
|| (flags & (uint)ClrDataValueFlag.ALL_KINDS) != (uint)ClrDataValueFlag.ALL_KINDS
|| (flags & (uint)ClrDataValueFlag.ALL_LOCATIONS) == 0)
{
throw new ArgumentException(nameof(flags));
}
}
private List<FieldEntry> GetFields(uint flags, IXCLRDataTypeInstance? fromType)
{
bool includeParents = (flags & (uint)ClrDataValueFlag.IS_INHERITED) != 0;
if (includeParents && fromType is not null)
throw new ArgumentException(nameof(fromType));
ITypeHandle? typeHandle = !includeParents && fromType is ClrDataTypeInstance fromTypeInstance
? fromTypeInstance.TypeHandle
: _typeHandle;
if (typeHandle is null)
throw new ArgumentException(nameof(flags));
bool includeInstanceFields = (flags & (uint)ClrDataValueFlag.FROM_INSTANCE) != 0;
bool includeStaticFields = (flags & (uint)ClrDataValueFlag.FROM_STATIC) != 0;
IRuntimeTypeSystem rts = _target.Contracts.RuntimeTypeSystem;
List<ITypeHandle> types = [];
ITypeHandle current = typeHandle;
do
{
types.Add(current);
TargetPointer parent = includeParents ? rts.GetParentMethodTable(current) : TargetPointer.Null;
if (parent == TargetPointer.Null)
break;
current = rts.GetTypeHandle(parent);
}
while (true);
types.Reverse();
List<FieldEntry> fields = [];
for (int i = 0; i < types.Count; i++)
{
bool inherited = i != types.Count - 1;
foreach (TargetPointer fieldDesc in rts.GetFieldDescList(types[i]))
{
bool isStatic = rts.IsFieldDescStatic(fieldDesc) || rts.IsFieldDescThreadStatic(fieldDesc);
if ((isStatic && includeStaticFields) || (!isStatic && includeInstanceFields))
fields.Add(new FieldEntry(fieldDesc, inherited));
}
}
return fields;
}
private (string Name, uint Token, FieldDefinition Definition, ITypeHandle EnclosingType) GetFieldMetadata(TargetPointer fieldDesc)
{
IRuntimeTypeSystem rts = _target.Contracts.RuntimeTypeSystem;
ITypeHandle enclosingType = rts.GetTypeHandle(rts.GetMTOfEnclosingClass(fieldDesc));
TargetPointer module = rts.GetModule(enclosingType);
Contracts.ModuleHandle moduleHandle = _target.Contracts.Loader.GetModuleHandleFromModulePtr(module);
MetadataReader metadata = _target.Contracts.EcmaMetadata.GetMetadata(moduleHandle) ?? throw new NotImplementedException();
uint token = rts.GetFieldDescMemberDef(fieldDesc);
FieldDefinition definition = metadata.GetFieldDefinition(MetadataTokens.FieldDefinitionHandle((int)EcmaMetadataUtils.GetRowId(token)));
return (metadata.GetString(definition.Name), token, definition, enclosingType);
}
private ClrDataValue CreateFieldValue(
FieldEntry entry,
FieldDefinition fieldDefinition,
ITypeHandle enclosingType,
IXCLRDataValue? legacyValue)
{
IRuntimeTypeSystem rts = _target.Contracts.RuntimeTypeSystem;
TargetPointer fieldDesc = entry.FieldDesc;
ITypeHandle? fieldType = rts.GetFieldDescApproxTypeHandle(fieldDesc);
CorElementType fieldElementType = rts.GetFieldDescType(fieldDesc);
if (fieldType is null && !rts.IsCorElementTypeObjRef(fieldElementType))
throw new ArgumentException();
NativeVarLocation[] locations;
ulong baseAddress;
if (rts.ContainsGenericVariables(enclosingType))
{
locations = [];
baseAddress = 0;
}
else
{
TargetPointer address;
if (rts.IsFieldDescThreadStatic(fieldDesc))
{
if (_threadAddress == TargetPointer.Null)
throw new ArgumentException();
address = rts.GetFieldDescThreadStaticAddress(fieldDesc, _threadAddress, unboxValueTypes: false);
}
else if (rts.IsFieldDescStatic(fieldDesc))
{
address = rts.GetFieldDescStaticAddress(fieldDesc, unboxValueTypes: false);
}
else
{
uint offset = rts.GetFieldDescOffset(fieldDesc, fieldDefinition);
ulong objectOffset = rts.IsValueType(enclosingType) ? 0 : (ulong)_target.PointerSize;
address = new TargetPointer(checked(_baseAddress + objectOffset + offset));
}
baseAddress = address.Value;
locations =
[
new NativeVarLocation
{
AddressOrValue = address.Value,
Size = fieldType is null
? (ulong)_target.PointerSize
: GetTypeSize(fieldType),
IsRegisterValue = false,
},
];
}
uint flags = entry.IsInherited ? (uint)ClrDataValueFlag.IS_INHERITED : 0;
flags = GetTypeFieldValueFlags(fieldType, fieldDesc, flags, isDeref: false, fieldDefinition);
return new ClrDataValue(_target, _threadAddress, flags, fieldType, baseAddress, locations, legacyValue);
}
private uint GetTypeFieldValueFlags(
ITypeHandle? typeHandle,
TargetPointer? fieldDesc,
uint otherFlags,
bool isDeref,
FieldDefinition fieldDefinition = default)
{
IRuntimeTypeSystem rts = _target.Contracts.RuntimeTypeSystem;
otherFlags &= ~(uint)ClrDataValueFlag.ALL_KINDS;
CorElementType elementType = fieldDesc is TargetPointer field
? rts.GetFieldDescType(field)
: rts.GetInternalCorElementType(typeHandle!);
if (!isDeref && rts.IsCorElementTypeObjRef(elementType))
otherFlags |= (uint)ClrDataValueFlag.IS_REFERENCE;
else if (typeHandle is not null && rts.IsEnum(typeHandle))
otherFlags |= (uint)ClrDataValueFlag.IS_ENUM;
else if (elementType == CorElementType.String)
otherFlags |= (uint)ClrDataValueFlag.IS_STRING;
else if (elementType == CorElementType.Ptr)
otherFlags |= (uint)ClrDataValueFlag.IS_POINTER;
else if (IsPrimitive(elementType))
otherFlags |= (uint)ClrDataValueFlag.IS_PRIMITIVE;
else if (typeHandle is not null && rts.IsArray(typeHandle, out _))
otherFlags |= (uint)ClrDataValueFlag.IS_ARRAY;
else if (typeHandle is not null && rts.IsValueType(typeHandle))
otherFlags |= (uint)ClrDataValueFlag.IS_VALUE_TYPE;
else if (elementType == CorElementType.Class
&& typeHandle is not null
&& typeHandle.Address == rts.GetWellKnownMethodTable(WellKnownMethodTable.String))
{
otherFlags |= (uint)ClrDataValueFlag.IS_STRING;
}
if (fieldDesc is TargetPointer fieldPointer)
{
otherFlags &= ~((uint)ClrDataValueFlag.IS_LITERAL
| (uint)ClrDataValueFlag.FROM_INSTANCE
| (uint)ClrDataValueFlag.FROM_TASK_LOCAL
| (uint)ClrDataValueFlag.FROM_STATIC);
if ((isDeref || (otherFlags & (uint)ClrDataValueFlag.IS_REFERENCE) == 0)
&& (fieldDefinition.Attributes & FieldAttributes.Literal) != 0)
{
otherFlags |= (uint)ClrDataValueFlag.IS_LITERAL;
}
if (rts.IsFieldDescStatic(fieldPointer))
otherFlags |= (uint)ClrDataValueFlag.FROM_STATIC;
else if (rts.IsFieldDescThreadStatic(fieldPointer))
otherFlags |= (uint)ClrDataValueFlag.FROM_TASK_LOCAL;
else
otherFlags |= (uint)ClrDataValueFlag.FROM_INSTANCE;
}
return otherFlags;
}
private ulong GetTypeSize(ITypeHandle typeHandle)
{
IRuntimeTypeSystem rts = _target.Contracts.RuntimeTypeSystem;
CorElementType elementType = rts.GetInternalCorElementType(typeHandle);
return elementType switch
{
CorElementType.ValueType => rts.GetNumInstanceFieldBytes(typeHandle),
_ when elementType != CorElementType.Void && IsPrimitive(elementType)
=> rts.GetNumInstanceFieldBytes(typeHandle),
_ => (ulong)_target.PointerSize,
};
}
private static bool IsPrimitive(CorElementType elementType) =>
elementType is >= CorElementType.Void and <= CorElementType.R8
or CorElementType.I
or CorElementType.U;
private static IXCLRDataTypeInstance? GetLegacyType(IXCLRDataTypeInstance? typeInstance) =>
typeInstance is ClrDataTypeInstance managedType ? managedType.LegacyImpl : typeInstance;
int IXCLRDataValue.EnumField2(
ulong* handle,
DacComNullableByRef<IXCLRDataValue> field,
uint nameBufLen,
uint* nameLen,
char* nameBuf,
DacComNullableByRef<IXCLRDataModule> tokenScope,
uint* token)
=> HResults.E_NOTIMPL;
int IXCLRDataValue.EnumFieldByName2(
ulong* handle,
DacComNullableByRef<IXCLRDataValue> field,
DacComNullableByRef<IXCLRDataModule> tokenScope,
uint* token)
=> HResults.E_NOTIMPL;
int IXCLRDataValue.GetFieldByToken2(
IXCLRDataModule? tokenScope,
uint token,
DacComNullableByRef<IXCLRDataValue> field,
uint bufLen,
uint* nameLen,
char* nameBuf)
=> HResults.E_NOTIMPL;
int IXCLRDataValue.GetNumLocations(uint* numLocs)
{
int hr = HResults.S_OK;
try
{
*numLocs = (uint)_locations.Length;
}
catch (System.Exception ex)
{
hr = ex.HResult;
}
#if DEBUG
if (_legacyImpl is not null)
{
uint numLocsLocal;
int hrLocal = _legacyImpl.GetNumLocations(&numLocsLocal);
Debug.ValidateHResult(hr, hrLocal);
if (hr >= 0)
Debug.Assert(*numLocs == numLocsLocal, $"GetNumLocations cDAC: {*numLocs}, DAC: {numLocsLocal}");
}
#endif
return hr;
}
int IXCLRDataValue.GetLocationByIndex(uint loc, uint* flags, ClrDataAddress* arg)
{
int hr = HResults.S_OK;
try
{
*flags = 0;
*arg = 0;
if (loc >= (uint)_locations.Length)
throw new ArgumentException();
NativeVarLocation location = _locations[loc];
*flags = location.IsRegisterValue ? ClrDataVLocFlag.CLRDATA_VLOC_REGISTER : ClrDataVLocFlag.CLRDATA_VLOC_MEMORY;
*arg = location.IsRegisterValue ? 0 : location.AddressOrValue;
}
catch (System.Exception ex)
{
hr = ex.HResult;
}
#if DEBUG
if (_legacyImpl is not null)
{
uint flagsLocal;
ClrDataAddress argLocal;
int hrLocal = _legacyImpl.GetLocationByIndex(loc, &flagsLocal, &argLocal);
Debug.ValidateHResult(hr, hrLocal);
if (hr >= 0)
{
Debug.Assert(*flags == flagsLocal, $"GetLocationByIndex[{loc}] flags cDAC: {*flags}, DAC: {flagsLocal}");
// Address comparison is best-effort: the native DAC does not handle REGNUM_AMBIENT_SP
// on AMD64 (returns garbage from GetRegOffsInCONTEXT's default case), so addresses may
// legitimately differ for variables stored relative to the ambient stack pointer.
if ((ulong)*arg != (ulong)argLocal)
{
Debug.WriteLine($"GetLocationByIndex[{loc}] addr divergence - cDAC: 0x{(ulong)*arg:X}, DAC: 0x{(ulong)argLocal:X}");
}
}
}
#endif
return hr;
}
}