diff --git a/docs/design/datacontracts/Object.md b/docs/design/datacontracts/Object.md index 9d76a10966b178..bd96e2c25ca943 100644 --- a/docs/design/datacontracts/Object.md +++ b/docs/design/datacontracts/Object.md @@ -29,8 +29,9 @@ TargetPointer GetMethodTableAddress(TargetPointer address); // Get the string corresponding to a managed string object. Error if address does not represent a string. string GetStringValue(TargetPointer address); -// Get the pointer to the data corresponding to a managed array object. Error if address does not represent a array. -TargetPointer GetArrayData(TargetPointer address, out uint count, out TargetPointer boundsStart, out TargetPointer lowerBounds); +// Get the pointer to the data and shape information corresponding to a managed array object. +// Error if address does not represent an array. +TargetPointer GetArrayData(TargetPointer address, out uint count, out TargetPointer boundsStart, out TargetPointer lowerBounds, out uint[] dimensionLengths, out int[] lowerBoundsValues); // Get the length (in chars) and the offset from the object base to the first character // for a managed string object. Error if address does not represent a string. @@ -142,7 +143,7 @@ void GetStringData(TargetPointer address, out uint length, out uint offsetToFirs offsetToFirstChar = /* String::m_FirstChar offset */; } -TargetPointer GetArrayData(TargetPointer address, out uint count, out TargetPointer boundsStart, out TargetPointer lowerBounds) +TargetPointer GetArrayData(TargetPointer address, out uint count, out TargetPointer boundsStart, out TargetPointer lowerBounds, out uint[] dimensionLengths, out int[] lowerBoundsValues) { TargetPointer mt = GetMethodTableAddress(address); if (mt == TargetPointer.Null) @@ -173,6 +174,21 @@ TargetPointer GetArrayData(TargetPointer address, out uint count, out TargetPoin lowerBounds = target.ReadGlobalPointer("ArrayBoundsZero"); } + dimensionLengths = new uint[rank]; + lowerBoundsValues = new int[rank]; + if (corType == CorElementType.Array) + { + for (int i = 0; i < rank; i++) + { + dimensionLengths[i] = target.Read(boundsStart + i * sizeof(int)); + lowerBoundsValues[i] = target.Read(lowerBounds + i * sizeof(int)); + } + } + else + { + dimensionLengths[0] = count; + } + // Sync block is before `this` pointer, so substract the object header size ulong dataOffset = typeSystemContract.GetBaseSize(typeHandle) - target.ReadGlobal("ObjectHeaderSize"); return address + dataOffset; diff --git a/docs/design/datacontracts/RuntimeMutableTypeSystem.md b/docs/design/datacontracts/RuntimeMutableTypeSystem.md index 82564a82c60afe..ae0403eebdce28 100644 --- a/docs/design/datacontracts/RuntimeMutableTypeSystem.md +++ b/docs/design/datacontracts/RuntimeMutableTypeSystem.md @@ -175,7 +175,7 @@ TargetPointer GetEnCInstanceFieldAddress(TargetPointer objectAddress, TargetPoin // Primitive stored in a 1-element array. Return the address of the first element. if (fieldObject == TargetPointer.Null) return TargetPointer.Null; - return target.Contracts.Object.GetArrayData(fieldObject, out _, out _, out _); + return target.Contracts.Object.GetArrayData(fieldObject, out _, out _, out _, out _, out _); } } entryPtr = target.ReadPointer(entryPtr + /* EnCAddedField::Next offset */); diff --git a/docs/design/datacontracts/WindowsErrorReporting.md b/docs/design/datacontracts/WindowsErrorReporting.md index 487f47dd0b64ac..264d49f7ee728c 100644 --- a/docs/design/datacontracts/WindowsErrorReporting.md +++ b/docs/design/datacontracts/WindowsErrorReporting.md @@ -56,7 +56,7 @@ byte[] GetWatsonBuckets(TargetPointer threadPointer) Data.Exception exception = target.ProcessedData.GetOrAdd(thrownObject); if (exception.WatsonBuckets != TargetPointer.Null) { - readFrom = target.Contracts.Object.GetArrayData(exception.WatsonBuckets, out _, out _, out _); + readFrom = target.Contracts.Object.GetArrayData(exception.WatsonBuckets, out _, out _, out _, out _, out _); } else { diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IObject.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IObject.cs index cec9c0cc3bcfa7..abf8b0535e1ac7 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IObject.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IObject.cs @@ -27,7 +27,7 @@ public interface IObject : IContract static string IContract.Name { get; } = nameof(Object); TargetPointer GetMethodTableAddress(TargetPointer address) => throw new NotImplementedException(); string GetStringValue(TargetPointer address) => throw new NotImplementedException(); - TargetPointer GetArrayData(TargetPointer address, out uint count, out TargetPointer boundsStart, out TargetPointer lowerBounds) => throw new NotImplementedException(); + TargetPointer GetArrayData(TargetPointer address, out uint count, out TargetPointer boundsStart, out TargetPointer lowerBounds, out uint[] dimensionLengths, out int[] lowerBoundsValues) => throw new NotImplementedException(); bool GetBuiltInComData(TargetPointer address, out TargetPointer rcw, out TargetPointer ccw, out TargetPointer ccf) => throw new NotImplementedException(); int TryGetHashCode(TargetPointer address) => throw new NotImplementedException(); // Returns the SyncBlock address for the object, or TargetPointer.Null if no sync block is associated with it. diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Object_1.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Object_1.cs index 2fb8be8ed77b04..e5f7cb2126ba82 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Object_1.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Object_1.cs @@ -50,7 +50,8 @@ string IObject.GetStringValue(TargetPointer address) if (str.StringLength == 0) return string.Empty; - Span span = stackalloc byte[(int)str.StringLength * sizeof(char)]; + byte[] bytes = new byte[checked((int)str.StringLength * sizeof(char))]; + Span span = bytes; _target.ReadBuffer(str.FirstChar, span); return new string(MemoryMarshal.Cast(span)); } @@ -68,7 +69,7 @@ public void GetStringData(TargetPointer address, out uint length, out uint offse offsetToFirstChar = (uint)(str.FirstChar.Value - address.Value); } - public TargetPointer GetArrayData(TargetPointer address, out uint count, out TargetPointer boundsStart, out TargetPointer lowerBounds) + public TargetPointer GetArrayData(TargetPointer address, out uint count, out TargetPointer boundsStart, out TargetPointer lowerBounds, out uint[] dimensionLengths, out int[] lowerBoundsValues) { TargetPointer mt = GetMethodTableAddress(address); if (mt == TargetPointer.Null) @@ -101,6 +102,24 @@ public TargetPointer GetArrayData(TargetPointer address, out uint count, out Tar lowerBounds = _target.ReadGlobalPointer(Constants.Globals.ArrayBoundsZero); } + int rankValue = checked((int)rank); + dimensionLengths = new uint[rankValue]; + lowerBoundsValues = new int[rankValue]; + if (corType == CorElementType.Array) + { + for (int i = 0; i < rankValue; i++) + { + ulong offset = (ulong)(i * sizeof(int)); + dimensionLengths[i] = _target.Read(boundsStart + offset); + lowerBoundsValues[i] = _target.Read(lowerBounds + offset); + } + } + else + { + Debug.Assert(rankValue == 1); + dimensionLengths[0] = count; + } + // Sync block is before `this` pointer, so substract the object header size ulong dataOffset = typeSystemContract.GetBaseSize(typeHandle) - Data.ObjectHeader.GetSize(_target); return address + dataOffset; diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeMutableTypeSystem_1.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeMutableTypeSystem_1.cs index 6b55508f77668a..40f66e806c42b8 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeMutableTypeSystem_1.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeMutableTypeSystem_1.cs @@ -160,7 +160,7 @@ TargetPointer IRuntimeMutableTypeSystem.GetEnCInstanceFieldAddress(TargetPointer // Primitive stored in a 1-element array. Get pointer to first element. if (fieldObject == TargetPointer.Null) return TargetPointer.Null; - return objectContract.GetArrayData(fieldObject, out _, out _, out _); + return objectContract.GetArrayData(fieldObject, out _, out _, out _, out _, out _); } } entryPtr = entry.Next; diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Signature/SignatureTypeProvider.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Signature/SignatureTypeProvider.cs index 1f10f401710ca3..46add2db61ee1e 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Signature/SignatureTypeProvider.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Signature/SignatureTypeProvider.cs @@ -55,6 +55,12 @@ public SignatureTypeProvider(Target target, Contracts.ModuleHandle moduleHandle) return null; return _runtimeTypeSystem.GetInstantiation(typeContext)[index]; } + if (typeof(T) == typeof(MethodDescHandle)) + { + MethodDescHandle methodContext = (MethodDescHandle)(object)context!; + ITypeHandle declaringType = _runtimeTypeSystem.GetTypeHandle(_runtimeTypeSystem.GetMethodTable(methodContext)); + return _runtimeTypeSystem.GetInstantiation(declaringType)[index]; + } throw new NotImplementedException(); } public ITypeHandle? GetModifiedType(ITypeHandle? modifier, ITypeHandle? unmodifiedType, bool isRequired) diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/WindowsErrorReporting_1.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/WindowsErrorReporting_1.cs index 4587d1f5af3e4e..7b3cd049c1b5dc 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/WindowsErrorReporting_1.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/WindowsErrorReporting_1.cs @@ -28,7 +28,7 @@ byte[] IWindowsErrorReporting.GetWatsonBuckets(TargetPointer threadPointer) Data.Exception exception = _target.ProcessedData.GetOrAdd(thrownObject); if (exception.WatsonBuckets != TargetPointer.Null) { - readFrom = _target.Contracts.Object.GetArrayData(exception.WatsonBuckets, out _, out _, out _); + readFrom = _target.Contracts.Object.GetArrayData(exception.WatsonBuckets, out _, out _, out _, out _, out _); } else { diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/ClrDataExceptionState.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/ClrDataExceptionState.cs index 52c34c2b99f618..f667d88c36c216 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/ClrDataExceptionState.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/ClrDataExceptionState.cs @@ -142,10 +142,16 @@ int IXCLRDataExceptionState.GetManagedObject(DacComNullableByRef throw new ArgumentException(); } - ulong objectSize = _target.Contracts.Object.GetSize(exceptionObject); + IObject objectContract = _target.Contracts.Object; + ulong objectSize = objectContract.GetSize(exceptionObject); + ITypeHandle typeHandle = _target.Contracts.RuntimeTypeSystem.GetTypeHandle( + objectContract.GetMethodTableAddress(exceptionObject)); value.Interface = new ClrDataValue( _target, + _threadAddress, (uint)ClrDataValueFlag.DEFAULT, + typeHandle, + exceptionObject, [ new NativeVarLocation { diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/ClrDataFrame.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/ClrDataFrame.cs index 2dbd8d9a5d1559..5088c9f3291148 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/ClrDataFrame.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/ClrDataFrame.cs @@ -11,6 +11,7 @@ using System.Runtime.InteropServices.Marshalling; using Microsoft.Diagnostics.DataContractReader.Contracts; using Microsoft.Diagnostics.DataContractReader.Contracts.StackWalkHelpers; +using Microsoft.Diagnostics.DataContractReader.SignatureHelpers; namespace Microsoft.Diagnostics.DataContractReader.Legacy; @@ -18,13 +19,15 @@ namespace Microsoft.Diagnostics.DataContractReader.Legacy; public sealed unsafe partial class ClrDataFrame : IXCLRDataFrame, IXCLRDataFrame2 { private readonly Target _target; + private readonly TargetPointer _threadAddress; private readonly IXCLRDataFrame? _legacyImpl; private readonly IStackDataFrameHandle _dataFrame; - public ClrDataFrame(Target target, IStackDataFrameHandle dataFrame, IXCLRDataFrame? legacyImpl) + public ClrDataFrame(Target target, TargetPointer threadAddress, IStackDataFrameHandle dataFrame, IXCLRDataFrame? legacyImpl) { _target = target; + _threadAddress = threadAddress; _legacyImpl = legacyImpl; _dataFrame = dataFrame; @@ -463,14 +466,24 @@ private ClrDataValue CreateValueFromDebugInfo( // Only VAR/MVAR (generic parameters) require runtime type system resolution. uint valueFlags; int typeSize = -1; + ITypeHandle? typeHandle; if (isArg && sigIndex == 0 && methodHeader.IsInstance) { // 'this' parameter is always a reference valueFlags = (uint)ClrDataValueFlag.IS_REFERENCE; + typeHandle = _target.Contracts.RuntimeTypeSystem.GetTypeHandle( + _target.Contracts.RuntimeTypeSystem.GetMethodTable(mdh)); } else { (valueFlags, typeSize) = ComputeFlagsFromSignature(isArg, sigIndex, methodHeader, mdh, moduleHandle); + typeHandle = GetTypeHandleFromSignature(isArg, sigIndex, methodHeader, mdh, moduleHandle); + if (typeHandle is null) + { + typeHandle = _target.Contracts.RuntimeTypeSystem.GetPrimitiveType(CorElementType.U8); + valueFlags = (uint)ClrDataValueFlag.DEFAULT; + typeSize = -1; + } } // Match native DAC (ValueFromDebugInfo in stack.cpp): for primitives with a @@ -497,7 +510,10 @@ private ClrDataValue CreateValueFromDebugInfo( ]; } - return new ClrDataValue(_target, valueFlags, locations, legacyImpl); + ulong baseAddress = locations.Length == 1 && !locations[0].IsRegisterValue + ? locations[0].AddressOrValue + : 0; + return new ClrDataValue(_target, _threadAddress, valueFlags, typeHandle, baseAddress, locations, legacyImpl); } // ========== Signature-based flag computation ========== @@ -607,6 +623,40 @@ private uint GetLocalVariableCount(MethodDescHandle mdh, Contracts.ModuleHandle } } + private ITypeHandle? GetTypeHandleFromSignature( + bool isArg, uint sigIndex, SignatureHeader methodHeader, + MethodDescHandle mdh, Contracts.ModuleHandle moduleHandle) + { + try + { + MetadataReader mdReader = _target.Contracts.EcmaMetadata.GetMetadata(moduleHandle) ?? throw new NotImplementedException(); + uint token = _target.Contracts.RuntimeTypeSystem.GetMethodToken(mdh); + MethodDefinition methodDef = mdReader.GetMethodDefinition(MetadataTokens.MethodDefinitionHandle((int)EcmaMetadataUtils.GetRowId(token))); + SignatureTypeProvider provider = new(_target, moduleHandle); + SignatureDecoder decoder = new(provider, mdReader, mdh); + + if (isArg) + { + BlobReader sigReader = mdReader.GetBlobReader(methodDef.Signature); + MethodSignature methodSig = decoder.DecodeMethodSignature(ref sigReader); + int paramIndex = methodHeader.IsInstance ? (int)sigIndex - 1 : (int)sigIndex; + return methodSig.ParameterTypes[paramIndex]; + } + + BlobReader? localReader = GetLocalSignatureReader(mdh, moduleHandle, out _); + if (localReader is null) + return null; + + BlobReader localSigReader = localReader.Value; + ImmutableArray localTypes = decoder.DecodeLocalSignature(ref localSigReader); + return localTypes[(int)sigIndex]; + } + catch (System.Exception) + { + return null; + } + } + /// /// Maps a CorElementType to ClrDataValueFlag and primitive type size. /// Used for generic parameter resolution (VAR/MVAR) where we get the diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/ClrDataStackWalk.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/ClrDataStackWalk.cs index 418ee8af19404e..c9c682d988a8ab 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/ClrDataStackWalk.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/ClrDataStackWalk.cs @@ -132,7 +132,7 @@ int IXCLRDataStackWalk.GetFrame(DacComNullableByRef frame) if (!_currentFrameIsValid) throw new ArgumentException(); - frame.Interface = new ClrDataFrame(_target, _dataFrames.Current, legacyFrame); + frame.Interface = new ClrDataFrame(_target, _threadAddr, _dataFrames.Current, legacyFrame); } catch (System.Exception ex) { diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/ClrDataTypeInstance.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/ClrDataTypeInstance.cs index f8aeecde9b47b3..3c3af984c9960d 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/ClrDataTypeInstance.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/ClrDataTypeInstance.cs @@ -15,6 +15,9 @@ public sealed unsafe partial class ClrDataTypeInstance : IXCLRDataTypeInstance private readonly ITypeHandle _typeHandle; private readonly IXCLRDataTypeInstance? _legacyImpl; + internal ITypeHandle TypeHandle => _typeHandle; + internal IXCLRDataTypeInstance? LegacyImpl => _legacyImpl; + public ClrDataTypeInstance(Target target, ITypeHandle typeHandle, IXCLRDataTypeInstance? legacyImpl) { _target = target; diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/ClrDataValue.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/ClrDataValue.cs index 5a1e6ae49d2005..5cf6e8ec6c7b3c 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/ClrDataValue.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/ClrDataValue.cs @@ -2,7 +2,13 @@ // 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; @@ -24,20 +30,29 @@ public readonly struct NativeVarLocation 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) @@ -213,7 +228,47 @@ int IXCLRDataValue.GetBytes(uint bufLen, uint* dataSize, byte* buffer) int IXCLRDataValue.SetBytes(uint bufLen, uint* dataSize, byte* buffer) => HResults.E_NOTIMPL; - int IXCLRDataValue.GetType(DacComNullableByRef typeInstance) => HResults.E_NOTIMPL; + int IXCLRDataValue.GetType(DacComNullableByRef typeInstance) + { + int hr = HResults.S_OK; + IXCLRDataTypeInstance? legacyType = null; + int hrLocal = HResults.S_OK; + if (_legacyImpl is not null) + { + DacComNullableByRef 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; @@ -254,12 +309,7 @@ int IXCLRDataValue.Request(uint reqCode, uint inBufferSize, byte* inBuffer, uint if (_legacyImpl is not null) { uint revisionLocal = 0; - int hrLocal = _legacyImpl.Request( - reqCode, - inBufferSize, - inBuffer, - outBufferSize, - outBuffer is null ? null : (byte*)&revisionLocal); + 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); @@ -270,10 +320,36 @@ int IXCLRDataValue.Request(uint reqCode, uint inBufferSize, byte* inBuffer, uint } int IXCLRDataValue.GetNumFields2(uint flags, IXCLRDataTypeInstance? fromType, uint* numFields) - => HResults.E_NOTIMPL; + { + int hr = HResults.S_OK; + try + { + 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) - => HResults.E_NOTIMPL; + => StartEnumFields(null, (uint)CLRDataByNameFlag.CLRDATA_BYNAME_CASE_SENSITIVE, flags, fromType, handle); int IXCLRDataValue.EnumField( ulong* handle, @@ -282,17 +358,17 @@ int IXCLRDataValue.EnumField( uint* nameLen, char* nameBuf, uint* token) - => HResults.E_NOTIMPL; + => EnumField(handle, field, nameBufLen, nameLen, nameBuf, token, byName: false); - int IXCLRDataValue.EndEnumFields(ulong handle) => HResults.E_NOTIMPL; + int IXCLRDataValue.EndEnumFields(ulong handle) => EndEnumFields(handle, byName: false); int IXCLRDataValue.StartEnumFieldsByName(char* name, uint nameFlags, uint fieldFlags, IXCLRDataTypeInstance? fromType, ulong* handle) - => HResults.E_NOTIMPL; + => StartEnumFields(name is null ? null : new string(name), nameFlags, fieldFlags, fromType, handle); int IXCLRDataValue.EnumFieldByName(ulong* handle, DacComNullableByRef field, uint* token) - => HResults.E_NOTIMPL; + => EnumField(handle, field, 0, null, null, token, byName: true); - int IXCLRDataValue.EndEnumFieldsByName(ulong handle) => HResults.E_NOTIMPL; + int IXCLRDataValue.EndEnumFieldsByName(ulong handle) => EndEnumFields(handle, byName: true); int IXCLRDataValue.GetFieldByToken( uint token, @@ -303,18 +379,660 @@ int IXCLRDataValue.GetFieldByToken( => HResults.E_NOTIMPL; int IXCLRDataValue.GetAssociatedValue(DacComNullableByRef assocValue) - => HResults.E_NOTIMPL; + { + int hr = HResults.S_OK; + IXCLRDataValue? legacyValue = null; + int hrLocal = HResults.S_OK; + if (_legacyImpl is not null) + { + DacComNullableByRef 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 assocType) - => HResults.E_NOTIMPL; + { + int hr = HResults.S_OK; + IXCLRDataTypeInstance? legacyType = null; + int hrLocal = HResults.S_OK; + if (_legacyImpl is not null) + { + DacComNullableByRef legacyTypeOut = new(isNullRef: false); + hrLocal = _legacyImpl.GetAssociatedType(legacyTypeOut); + if (hrLocal >= 0) + legacyType = legacyTypeOut.Interface; + } - int IXCLRDataValue.GetString(uint bufLen, uint* strLen, char* str) => HResults.E_NOTIMPL; + 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, uint* dims, uint numBases, int* bases) - => HResults.E_NOTIMPL; + { + 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; + fixed (uint* dimsLocalPtr = dimsLocal) + fixed (int* basesLocalPtr = basesLocal) + { + hrLocal = _legacyImpl.GetArrayProperties(rank is null ? null : &rankLocal, totalElements is null ? null : &totalElementsLocal, numDim, dims is null ? null : dimsLocalPtr, numBases, bases is null ? null : basesLocalPtr); + } + 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 is null || new ReadOnlySpan(dims, (int)numDim).SequenceEqual(dimsLocal)); + Debug.Assert(bases is null || new ReadOnlySpan(bases, (int)numBases).SequenceEqual(basesLocal)); + } + } +#endif + + return hr; + } int IXCLRDataValue.GetArrayElement(uint numInd, int* indices, DacComNullableByRef value) - => HResults.E_NOTIMPL; + { + int hr = HResults.S_OK; + IXCLRDataValue? legacyValue = null; + int hrLocal = HResults.S_OK; + if (_legacyImpl is not null) + { + DacComNullableByRef 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 + { + public IEnumerator Enumerator { get; } + public nuint LegacyHandle { get; set; } + + public FieldEnumeration(IEnumerable 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 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)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 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 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(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)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 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 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 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, diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/DacDbiImpl.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/DacDbiImpl.cs index 7cffb14141ac9d..1e5678ffe91dbb 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/DacDbiImpl.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/DacDbiImpl.cs @@ -4384,7 +4384,7 @@ public int GetArrayData(ulong objectAddress, Interop.BOOL* pIsValidArray, DacDbi ITypeHandle th = rts.GetTypeHandle(mt); if (rts.IsArray(th, out uint rank)) { - TargetPointer dataStart = objectContract.GetArrayData(objectAddress, out uint numComponents, out TargetPointer boundsStart, out TargetPointer lowerBounds); + TargetPointer dataStart = objectContract.GetArrayData(objectAddress, out uint numComponents, out TargetPointer boundsStart, out TargetPointer lowerBounds, out _, out _); *pIsValidArray = Interop.BOOL.TRUE; uint offsetToArrayBase = (uint)(dataStart - objectAddress); diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/IXCLRData.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/IXCLRData.cs index e817843ff3a791..5b1e7084210b78 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/IXCLRData.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/IXCLRData.cs @@ -1063,6 +1063,14 @@ public enum ClrDataValueFlag : uint IS_REFERENCE = 0x00000010, IS_POINTER = 0x00000020, IS_ENUM = 0x00000040, + ALL_KINDS = 0x0000007f, + IS_INHERITED = 0x00000080, + IS_LITERAL = 0x00000100, + FROM_INSTANCE = 0x00000200, + FROM_TASK_LOCAL = 0x00000400, + FROM_STATIC = 0x00000800, + ALL_LOCATIONS = 0x00000e00, + ALL_FIELDS = 0x00000eff, } public static class ClrDataVLocFlag diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/SOSDacImpl.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/SOSDacImpl.cs index 18255e48911da2..b36928287a4f5d 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/SOSDacImpl.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/SOSDacImpl.cs @@ -3362,7 +3362,7 @@ int ISOSDacInterface.GetObjectData(ClrDataAddress objAddr, DacpObjectData* data) data->ObjectType = DacpObjectType.OBJ_ARRAY; data->dwRank = rank; - TargetPointer arrayData = objectContract.GetArrayData(objPtr, out uint numComponents, out TargetPointer boundsStart, out TargetPointer lowerBounds); + TargetPointer arrayData = objectContract.GetArrayData(objPtr, out uint numComponents, out TargetPointer boundsStart, out TargetPointer lowerBounds, out _, out _); data->ArrayDataPtr = arrayData.ToClrDataAddress(_target); data->dwNumComponents = numComponents; data->ArrayBoundsPtr = boundsStart.ToClrDataAddress(_target); diff --git a/src/native/managed/cdac/tests/DumpTests/Debuggees/LocalVariables/Program.cs b/src/native/managed/cdac/tests/DumpTests/Debuggees/LocalVariables/Program.cs index 90bc8a638a3976..80b67cd6cc844f 100644 --- a/src/native/managed/cdac/tests/DumpTests/Debuggees/LocalVariables/Program.cs +++ b/src/native/managed/cdac/tests/DumpTests/Debuggees/LocalVariables/Program.cs @@ -32,7 +32,14 @@ public struct TinyStruct { public byte Value; } public struct SmallStruct { public int X; public int Y; } public struct LargeStruct { public long A; public long B; public long C; public long D; } - public class SimpleClass { public int Value; public string? Name; } + public class SimpleClass + { + [ThreadStatic] + public static int ThreadValue; + + public int Value; + public string? Name; + } public enum Color { @@ -43,6 +50,7 @@ public enum Color private static void Main() { + SimpleClass.ThreadValue = 1234; PrimitiveVars(42, 3.14, true, 'Z', (byte)0xFF, (short)-1, 123456789L, 2.5f); } diff --git a/src/native/managed/cdac/tests/DumpTests/IXCLRDataAppDomainDumpTests.cs b/src/native/managed/cdac/tests/DumpTests/IXCLRDataAppDomainDumpTests.cs index 5293d210eaaa36..d17cde13092631 100644 --- a/src/native/managed/cdac/tests/DumpTests/IXCLRDataAppDomainDumpTests.cs +++ b/src/native/managed/cdac/tests/DumpTests/IXCLRDataAppDomainDumpTests.cs @@ -207,7 +207,7 @@ private IXCLRDataAppDomain GetAppDomain() Assert.NotNull(managedFrame); - ClrDataFrame frame = new ClrDataFrame(Target, managedFrame, legacyImpl: null); + ClrDataFrame frame = new ClrDataFrame(Target, crashingThread.ThreadAddress, managedFrame, legacyImpl: null); IXCLRDataFrame xclrFrame = frame; DacComNullableByRef appDomainOut = new(isNullRef: false); diff --git a/src/native/managed/cdac/tests/DumpTests/IXCLRDataFrameDumpTests.cs b/src/native/managed/cdac/tests/DumpTests/IXCLRDataFrameDumpTests.cs index 96fe2bad676ee1..cc4455b79f382c 100644 --- a/src/native/managed/cdac/tests/DumpTests/IXCLRDataFrameDumpTests.cs +++ b/src/native/managed/cdac/tests/DumpTests/IXCLRDataFrameDumpTests.cs @@ -44,7 +44,7 @@ public void GetContext_ContextSizeMatchesRawContext(TestConfiguration config) { InitializeDumpTest(config); IStackDataFrameHandle dataFrame = GetFirstManagedFrame(); - IXCLRDataFrame frame = new ClrDataFrame(Target, dataFrame, legacyImpl: null); + IXCLRDataFrame frame = CreateFrame(dataFrame); byte[] contextBuf = new byte[4096]; uint contextSize; @@ -63,7 +63,7 @@ public void GetContext_ExactSizeBuffer_CopiesAllBytes(TestConfiguration config) { InitializeDumpTest(config); IStackDataFrameHandle dataFrame = GetFirstManagedFrame(); - IXCLRDataFrame frame = new ClrDataFrame(Target, dataFrame, legacyImpl: null); + IXCLRDataFrame frame = CreateFrame(dataFrame); byte[] rawContext = Target.Contracts.StackWalk.GetRawContext(dataFrame); byte[] contextBuf = new byte[rawContext.Length]; @@ -82,7 +82,7 @@ public void GetContext_BufferTooSmall_ReturnsInvalidArg_ButSetsContextSize(TestC { InitializeDumpTest(config); IStackDataFrameHandle dataFrame = GetFirstManagedFrame(); - IXCLRDataFrame frame = new ClrDataFrame(Target, dataFrame, legacyImpl: null); + IXCLRDataFrame frame = CreateFrame(dataFrame); byte[] rawContext = Target.Contracts.StackWalk.GetRawContext(dataFrame); Assert.True(rawContext.Length > 0, "Raw context should not be empty for this test."); @@ -102,7 +102,7 @@ public void GetContext_OversizedBuffer_SucceedsWithoutCorruptingExtra(TestConfig { InitializeDumpTest(config); IStackDataFrameHandle dataFrame = GetFirstManagedFrame(); - IXCLRDataFrame frame = new ClrDataFrame(Target, dataFrame, legacyImpl: null); + IXCLRDataFrame frame = CreateFrame(dataFrame); byte[] rawContext = Target.Contracts.StackWalk.GetRawContext(dataFrame); int oversized = rawContext.Length + 128; @@ -196,7 +196,7 @@ public void GetNumArguments_ReturnsCountMatchingMetadata(TestConfiguration confi if (name is not "MethodA") continue; - ClrDataFrame frame = new ClrDataFrame(Target, dataFrame, legacyImpl: null); + ClrDataFrame frame = CreateFrame(dataFrame); IXCLRDataFrame xclrFrame = frame; uint numArgs; int hr = xclrFrame.GetNumArguments(&numArgs); @@ -236,7 +236,7 @@ public void GetNumLocalVariables_ReturnsCountForILMethod(TestConfiguration confi MethodDescHandle mdh = rts.GetMethodDescHandle(md); Assert.True(rts.IsIL(mdh), "MethodB should be an IL method"); - ClrDataFrame frame = new ClrDataFrame(Target, dataFrame, legacyImpl: null); + ClrDataFrame frame = CreateFrame(dataFrame); IXCLRDataFrame xclrFrame = frame; uint numLocals; int hr = xclrFrame.GetNumLocalVariables(&numLocals); @@ -267,7 +267,7 @@ public void GetMethodInstance_ReturnsNonNullForManagedFrame(TestConfiguration co if (md == TargetPointer.Null) continue; - ClrDataFrame frame = new ClrDataFrame(Target, dataFrame, legacyImpl: null); + ClrDataFrame frame = CreateFrame(dataFrame); IXCLRDataFrame xclrFrame = frame; DacComNullableByRef methodOut = new(isNullRef: false); @@ -303,7 +303,7 @@ public void GetArgumentByIndex_ReturnsValueForMethodADepthArg(TestConfiguration if (name is not "MethodA") continue; - ClrDataFrame frame = new ClrDataFrame(Target, dataFrame, legacyImpl: null); + ClrDataFrame frame = CreateFrame(dataFrame); IXCLRDataFrame xclrFrame = frame; // MethodA(int depth) is static with 1 argument @@ -338,7 +338,7 @@ public void GetArgumentByIndex_ReturnsParameterName(TestConfiguration config) if (name is not "MethodA") continue; - ClrDataFrame frame = new ClrDataFrame(Target, dataFrame, legacyImpl: null); + ClrDataFrame frame = CreateFrame(dataFrame); IXCLRDataFrame xclrFrame = frame; // Get the name of the first (and only) argument: "depth" @@ -376,7 +376,7 @@ public void GetArgumentByIndex_InvalidIndex_ReturnsError(TestConfiguration confi if (name is not "MethodA") continue; - ClrDataFrame frame = new ClrDataFrame(Target, dataFrame, legacyImpl: null); + ClrDataFrame frame = CreateFrame(dataFrame); IXCLRDataFrame xclrFrame = frame; // MethodA has 1 argument, so index 1 should be out of range @@ -412,7 +412,7 @@ public void GetLocalVariableByIndex_ReturnsValueForMethodBLocal(TestConfiguratio if (name is not "MethodB") continue; - ClrDataFrame frame = new ClrDataFrame(Target, dataFrame, legacyImpl: null); + ClrDataFrame frame = CreateFrame(dataFrame); IXCLRDataFrame xclrFrame = frame; // MethodB has at least 1 local variable (localObj) @@ -447,7 +447,7 @@ public void GetLocalVariableByIndex_InvalidIndex_ReturnsError(TestConfiguration if (name is not "MethodB") continue; - ClrDataFrame frame = new ClrDataFrame(Target, dataFrame, legacyImpl: null); + ClrDataFrame frame = CreateFrame(dataFrame); IXCLRDataFrame xclrFrame = frame; // Get actual local count, then use an out-of-range index @@ -485,7 +485,7 @@ public void GetLocalVariableByIndex_LocalNameIsEmpty(TestConfiguration config) if (name is not "MethodB") continue; - ClrDataFrame frame = new ClrDataFrame(Target, dataFrame, legacyImpl: null); + ClrDataFrame frame = CreateFrame(dataFrame); IXCLRDataFrame xclrFrame = frame; // Local variable names are not available - name should be empty @@ -524,6 +524,12 @@ private IStackDataFrameHandle GetFirstManagedFrame() private IXCLRDataFrame CreateFrameForFirstManagedFrame() { IStackDataFrameHandle dataFrame = GetFirstManagedFrame(); - return new ClrDataFrame(Target, dataFrame, legacyImpl: null); + return CreateFrame(dataFrame); + } + + private ClrDataFrame CreateFrame(IStackDataFrameHandle dataFrame) + { + ThreadData crashingThread = DumpTestHelpers.FindFailFastThread(Target); + return new ClrDataFrame(Target, crashingThread.ThreadAddress, dataFrame, legacyImpl: null); } } diff --git a/src/native/managed/cdac/tests/DumpTests/IXCLRDataValueDumpTests.cs b/src/native/managed/cdac/tests/DumpTests/IXCLRDataValueDumpTests.cs index 1ca092dd50e103..d2e1c8f48c640f 100644 --- a/src/native/managed/cdac/tests/DumpTests/IXCLRDataValueDumpTests.cs +++ b/src/native/managed/cdac/tests/DumpTests/IXCLRDataValueDumpTests.cs @@ -438,7 +438,7 @@ public void GetBytes_ReturnsExpectedValues(TestConfiguration config) ["arrayArg"] = (v, d) => { TargetPointer ptr = ReadPointerFromValue(v, d); - Target.Contracts.Object.GetArrayData(ptr, out uint count, out _, out _); + Target.Contracts.Object.GetArrayData(ptr, out uint count, out _, out _, out _, out _); Assert.Equal(3u, count); }, }); @@ -522,6 +522,160 @@ public void GetLocationByIndex_ValidAndOutOfRange(TestConfiguration config) Assert.True(hr < 0, $"Expected failure for out-of-range location index {numLocs}"); } + [ConditionalTheory] + [MemberData(nameof(TestConfigurations))] + [SkipOnVersion("net10.0", "InlinedCallFrame.Datum was added after net10.0")] + public void AssociatedValues_ExposeTypesStringsAndFields(TestConfiguration config) + { + InitializeDumpTest(config); + Dictionary arguments = GetArgumentValues("ReferenceTypeVars"); + + DacComNullableByRef referenceType = new(isNullRef: false); + AssertHResult(HResults.S_FALSE, arguments["classArg"].GetType(referenceType)); + + DacComNullableByRef associatedType = new(isNullRef: false); + AssertHResult(HResults.S_OK, arguments["classArg"].GetAssociatedType(associatedType)); + Assert.NotNull(associatedType.Interface); + + IXCLRDataValue classValue = GetAssociatedValue(arguments["classArg"]); + DacComNullableByRef classType = new(isNullRef: false); + AssertHResult(HResults.S_OK, classValue.GetType(classType)); + Assert.NotNull(classType.Interface); + + uint fieldFlags = (uint)(ClrDataValueFlag.ALL_KINDS | ClrDataValueFlag.FROM_INSTANCE); + uint fieldCount; + AssertHResult(HResults.S_OK, classValue.GetNumFields2(fieldFlags, null, &fieldCount)); + Assert.Equal(2u, fieldCount); + + ulong handle; + AssertHResult(HResults.S_OK, classValue.StartEnumFields(fieldFlags, null, &handle)); + Dictionary fields = new(); + try + { + while (true) + { + char* name = stackalloc char[32]; + uint nameLength; + uint token; + DacComNullableByRef field = new(isNullRef: false); + int hr = classValue.EnumField(&handle, field, 32, &nameLength, name, &token); + if (hr == HResults.S_FALSE) + break; + AssertHResult(HResults.S_OK, hr); + fields.Add(new string(name), field.Interface!); + } + } + finally + { + AssertHResult(HResults.S_OK, classValue.EndEnumFields(handle)); + } + + AssertBytes(fields["Value"], BitConverter.GetBytes(99), "SimpleClass.Value"); + IXCLRDataValue nameValue = GetAssociatedValue(fields["Name"]); + char* stringBuffer = stackalloc char[16]; + uint stringLength; + AssertHResult(HResults.S_OK, nameValue.GetString(16, &stringLength, stringBuffer)); + Assert.Equal(6u, stringLength); + Assert.Equal("hello", new string(stringBuffer)); + + char* fieldName = stackalloc char[6]; + "value".AsSpan().CopyTo(new Span(fieldName, 5)); + fieldName[5] = '\0'; + ulong nameHandle; + AssertHResult( + HResults.S_OK, + classValue.StartEnumFieldsByName( + fieldName, + (uint)CLRDataByNameFlag.CLRDATA_BYNAME_CASE_INSENSITIVE, + fieldFlags, + null, + &nameHandle)); + try + { + uint token; + DacComNullableByRef field = new(isNullRef: false); + AssertHResult(HResults.S_OK, classValue.EnumFieldByName(&nameHandle, field, &token)); + AssertBytes(field.Interface!, BitConverter.GetBytes(99), "SimpleClass.Value by name"); + AssertHResult(HResults.S_FALSE, classValue.EnumFieldByName(&nameHandle, field, &token)); + } + finally + { + AssertHResult(HResults.S_OK, classValue.EndEnumFieldsByName(nameHandle)); + } + } + + [ConditionalTheory] + [MemberData(nameof(TestConfigurations))] + [SkipOnVersion("net10.0", "InlinedCallFrame.Datum was added after net10.0")] + public void ThreadStaticFields_UseValueThread(TestConfiguration config) + { + InitializeDumpTest(config); + IXCLRDataValue classValue = GetAssociatedValue(GetArgumentValues("ReferenceTypeVars")["classArg"]); + uint fieldFlags = (uint)(ClrDataValueFlag.ALL_KINDS | ClrDataValueFlag.FROM_STATIC); + + ulong handle; + AssertHResult(HResults.S_OK, classValue.StartEnumFields(fieldFlags, null, &handle)); + try + { + char* name = stackalloc char[32]; + uint nameLength; + uint token; + DacComNullableByRef field = new(isNullRef: false); + AssertHResult(HResults.S_OK, classValue.EnumField(&handle, field, 32, &nameLength, name, &token)); + Assert.Equal("ThreadValue", new string(name)); + AssertBytes(field.Interface!, BitConverter.GetBytes(1234), "SimpleClass.ThreadValue"); + + uint flags = AssertGetFlags(field.Interface!, "SimpleClass.ThreadValue"); + Assert.Equal((uint)ClrDataValueFlag.FROM_TASK_LOCAL, flags & (uint)ClrDataValueFlag.ALL_LOCATIONS); + AssertHResult(HResults.S_FALSE, classValue.EnumField(&handle, field, 32, &nameLength, name, &token)); + } + finally + { + AssertHResult(HResults.S_OK, classValue.EndEnumFields(handle)); + } + } + + [ConditionalTheory] + [MemberData(nameof(TestConfigurations))] + [SkipOnVersion("net10.0", "InlinedCallFrame.Datum was added after net10.0")] + public void Arrays_ExposePropertiesElementsAndElementTypes(TestConfiguration config) + { + InitializeDumpTest(config); + + IXCLRDataValue singleDimension = GetAssociatedValue(GetArgumentValues("SingleDimArrayVars")["arrayArg"]); + uint rank; + uint count; + uint dimension; + int lowerBound; + AssertHResult(HResults.S_OK, singleDimension.GetArrayProperties(&rank, &count, 1, &dimension, 1, &lowerBound)); + Assert.Equal(1u, rank); + Assert.Equal(3u, count); + Assert.Equal(3u, dimension); + Assert.Equal(0, lowerBound); + + int index = 1; + DacComNullableByRef element = new(isNullRef: false); + AssertHResult(HResults.S_OK, singleDimension.GetArrayElement(1, &index, element)); + AssertBytes(element.Interface!, BitConverter.GetBytes(20), "arrayArg[1]"); + + DacComNullableByRef elementType = new(isNullRef: false); + AssertHResult(HResults.S_OK, singleDimension.GetAssociatedType(elementType)); + Assert.NotNull(elementType.Interface); + + IXCLRDataValue multiDimension = GetAssociatedValue(GetArgumentValues("MultiDimArrayVars")["multiDimArg"]); + uint* dimensions = stackalloc uint[2]; + int* lowerBounds = stackalloc int[2]; + AssertHResult(HResults.S_OK, multiDimension.GetArrayProperties(&rank, &count, 2, dimensions, 2, lowerBounds)); + Assert.Equal(2u, rank); + Assert.Equal(6u, count); + Assert.Equal([2u, 3u], new ReadOnlySpan(dimensions, 2).ToArray()); + Assert.Equal([0, 0], new ReadOnlySpan(lowerBounds, 2).ToArray()); + + int* indices = stackalloc int[2] { 1, 2 }; + AssertHResult(HResults.S_OK, multiDimension.GetArrayElement(2, indices, element)); + AssertBytes(element.Interface!, BitConverter.GetBytes(6), "multiDimArg[1,2]"); + } + // ========== Comprehensive: all frames ========== [ConditionalTheory] @@ -544,7 +698,7 @@ public void GetSize_GetFlags_AllFrames_Succeeds(TestConfiguration config) if (md == TargetPointer.Null) continue; - ClrDataFrame frame = new ClrDataFrame(Target, dataFrame, legacyImpl: null); + ClrDataFrame frame = new ClrDataFrame(Target, crashingThread.ThreadAddress, dataFrame, legacyImpl: null); IXCLRDataFrame xclrFrame = frame; uint numArgs; @@ -678,6 +832,13 @@ private TargetPointer ReadPointerFromValue(IXCLRDataValue value, string descript return new TargetPointer(ptr); } + private static IXCLRDataValue GetAssociatedValue(IXCLRDataValue value) + { + DacComNullableByRef associatedValue = new(isNullRef: false); + AssertHResult(HResults.S_OK, value.GetAssociatedValue(associatedValue)); + return Assert.IsAssignableFrom(associatedValue.Interface); + } + private static void AssertFlags(IXCLRDataValue value, ClrDataValueFlag expectedFlags, string description) { uint flags = AssertGetFlags(value, description); @@ -740,7 +901,7 @@ private static void AssertEach(Dictionary values, Di string? name = DumpTestHelpers.GetMethodName(Target, md); if (name == methodName) { - ClrDataFrame frame = new ClrDataFrame(Target, dataFrame, legacyImpl: null); + ClrDataFrame frame = new ClrDataFrame(Target, crashingThread.ThreadAddress, dataFrame, legacyImpl: null); return (frame, dataFrame); } diff --git a/src/native/managed/cdac/tests/DumpTests/StackReferenceDumpTests.cs b/src/native/managed/cdac/tests/DumpTests/StackReferenceDumpTests.cs index 2f9452d782813f..ded836c4fc0761 100644 --- a/src/native/managed/cdac/tests/DumpTests/StackReferenceDumpTests.cs +++ b/src/native/managed/cdac/tests/DumpTests/StackReferenceDumpTests.cs @@ -281,7 +281,7 @@ public void StackRefs_FindsArrayReference(TestConfiguration config) try { - TargetPointer dataStart = objectContract.GetArrayData(r.Object, out uint count, out _, out _); + TargetPointer dataStart = objectContract.GetArrayData(r.Object, out uint count, out _, out _, out _, out _); if (count != 5) continue; diff --git a/src/native/managed/cdac/tests/UnitTests/ClrDataExceptionStateTests.cs b/src/native/managed/cdac/tests/UnitTests/ClrDataExceptionStateTests.cs index 94703ef768f915..f5b1cc5023d703 100644 --- a/src/native/managed/cdac/tests/UnitTests/ClrDataExceptionStateTests.cs +++ b/src/native/managed/cdac/tests/UnitTests/ClrDataExceptionStateTests.cs @@ -15,6 +15,7 @@ public unsafe class ExceptionStateTests { private const ulong ExceptionObjectSize = 0x40; private static readonly TargetPointer s_exceptionObjectAddress = new(0xAA00_0000); + private static readonly TargetPointer s_exceptionMethodTableAddress = new(0xBB00_0000); private static (TestPlaceholderTarget Target, TargetPointer ThrownObjectHandle) CreateTargetWithException( MockTarget.Architecture arch, @@ -43,12 +44,19 @@ private static (TestPlaceholderTarget Target, TargetPointer ThrownObjectHandle) var mockObject = new Mock(); mockObject.Setup(o => o.GetSize(s_exceptionObjectAddress)).Returns(ExceptionObjectSize); + mockObject.Setup(o => o.GetMethodTableAddress(s_exceptionObjectAddress)).Returns(s_exceptionMethodTableAddress); if (messageAddr != TargetPointer.Null && messageString is not null) mockObject.Setup(o => o.GetStringValue(messageAddr)).Returns(messageString); + var mockRuntimeTypeSystem = new Mock(); + mockRuntimeTypeSystem + .Setup(rts => rts.GetTypeHandle(s_exceptionMethodTableAddress)) + .Returns(new TargetTypeHandle(s_exceptionMethodTableAddress)); + var target = targetBuilder .AddMockContract(mockException) .AddMockContract(mockObject) + .AddMockContract(mockRuntimeTypeSystem) .Build(); return (target, thrownObjectHandle); @@ -269,6 +277,10 @@ public void GetManagedObject(MockTarget.Architecture arch) Assert.Equal(HResults.S_OK, value.Interface.GetLocationByIndex(0, &locationFlags, &location)); Assert.Equal(ClrDataVLocFlag.CLRDATA_VLOC_MEMORY, locationFlags); Assert.Equal(s_exceptionObjectAddress.ToClrDataAddress(target), location); + + DacComNullableByRef type = new(isNullRef: false); + Assert.Equal(HResults.S_OK, value.Interface.GetType(type)); + Assert.NotNull(type.Interface); } [Theory] diff --git a/src/native/managed/cdac/tests/UnitTests/ClrDataRequestTests.cs b/src/native/managed/cdac/tests/UnitTests/ClrDataRequestTests.cs index b6be7dc6200a1c..9b2138db990b61 100644 --- a/src/native/managed/cdac/tests/UnitTests/ClrDataRequestTests.cs +++ b/src/native/managed/cdac/tests/UnitTests/ClrDataRequestTests.cs @@ -85,7 +85,7 @@ private static object CreateInstance(RequestType type) RequestType.Task => new ClrDataTask(default, null!, null), RequestType.MethodDefinition => new ClrDataMethodDefinition(null!, default, 0, null), RequestType.MethodInstance => new ClrDataMethodInstance(null!, default, default, null), - RequestType.Value => new ClrDataValue(null!, 0, Array.Empty(), null), + RequestType.Value => new ClrDataValue(null!, TargetPointer.Null, 0, null, TargetPointer.Null, [], null), _ => throw new ArgumentOutOfRangeException(nameof(type)), }; diff --git a/src/native/managed/cdac/tests/UnitTests/MockDescriptors/MockDescriptors.Object.cs b/src/native/managed/cdac/tests/UnitTests/MockDescriptors/MockDescriptors.Object.cs index 28f227e4f336b5..adb762156eface 100644 --- a/src/native/managed/cdac/tests/UnitTests/MockDescriptors/MockDescriptors.Object.cs +++ b/src/native/managed/cdac/tests/UnitTests/MockDescriptors/MockDescriptors.Object.cs @@ -417,6 +417,22 @@ internal ulong AddArrayObject(Array array) MockArrayObjectData arrayObject = ArrayLayout.Create(fragment); arrayObject.MethodTable = methodTable.Address; arrayObject.NumComponents = (uint)array.Length; + + if (!isSingleDimensionZeroLowerBound) + { + int boundsOffset = ArrayLayout.Size; + int lowerBoundsOffset = boundsOffset + (array.Rank * sizeof(int)); + for (int i = 0; i < array.Rank; i++) + { + Builder.TargetTestHelpers.Write( + fragment.Data.AsSpan(boundsOffset + (i * sizeof(int)), sizeof(int)), + array.GetLength(i)); + Builder.TargetTestHelpers.Write( + fragment.Data.AsSpan(lowerBoundsOffset + (i * sizeof(int)), sizeof(int)), + array.GetLowerBound(i)); + } + } + return fragment.Address; } diff --git a/src/native/managed/cdac/tests/UnitTests/ObjectTests.cs b/src/native/managed/cdac/tests/UnitTests/ObjectTests.cs index 87d018161e084d..054bc9f8ece328 100644 --- a/src/native/managed/cdac/tests/UnitTests/ObjectTests.cs +++ b/src/native/managed/cdac/tests/UnitTests/ObjectTests.cs @@ -202,25 +202,31 @@ public void ArrayData(MockTarget.Architecture arch) }); { - TargetPointer data = contract.GetArrayData(SingleDimensionArrayAddress, out uint count, out TargetPointer boundsStart, out TargetPointer lowerBounds); + TargetPointer data = contract.GetArrayData(SingleDimensionArrayAddress, out uint count, out TargetPointer boundsStart, out TargetPointer lowerBounds, out uint[] dimensionLengths, out int[] lowerBoundsValues); Assert.Equal(SingleDimensionArrayAddress + targetTestHelpers.ArrayBaseBaseSize - targetTestHelpers.ObjHeaderSize, data.Value); Assert.Equal((uint)singleDimension.Length, count); Assert.Equal(SingleDimensionArrayAddress + (ulong)numComponentsOffset, boundsStart.Value); Assert.Equal(MockDescriptors.MockObjectBuilder.TestArrayBoundsZeroGlobalAddress, lowerBounds.Value); + Assert.Equal([10u], dimensionLengths); + Assert.Equal([0], lowerBoundsValues); } { - TargetPointer data = contract.GetArrayData(MultiDimensionArrayAddress, out uint count, out TargetPointer boundsStart, out TargetPointer lowerBounds); + TargetPointer data = contract.GetArrayData(MultiDimensionArrayAddress, out uint count, out TargetPointer boundsStart, out TargetPointer lowerBounds, out uint[] dimensionLengths, out int[] lowerBoundsValues); Assert.Equal(MultiDimensionArrayAddress + targetTestHelpers.ArrayBaseSize + (ulong)(multiDimension.Rank * sizeof(int) * 2), data.Value); Assert.Equal((uint)multiDimension.Length, count); Assert.Equal(MultiDimensionArrayAddress + targetTestHelpers.ArrayBaseSize, boundsStart.Value); Assert.Equal(boundsStart.Value + (ulong)(multiDimension.Rank * sizeof(int)), lowerBounds.Value); + Assert.Equal([1u, 2u, 3u, 4u], dimensionLengths); + Assert.Equal([0, 0, 0, 0], lowerBoundsValues); } { - TargetPointer data = contract.GetArrayData(NonZeroLowerBoundArrayAddress, out uint count, out TargetPointer boundsStart, out TargetPointer lowerBounds); + TargetPointer data = contract.GetArrayData(NonZeroLowerBoundArrayAddress, out uint count, out TargetPointer boundsStart, out TargetPointer lowerBounds, out uint[] dimensionLengths, out int[] lowerBoundsValues); Assert.Equal(NonZeroLowerBoundArrayAddress + targetTestHelpers.ArrayBaseSize + (ulong)(nonZeroLowerBound.Rank * sizeof(int) * 2), data.Value); Assert.Equal((uint)nonZeroLowerBound.Length, count); Assert.Equal(NonZeroLowerBoundArrayAddress + targetTestHelpers.ArrayBaseSize, boundsStart.Value); Assert.Equal(boundsStart.Value + (ulong)(nonZeroLowerBound.Rank * sizeof(int)), lowerBounds.Value); + Assert.Equal([10u], dimensionLengths); + Assert.Equal([5], lowerBoundsValues); } }