|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Reflection.Metadata;
using System.Reflection.Metadata.Ecma335;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.Marshalling;
using Microsoft.Diagnostics.DataContractReader.Contracts;
using Microsoft.Diagnostics.DataContractReader.Contracts.StackWalkHelpers;
namespace Microsoft.Diagnostics.DataContractReader.Legacy;
[GeneratedComClass]
public sealed unsafe partial class ClrDataFrame : IXCLRDataFrame, IXCLRDataFrame2
{
private readonly Target _target;
private readonly IXCLRDataFrame? _legacyImpl;
private readonly IXCLRDataFrame2? _legacyImpl2;
private readonly IStackDataFrameHandle _dataFrame;
public ClrDataFrame(Target target, IStackDataFrameHandle dataFrame, IXCLRDataFrame? legacyImpl)
{
_target = target;
_legacyImpl = legacyImpl;
_legacyImpl2 = legacyImpl as IXCLRDataFrame2;
_dataFrame = dataFrame;
}
// IXCLRDataFrame implementation
int IXCLRDataFrame.GetFrameType(uint* simpleType, uint* detailedType)
=> LegacyFallbackHelper.CanFallback() && _legacyImpl is not null ? _legacyImpl.GetFrameType(simpleType, detailedType) : HResults.E_NOTIMPL;
int IXCLRDataFrame.GetContext(
uint contextFlags,
uint contextBufSize,
uint* contextSize,
[Out, MarshalUsing(CountElementName = nameof(contextBufSize))] byte[] contextBuf)
{
int hr = HResults.S_OK;
try
{
IStackWalk stackWalk = _target.Contracts.StackWalk;
byte[] context = stackWalk.GetRawContext(_dataFrame);
// TODO(https://github.com/dotnet/runtime/issues/125791):
// Use contextFlags to compute the required size via ContextSizeForFlags
// (see native ClrDataFrame::GetContext in stack.cpp). Currently we always
// return the full platform context regardless of the requested flags.
if (contextSize is not null)
*contextSize = (uint)context.Length;
// Match native DAC behavior: fail when the buffer is too small,
// and on success copy the full context.
if (contextBufSize < (uint)context.Length)
throw new ArgumentException();
if (contextBufSize > 0 && context.Length > 0)
Array.Copy(context, 0, contextBuf, 0, context.Length);
}
catch (System.Exception ex)
{
hr = ex.HResult;
}
#if DEBUG
if (_legacyImpl is not null)
{
byte[] localContextBuf = new byte[contextBufSize];
int hrLocal = _legacyImpl.GetContext(contextFlags, contextBufSize, null, localContextBuf);
Debug.ValidateHResult(hr, hrLocal);
if (hr == HResults.S_OK)
{
IPlatformAgnosticContext contextStruct = IPlatformAgnosticContext.GetContextForPlatform(_target);
IPlatformAgnosticContext localContextStruct = IPlatformAgnosticContext.GetContextForPlatform(_target);
contextStruct.FillFromBuffer(contextBuf);
localContextStruct.FillFromBuffer(localContextBuf);
Debug.Assert(contextStruct.Equals(localContextStruct));
}
}
#endif
return hr;
}
int IXCLRDataFrame.GetAppDomain(DacComNullableByRef<IXCLRDataAppDomain> appDomain)
{
int hr = HResults.S_OK;
int hrLegacy = HResults.S_OK;
IXCLRDataAppDomain? legacyAppDomain = null;
if (_legacyImpl is not null)
{
DacComNullableByRef<IXCLRDataAppDomain> legacyAppDomainOut = new(isNullRef: false);
hrLegacy = _legacyImpl.GetAppDomain(legacyAppDomainOut);
if (hrLegacy >= 0)
{
legacyAppDomain = legacyAppDomainOut.Interface;
}
}
try
{
TargetPointer appDomainPointer = _target.ReadGlobalPointer(Constants.Globals.AppDomain);
TargetPointer appDomainAddr = _target.ReadPointer(appDomainPointer);
if (appDomainAddr != TargetPointer.Null)
{
appDomain.Interface = new ClrDataAppDomain(_target, appDomainAddr, legacyAppDomain);
}
else
{
hr = HResults.S_FALSE;
}
}
catch (System.Exception ex)
{
hr = ex.HResult;
}
#if DEBUG
if (_legacyImpl is not null)
{
Debug.ValidateHResult(hr, hrLegacy);
}
#endif
return hr;
}
int IXCLRDataFrame.GetNumArguments(uint* numArgs)
{
int hr = HResults.S_OK;
try
{
*numArgs = 0;
GetMethodInfo(out _, out MetadataReader mdReader, out MethodDefinition methodDef, out _, out _);
GetMethodSignatureInfo(mdReader, methodDef, out _, out uint numArgsResult);
*numArgs = numArgsResult;
if (*numArgs == 0)
hr = HResults.S_FALSE;
}
catch (System.Exception ex)
{
hr = ex.HResult;
}
#if DEBUG
if (_legacyImpl is not null)
{
uint numArgsLocal;
int hrLocal = _legacyImpl.GetNumArguments(&numArgsLocal);
Debug.ValidateHResult(hr, hrLocal);
if (hr == HResults.S_OK)
Debug.Assert(*numArgs == numArgsLocal, $"cDAC: {*numArgs}, DAC: {numArgsLocal}");
}
#endif
return hr;
}
int IXCLRDataFrame.GetArgumentByIndex(
uint index,
DacComNullableByRef<IXCLRDataValue> arg,
uint bufLen,
uint* nameLen,
char* name)
{
int hr = HResults.S_OK;
int hrLegacy = HResults.S_OK;
IXCLRDataValue? legacyValue = null;
if (_legacyImpl is not null)
{
DacComNullableByRef<IXCLRDataValue> legacyArgOut = new(isNullRef: false);
hrLegacy = _legacyImpl.GetArgumentByIndex(index, legacyArgOut, bufLen, null, null);
if (hrLegacy >= 0)
{
legacyValue = legacyArgOut.Interface;
}
}
try
{
if (nameLen is not null)
*nameLen = 0;
GetMethodInfo(out MethodDescHandle mdh, out MetadataReader mdReader, out MethodDefinition methodDef, out Contracts.ModuleHandle moduleHandle, out _);
GetMethodSignatureInfo(mdReader, methodDef, out SignatureHeader header, out uint numArgs);
if (index >= numArgs)
throw Marshal.GetExceptionForHR(HResults.E_INVALIDARG)!;
// Resolve parameter name
if ((bufLen > 0 && name is not null) || nameLen is not null)
{
IRuntimeTypeSystem rts = _target.Contracts.RuntimeTypeSystem;
if (index == 0 && header.IsInstance)
{
OutputBufferHelpers.CopyStringToBuffer(name, bufLen, nameLen, "this");
}
else if (!rts.IsNoMetadataMethod(mdh, out _))
{
// Param indexing is 1-based in metadata. 'this' isn't in the
// signature, so for instance methods adjust the index down.
int mdIndex = (int)(header.IsInstance ? index : index + 1);
string? paramName = GetParameterName(mdReader, methodDef, mdIndex);
OutputBufferHelpers.CopyStringToBuffer(name, bufLen, nameLen, paramName ?? string.Empty);
}
else
{
OutputBufferHelpers.CopyStringToBuffer(name, bufLen, nameLen, string.Empty);
}
}
if (!arg.IsNullRef)
{
arg.Interface = CreateValueFromDebugInfo(
header, isArg: true, sigIndex: index, varInfoSlot: index,
legacyValue, mdh, moduleHandle);
}
}
catch (System.Exception ex)
{
hr = ex.HResult;
}
#if DEBUG
if (_legacyImpl is not null)
{
// See AllowCdacSuccess in DebugExtensions.cs — the native DAC's MetaSig
// constructor can fail on certain frames (e.g., EH dispatch) where the cDAC
// succeeds via contract-based metadata access.
Debug.ValidateHResult(hr, hrLegacy, HResultValidationMode.AllowCdacSuccess);
}
#endif
return hr;
}
int IXCLRDataFrame.GetNumLocalVariables(uint* numLocals)
{
int hr = HResults.S_OK;
try
{
*numLocals = 0;
GetMethodInfo(out MethodDescHandle mdh, out _, out _, out Contracts.ModuleHandle moduleHandle, out _);
*numLocals = GetLocalVariableCount(mdh, moduleHandle);
}
catch (System.Exception ex)
{
hr = ex.HResult;
}
#if DEBUG
if (_legacyImpl is not null)
{
uint numLocalsLocal;
int hrLocal = _legacyImpl.GetNumLocalVariables(&numLocalsLocal);
Debug.ValidateHResult(hr, hrLocal);
if (hr == HResults.S_OK)
Debug.Assert(*numLocals == numLocalsLocal, $"cDAC: {*numLocals}, DAC: {numLocalsLocal}");
}
#endif
return hr;
}
int IXCLRDataFrame.GetLocalVariableByIndex(
uint index,
DacComNullableByRef<IXCLRDataValue> localVariable,
uint bufLen,
uint* nameLen,
char* name)
{
int hr = HResults.S_OK;
int hrLegacy = HResults.S_OK;
IXCLRDataValue? legacyValue = null;
if (_legacyImpl is not null)
{
DacComNullableByRef<IXCLRDataValue> legacyLocalOut = new(isNullRef: false);
hrLegacy = _legacyImpl.GetLocalVariableByIndex(index, legacyLocalOut, bufLen, null, null);
if (hrLegacy >= 0)
{
legacyValue = legacyLocalOut.Interface;
}
}
try
{
if (nameLen is not null)
*nameLen = 0;
GetMethodInfo(out MethodDescHandle mdh, out MetadataReader mdReader, out MethodDefinition methodDef, out Contracts.ModuleHandle moduleHandle, out _);
GetMethodSignatureInfo(mdReader, methodDef, out SignatureHeader argHeader, out uint numArgs);
uint numLocals = GetLocalVariableCount(mdh, moduleHandle);
if (index >= numLocals)
throw Marshal.GetExceptionForHR(HResults.E_INVALIDARG)!;
// Local variable names are not available
OutputBufferHelpers.CopyStringToBuffer(name, bufLen, nameLen, string.Empty);
if (!localVariable.IsNullRef)
{
// The locals are indexed immediately following the arguments in the NativeVarInfos.
// varInfoSlot = index + numArgs
localVariable.Interface = CreateValueFromDebugInfo(
argHeader, isArg: false, sigIndex: index, varInfoSlot: index + numArgs,
legacyValue, mdh, moduleHandle);
}
}
catch (System.Exception ex)
{
hr = ex.HResult;
}
#if DEBUG
if (_legacyImpl is not null)
{
// See comment in GetArgumentByIndex.
Debug.ValidateHResult(hr, hrLegacy, HResultValidationMode.AllowCdacSuccess);
}
#endif
return hr;
}
int IXCLRDataFrame.GetCodeName(
uint flags,
uint bufLen,
uint* nameLen,
char* nameBuf)
=> LegacyFallbackHelper.CanFallback() && _legacyImpl is not null ? _legacyImpl.GetCodeName(flags, bufLen, nameLen, nameBuf) : HResults.E_NOTIMPL;
int IXCLRDataFrame.GetMethodInstance(DacComNullableByRef<IXCLRDataMethodInstance> method)
{
int hr = HResults.S_OK;
int hrLocal = HResults.S_OK;
IXCLRDataMethodInstance? legacyMethod = null;
if (_legacyImpl is not null)
{
DacComNullableByRef<IXCLRDataMethodInstance> legacyMethodOut = new(isNullRef: false);
hrLocal = _legacyImpl.GetMethodInstance(legacyMethodOut);
legacyMethod = legacyMethodOut.Interface;
}
try
{
IStackWalk stackWalk = _target.Contracts.StackWalk;
IRuntimeTypeSystem rts = _target.Contracts.RuntimeTypeSystem;
TargetPointer methodDesc = stackWalk.GetMethodDescPtr(_dataFrame);
if (methodDesc == TargetPointer.Null)
throw new InvalidCastException(); // E_NOINTERFACE
MethodDescHandle mdh = rts.GetMethodDescHandle(methodDesc);
TargetPointer appDomain = _target.ReadPointer(
_target.ReadGlobalPointer(Constants.Globals.AppDomain));
method.Interface = new ClrDataMethodInstance(_target, mdh, appDomain, legacyMethod);
}
catch (System.Exception ex)
{
hr = ex.HResult;
}
#if DEBUG
if (_legacyImpl is not null)
{
Debug.ValidateHResult(hr, hrLocal);
}
#endif
return hr;
}
int IXCLRDataFrame.Request(
uint reqCode,
uint inBufferSize,
byte* inBuffer,
uint outBufferSize,
byte* outBuffer)
=> LegacyFallbackHelper.CanFallback() && _legacyImpl is not null ? _legacyImpl.Request(reqCode, inBufferSize, inBuffer, outBufferSize, outBuffer) : HResults.E_NOTIMPL;
int IXCLRDataFrame.GetNumTypeArguments(uint* numTypeArgs)
=> LegacyFallbackHelper.CanFallback() && _legacyImpl is not null ? _legacyImpl.GetNumTypeArguments(numTypeArgs) : HResults.E_NOTIMPL;
int IXCLRDataFrame.GetTypeArgumentByIndex(uint index, DacComNullableByRef<IXCLRDataTypeInstance> typeArg)
=> LegacyFallbackHelper.CanFallback() && _legacyImpl is not null ? _legacyImpl.GetTypeArgumentByIndex(index, typeArg) : HResults.E_NOTIMPL;
// IXCLRDataFrame2 implementation
int IXCLRDataFrame2.GetExactGenericArgsToken(DacComNullableByRef<IXCLRDataValue> genericToken)
=> LegacyFallbackHelper.CanFallback() && _legacyImpl2 is not null ? _legacyImpl2.GetExactGenericArgsToken(genericToken) : HResults.E_NOTIMPL;
// ========== Metadata resolution helpers ==========
/// <summary>
/// Resolves the frame's MethodDesc into its module-level metadata objects.
/// Throws on failure (no MethodDesc, no metadata, etc.).
/// </summary>
private void GetMethodInfo(out MethodDescHandle mdh, out MetadataReader mdReader, out MethodDefinition methodDef, out Contracts.ModuleHandle moduleHandle, out uint token)
{
IStackWalk stackWalk = _target.Contracts.StackWalk;
TargetPointer methodDescPtr = stackWalk.GetMethodDescPtr(_dataFrame);
if (methodDescPtr == TargetPointer.Null)
throw new InvalidCastException(); // E_NOINTERFACE
IRuntimeTypeSystem rts = _target.Contracts.RuntimeTypeSystem;
mdh = rts.GetMethodDescHandle(methodDescPtr);
TargetPointer mtAddr = rts.GetMethodTable(mdh);
TypeHandle typeHandle = rts.GetTypeHandle(mtAddr);
TargetPointer modulePtr = rts.GetModule(typeHandle);
ILoader loader = _target.Contracts.Loader;
moduleHandle = loader.GetModuleHandleFromModulePtr(modulePtr);
token = rts.GetMethodToken(mdh);
IEcmaMetadata ecmaMetadataContract = _target.Contracts.EcmaMetadata;
MetadataReader? reader = ecmaMetadataContract.GetMetadata(moduleHandle);
if (reader is null)
throw new NotImplementedException();
mdReader = reader;
MethodDefinitionHandle methodDefHandle = MetadataTokens.MethodDefinitionHandle((int)token);
methodDef = mdReader.GetMethodDefinition(methodDefHandle);
}
/// <summary>
/// Parses the method signature to determine argument count and signature header.
/// </summary>
private static void GetMethodSignatureInfo(MetadataReader mdReader, MethodDefinition methodDef, out SignatureHeader header, out uint numArgs)
{
BlobReader blobReader = mdReader.GetBlobReader(methodDef.Signature);
header = blobReader.ReadSignatureHeader();
if (header.Kind != SignatureKind.Method)
throw new BadImageFormatException();
if (header.IsGeneric)
blobReader.ReadCompressedInteger(); // skip generic arity
uint paramCount = (uint)blobReader.ReadCompressedInteger();
numArgs = paramCount + (header.IsInstance ? 1u : 0u);
}
/// <summary>
/// Creates a ClrDataValue by resolving variable locations for the current frame
/// via the DebugInfo contract. Mirrors the native ValueFromDebugInfo from stack.cpp.
/// </summary>
private ClrDataValue CreateValueFromDebugInfo(
SignatureHeader methodHeader,
bool isArg,
uint sigIndex,
uint varInfoSlot,
IXCLRDataValue? legacyImpl,
MethodDescHandle mdh,
Contracts.ModuleHandle moduleHandle)
{
IStackWalk stackWalk = _target.Contracts.StackWalk;
IDebugInfo debugInfo = _target.Contracts.DebugInfo;
TargetPointer ip = stackWalk.GetInstructionPointer(_dataFrame);
TargetCodePointer codePointer = new TargetCodePointer(ip.Value);
byte[] context = stackWalk.GetRawContext(_dataFrame);
IEnumerable<DebugVarInfo> varInfos = debugInfo.GetMethodVarInfo(codePointer, out uint codeOffset);
NativeVarLocation[] locations = FindAndResolveVarLocation(varInfos, codeOffset, varInfoSlot, context, _target);
// Determine value flags and adjust size for primitives.
// Read the raw method/local signature to determine type flags without requiring
// types to be loaded by the runtime (see https://github.com/dotnet/runtime/issues/125792).
// Only VAR/MVAR (generic parameters) require runtime type system resolution.
uint valueFlags;
int typeSize = -1;
if (isArg && sigIndex == 0 && methodHeader.IsInstance)
{
// 'this' parameter is always a reference
valueFlags = (uint)ClrDataValueFlag.IS_REFERENCE;
}
else
{
(valueFlags, typeSize) = ComputeFlagsFromSignature(isArg, sigIndex, methodHeader, mdh, moduleHandle);
}
// Match native DAC (ValueFromDebugInfo in stack.cpp): for primitives with a
// single location, shrink the location size to the actual type size. This is
// necessary because NativeVarLocations always sets size = sizeof(SIZE_T)
// (pointer-sized) for every location, regardless of the actual variable type.
//
// For value types (structs), no size adjustment is made — GetSize will always
// return the JIT storage size (pointer-sized per location), not the logical
// struct size. This matches the native DAC's behavior.
if ((valueFlags & (uint)ClrDataValueFlag.IS_PRIMITIVE) != 0
&& typeSize > 0
&& locations.Length == 1
&& (ulong)typeSize < locations[0].Size)
{
locations =
[
new NativeVarLocation
{
AddressOrValue = locations[0].AddressOrValue,
Size = (ulong)typeSize,
IsRegisterValue = locations[0].IsRegisterValue,
},
];
}
return new ClrDataValue(_target, valueFlags, locations, legacyImpl);
}
// ========== Signature-based flag computation ==========
/// <summary>
/// Returns a BlobReader for the local variable signature of the given method,
/// or null if the method has no locals (tiny header or no local sig token).
/// </summary>
private BlobReader? GetLocalSignatureReader(MethodDescHandle mdh, Contracts.ModuleHandle moduleHandle, out MetadataReader mdReader)
{
IRuntimeTypeSystem rts = _target.Contracts.RuntimeTypeSystem;
uint token = rts.GetMethodToken(mdh);
ILoader loader = _target.Contracts.Loader;
TargetPointer ilHeader = loader.GetILHeader(moduleHandle, token);
if (ilHeader == TargetPointer.Null)
{
mdReader = null!;
return null;
}
const int FatFormatFlag = 0x0003;
const int FormatMask = 0x0007;
ushort sizeAndFlags = _target.Read<ushort>(ilHeader);
if ((sizeAndFlags & FormatMask) != FatFormatFlag)
{
mdReader = null!;
return null;
}
int localToken = _target.Read<int>(ilHeader + 8);
if (localToken == 0)
{
mdReader = null!;
return null;
}
mdReader = _target.Contracts.EcmaMetadata.GetMetadata(moduleHandle)!;
StandaloneSignatureHandle localSigHandle = MetadataTokens.StandaloneSignatureHandle(localToken);
BlobHandle localSigBlob = mdReader.GetStandaloneSignature(localSigHandle).Signature;
return mdReader.GetBlobReader(localSigBlob);
}
/// <summary>
/// Returns the count of local variables by reading the local signature header.
/// Throws E_FAIL if the method has no local signature (matches native DAC behavior
/// for dynamic methods, IL stubs, and methods with no declared locals).
/// </summary>
private uint GetLocalVariableCount(MethodDescHandle mdh, Contracts.ModuleHandle moduleHandle)
{
BlobReader? reader = GetLocalSignatureReader(mdh, moduleHandle, out _);
if (reader is null)
throw Marshal.GetExceptionForHR(HResults.E_FAIL)!;
BlobReader r = reader.Value;
r.ReadSignatureHeader();
return (uint)r.ReadCompressedInteger();
}
/// <summary>
/// Computes value flags by decoding the method or local signature using a custom
/// ISignatureTypeProvider that maps types directly to (Flags, Size) tuples.
/// Avoids requiring types to be loaded by the runtime
/// (see https://github.com/dotnet/runtime/issues/125792).
/// Only VAR/MVAR (generic parameters) require runtime type system resolution.
/// </summary>
private (uint Flags, int Size) ComputeFlagsFromSignature(
bool isArg, uint sigIndex, SignatureHeader methodHeader,
MethodDescHandle mdh, Contracts.ModuleHandle moduleHandle)
{
try
{
GetMethodInfo(out _, out MetadataReader mdReader, out MethodDefinition methodDef, out _, out _);
FlagSignatureTypeProvider provider = new(_target, moduleHandle);
SignatureDecoder<(uint Flags, int Size), MethodDescHandle> decoder = new(provider, mdReader, mdh);
if (isArg)
{
BlobReader sigReader = mdReader.GetBlobReader(methodDef.Signature);
MethodSignature<(uint Flags, int Size)> methodSig = decoder.DecodeMethodSignature(ref sigReader);
int paramIndex = methodHeader.IsInstance ? (int)sigIndex - 1 : (int)sigIndex;
return methodSig.ParameterTypes[paramIndex];
}
else
{
BlobReader? localReader = GetLocalSignatureReader(mdh, moduleHandle, out _);
if (localReader is null)
return ((uint)ClrDataValueFlag.DEFAULT, -1);
BlobReader sigReader = localReader.Value;
ImmutableArray<(uint Flags, int Size)> localFlags = decoder.DecodeLocalSignature(ref sigReader);
return localFlags[(int)sigIndex];
}
}
catch (System.Exception)
{
return ((uint)ClrDataValueFlag.DEFAULT, -1);
}
}
/// <summary>
/// Maps a CorElementType to ClrDataValueFlag and primitive type size.
/// Used for generic parameter resolution (VAR/MVAR) where we get the
/// concrete type's CorElementType from the runtime type system.
/// </summary>
private static (uint Flags, int Size) MapCorElementTypeToFlags(CorElementType elementType)
{
return elementType switch
{
CorElementType.Boolean => ((uint)ClrDataValueFlag.IS_PRIMITIVE, 1),
CorElementType.I1 or CorElementType.U1 => ((uint)ClrDataValueFlag.IS_PRIMITIVE, 1),
CorElementType.Char or CorElementType.I2 or CorElementType.U2 => ((uint)ClrDataValueFlag.IS_PRIMITIVE, 2),
CorElementType.I4 or CorElementType.U4 or CorElementType.R4 => ((uint)ClrDataValueFlag.IS_PRIMITIVE, 4),
CorElementType.I8 or CorElementType.U8 or CorElementType.R8 => ((uint)ClrDataValueFlag.IS_PRIMITIVE, 8),
CorElementType.I or CorElementType.U => ((uint)ClrDataValueFlag.IS_PRIMITIVE, -1),
CorElementType.String or CorElementType.Object or CorElementType.Class
or CorElementType.SzArray or CorElementType.Array => ((uint)ClrDataValueFlag.IS_REFERENCE, -1),
CorElementType.Ptr => ((uint)ClrDataValueFlag.IS_POINTER, -1),
CorElementType.ValueType => ((uint)ClrDataValueFlag.IS_VALUE_TYPE, -1),
_ => ((uint)ClrDataValueFlag.DEFAULT, -1),
};
}
/// <summary>
/// Checks if a type's base type refers to System.Enum.
/// </summary>
private static bool IsEnumBaseType(MetadataReader mdReader, EntityHandle baseType)
{
if (baseType.IsNil)
return false;
string? baseTypeName = null;
string? baseTypeNamespace = null;
if (baseType.Kind == HandleKind.TypeReference)
{
TypeReference baseRef = mdReader.GetTypeReference((TypeReferenceHandle)baseType);
baseTypeName = mdReader.GetString(baseRef.Name);
baseTypeNamespace = mdReader.GetString(baseRef.Namespace);
}
else if (baseType.Kind == HandleKind.TypeDefinition)
{
TypeDefinition baseDef = mdReader.GetTypeDefinition((TypeDefinitionHandle)baseType);
baseTypeName = mdReader.GetString(baseDef.Name);
baseTypeNamespace = mdReader.GetString(baseDef.Namespace);
}
return baseTypeNamespace == "System" && baseTypeName == "Enum";
}
/// <summary>
/// ISignatureTypeProvider that maps signature types directly to ClrDataValue flags
/// and primitive type sizes. Avoids constructing TypeHandles (and the runtime type
/// loading that implies) for everything except generic parameters (VAR/MVAR).
/// </summary>
private sealed class FlagSignatureTypeProvider : ISignatureTypeProvider<(uint Flags, int Size), MethodDescHandle>
{
// ECMA-335 II.23.2.8 rawTypeKind values passed by SignatureDecoder
private const byte RawTypeKind_ValueType = 0x11; // ELEMENT_TYPE_VALUETYPE
private const byte RawTypeKind_Class = 0x12; // ELEMENT_TYPE_CLASS
private readonly Target _target;
private readonly Contracts.ModuleHandle _moduleHandle;
public FlagSignatureTypeProvider(Target target, Contracts.ModuleHandle moduleHandle)
{
_target = target;
_moduleHandle = moduleHandle;
}
// --- ISimpleTypeProvider ---
public (uint Flags, int Size) GetPrimitiveType(PrimitiveTypeCode typeCode) => typeCode switch
{
PrimitiveTypeCode.Boolean => ((uint)ClrDataValueFlag.IS_PRIMITIVE, 1),
PrimitiveTypeCode.SByte or PrimitiveTypeCode.Byte => ((uint)ClrDataValueFlag.IS_PRIMITIVE, 1),
PrimitiveTypeCode.Char or PrimitiveTypeCode.Int16 or PrimitiveTypeCode.UInt16 => ((uint)ClrDataValueFlag.IS_PRIMITIVE, 2),
PrimitiveTypeCode.Int32 or PrimitiveTypeCode.UInt32 or PrimitiveTypeCode.Single => ((uint)ClrDataValueFlag.IS_PRIMITIVE, 4),
PrimitiveTypeCode.Int64 or PrimitiveTypeCode.UInt64 or PrimitiveTypeCode.Double => ((uint)ClrDataValueFlag.IS_PRIMITIVE, 8),
PrimitiveTypeCode.IntPtr or PrimitiveTypeCode.UIntPtr => ((uint)ClrDataValueFlag.IS_PRIMITIVE, -1),
// String and Object are PrimitiveTypeCodes but are GC references
PrimitiveTypeCode.String or PrimitiveTypeCode.Object => ((uint)ClrDataValueFlag.IS_REFERENCE, -1),
// Void, TypedReference — no meaningful flag
_ => ((uint)ClrDataValueFlag.DEFAULT, -1),
};
public (uint Flags, int Size) GetTypeFromDefinition(MetadataReader reader, TypeDefinitionHandle handle, byte rawTypeKind) =>
rawTypeKind switch
{
RawTypeKind_Class => ((uint)ClrDataValueFlag.IS_REFERENCE, -1),
RawTypeKind_ValueType => CheckEnumFromTypeDef(reader, handle),
_ => ((uint)ClrDataValueFlag.DEFAULT, -1),
};
public (uint Flags, int Size) GetTypeFromReference(MetadataReader reader, TypeReferenceHandle handle, byte rawTypeKind) =>
rawTypeKind switch
{
RawTypeKind_Class => ((uint)ClrDataValueFlag.IS_REFERENCE, -1),
RawTypeKind_ValueType => CheckEnumFromTypeRef(reader, handle),
_ => ((uint)ClrDataValueFlag.DEFAULT, -1),
};
// --- IConstructedTypeProvider ---
public (uint Flags, int Size) GetPointerType((uint Flags, int Size) elementType)
=> ((uint)ClrDataValueFlag.IS_POINTER, -1);
// ByRef — native DAC's GetTypeFieldValueFlags returns DEFAULT for ELEMENT_TYPE_BYREF
public (uint Flags, int Size) GetByReferenceType((uint Flags, int Size) elementType)
=> ((uint)ClrDataValueFlag.DEFAULT, -1);
public (uint Flags, int Size) GetSZArrayType((uint Flags, int Size) elementType)
=> ((uint)ClrDataValueFlag.IS_REFERENCE, -1);
public (uint Flags, int Size) GetArrayType((uint Flags, int Size) elementType, ArrayShape shape)
=> ((uint)ClrDataValueFlag.IS_REFERENCE, -1);
// GenericInstantiation — the base type carries the CLASS/VALUETYPE distinction
public (uint Flags, int Size) GetGenericInstantiation((uint Flags, int Size) genericType, ImmutableArray<(uint Flags, int Size)> typeArguments)
=> genericType;
// --- ISignatureTypeProvider ---
// Function pointers are mapped to IntPtr in the native DAC
public (uint Flags, int Size) GetFunctionPointerType(MethodSignature<(uint Flags, int Size)> signature)
=> ((uint)ClrDataValueFlag.IS_PRIMITIVE, -1);
public (uint Flags, int Size) GetModifiedType((uint Flags, int Size) modifier, (uint Flags, int Size) unmodifiedType, bool isRequired)
=> unmodifiedType;
public (uint Flags, int Size) GetPinnedType((uint Flags, int Size) elementType)
=> elementType;
public (uint Flags, int Size) GetTypeFromSpecification(MetadataReader reader, MethodDescHandle genericContext, TypeSpecificationHandle handle, byte rawTypeKind)
=> ((uint)ClrDataValueFlag.DEFAULT, -1);
// --- Generic parameter resolution (needs runtime type system) ---
public (uint Flags, int Size) GetGenericMethodParameter(MethodDescHandle mdh, int index)
{
try
{
IRuntimeTypeSystem rts = _target.Contracts.RuntimeTypeSystem;
ReadOnlySpan<TypeHandle> methodInst = rts.GetGenericMethodInstantiation(mdh);
return ResolveGenericParam(rts, methodInst[index]);
}
catch (System.Exception) { return ((uint)ClrDataValueFlag.DEFAULT, -1); }
}
public (uint Flags, int Size) GetGenericTypeParameter(MethodDescHandle mdh, int index)
{
try
{
IRuntimeTypeSystem rts = _target.Contracts.RuntimeTypeSystem;
TargetPointer mtAddr = rts.GetMethodTable(mdh);
TypeHandle declaringType = rts.GetTypeHandle(mtAddr);
ReadOnlySpan<TypeHandle> typeInst = rts.GetInstantiation(declaringType);
return ResolveGenericParam(rts, typeInst[index]);
}
catch (System.Exception) { return ((uint)ClrDataValueFlag.DEFAULT, -1); }
}
private static (uint Flags, int Size) ResolveGenericParam(IRuntimeTypeSystem rts, TypeHandle resolvedType)
{
CorElementType elementType = rts.GetSignatureCorElementType(resolvedType);
(uint flags, int size) = MapCorElementTypeToFlags(elementType);
if (flags == (uint)ClrDataValueFlag.IS_VALUE_TYPE && rts.IsEnum(resolvedType))
flags = (uint)ClrDataValueFlag.IS_ENUM;
return (flags, size);
}
// --- Enum detection helpers ---
private static (uint Flags, int Size) CheckEnumFromTypeDef(MetadataReader reader, TypeDefinitionHandle handle)
{
TypeDefinition typeDef = reader.GetTypeDefinition(handle);
if (IsEnumBaseType(reader, typeDef.BaseType))
return ((uint)ClrDataValueFlag.IS_ENUM, -1);
return ((uint)ClrDataValueFlag.IS_VALUE_TYPE, -1);
}
private (uint Flags, int Size) CheckEnumFromTypeRef(MetadataReader reader, TypeReferenceHandle handle)
{
// For TypeRefs, try to resolve in the same module's TypeDef table.
TypeReference typeRef = reader.GetTypeReference(handle);
MetadataReader moduleReader = _target.Contracts.EcmaMetadata.GetMetadata(_moduleHandle)!;
foreach (TypeDefinitionHandle tdh in moduleReader.TypeDefinitions)
{
TypeDefinition td = moduleReader.GetTypeDefinition(tdh);
if (moduleReader.StringComparer.Equals(td.Name, reader.GetString(typeRef.Name))
&& moduleReader.StringComparer.Equals(td.Namespace, reader.GetString(typeRef.Namespace)))
{
if (IsEnumBaseType(moduleReader, td.BaseType))
return ((uint)ClrDataValueFlag.IS_ENUM, -1);
break;
}
}
return ((uint)ClrDataValueFlag.IS_VALUE_TYPE, -1);
}
}
private static string? GetParameterName(MetadataReader mdReader, MethodDefinition methodDef, int sequenceNumber)
{
foreach (ParameterHandle paramHandle in methodDef.GetParameters())
{
Parameter param = mdReader.GetParameter(paramHandle);
if (param.SequenceNumber == sequenceNumber)
{
return mdReader.GetString(param.Name);
}
}
return null;
}
#region Variable Location Resolution
/// <summary>
/// Finds the matching DebugVarInfo entry for a given variable number and code offset,
/// then resolves it to physical locations using the provided CPU context bytes.
/// </summary>
private static NativeVarLocation[] FindAndResolveVarLocation(
IEnumerable<DebugVarInfo> varInfos,
uint codeOffset,
uint varNumber,
byte[] context,
Target target)
{
foreach (DebugVarInfo varInfo in varInfos)
{
if (varInfo.StartOffset <= codeOffset &&
varInfo.EndOffset >= codeOffset &&
varInfo.VarNumber == varNumber)
{
IPlatformAgnosticContext platformContext = IPlatformAgnosticContext.GetContextForPlatform(target);
platformContext.FillFromBuffer(context);
return ResolveVarLocation(varInfo, platformContext, target);
}
}
return [];
}
/// <summary>
/// Resolves a DebugVarInfo entry to physical NativeVarLocation(s)
/// using the given CPU context. Mirrors the native NativeVarLocations() from util.cpp.
/// </summary>
private static NativeVarLocation[] ResolveVarLocation(
DebugVarInfo varInfo,
IPlatformAgnosticContext context,
Target target)
{
int pointerSize = target.PointerSize;
return (varInfo.Kind, varInfo.IsByRef) switch
{
(DebugVarLocKind.Register, false) =>
[
new NativeVarLocation { AddressOrValue = ReadRegister(context, target, varInfo.Register), Size = (ulong)pointerSize, IsRegisterValue = true },
],
(DebugVarLocKind.Register, true) => ResolveRegByRef(context, target, varInfo.Register, pointerSize),
(DebugVarLocKind.Stack, false) => ResolveStack(context, target, varInfo.BaseRegister, varInfo.StackOffset, pointerSize, deref: false),
(DebugVarLocKind.Stack, true) => ResolveStack(context, target, varInfo.BaseRegister, varInfo.StackOffset, pointerSize, deref: true),
(DebugVarLocKind.RegisterRegister, _) =>
[
new NativeVarLocation { AddressOrValue = ReadRegister(context, target, varInfo.Register), Size = (ulong)pointerSize, IsRegisterValue = true },
new NativeVarLocation { AddressOrValue = ReadRegister(context, target, varInfo.Register2), Size = (ulong)pointerSize, IsRegisterValue = true },
],
(DebugVarLocKind.RegisterStack, _) =>
[
new NativeVarLocation { AddressOrValue = ReadRegister(context, target, varInfo.Register), Size = (ulong)pointerSize, IsRegisterValue = true },
new NativeVarLocation { AddressOrValue = ComputeStackAddress(context, target, varInfo.BaseRegister2, varInfo.StackOffset2), Size = (ulong)pointerSize, IsRegisterValue = false },
],
(DebugVarLocKind.StackRegister, _) =>
[
new NativeVarLocation { AddressOrValue = ComputeStackAddress(context, target, varInfo.BaseRegister, varInfo.StackOffset), Size = (ulong)pointerSize, IsRegisterValue = false },
new NativeVarLocation { AddressOrValue = ReadRegister(context, target, varInfo.Register), Size = (ulong)pointerSize, IsRegisterValue = true },
],
(DebugVarLocKind.DoubleStack, _) =>
[
new NativeVarLocation { AddressOrValue = ComputeStackAddress(context, target, varInfo.BaseRegister, varInfo.StackOffset), Size = 2 * (ulong)pointerSize, IsRegisterValue = false },
],
_ => [],
};
}
private static NativeVarLocation[] ResolveRegByRef(IPlatformAgnosticContext context, Target target, uint register, int pointerSize)
{
// The register holds a pointer to the variable in target memory.
// Mark as IsRegisterValue=true to match the native DAC, which sets contextReg=true for
// VLT_REG_BYREF and copies the register bytes directly via memcpy in IntGetBytes.
// This means GetBytes returns the raw pointer value (matching the DAC), and
// GetLocationByIndex returns CLRDATA_VLOC_REGISTER (matching the DAC).
ulong regValue = ReadRegister(context, target, register);
return [new NativeVarLocation { AddressOrValue = regValue, Size = (ulong)pointerSize, IsRegisterValue = true }];
}
private static NativeVarLocation[] ResolveStack(IPlatformAgnosticContext context, Target target, uint baseRegister, int offset, int pointerSize, bool deref)
{
ulong addr = ComputeStackAddress(context, target, baseRegister, offset);
if (deref)
addr = DereferenceOrZero(target, addr);
return [new NativeVarLocation { AddressOrValue = addr, Size = (ulong)pointerSize, IsRegisterValue = false }];
}
private static ulong ComputeStackAddress(IPlatformAgnosticContext context, Target target, uint baseRegister, int offset)
{
ulong baseReg = ReadRegister(context, target, baseRegister);
return (ulong)((long)baseReg + offset);
}
/// <summary>
/// Reads a pointer from target memory, returning 0 on failure.
/// Matches native DereferenceByRefVar (util.cpp) which returns 0 when DacReadAll fails.
/// </summary>
private static ulong DereferenceOrZero(Target target, ulong addr)
{
try
{
return target.ReadPointer(addr);
}
catch (System.Exception)
{
return 0;
}
}
private static ulong ReadRegister(IPlatformAgnosticContext context, Target target, uint registerNumber)
{
if (context.TryReadRegister((int)registerNumber, out TargetNUInt value))
return value.Value;
// REGNUM_AMBIENT_SP is beyond the normal register range on every architecture.
// It represents the entry-time SP, not necessarily the current SP.
// Map it to the stack pointer as a best-effort approximation (see util.cpp).
int spRegisterNumber = GetStackPointerRegisterNumber(target);
if (spRegisterNumber >= 0 && context.TryReadRegister(spRegisterNumber, out value))
return value.Value;
return 0;
}
private static int GetStackPointerRegisterNumber(Target target)
{
RuntimeInfoArchitecture arch = target.Contracts.RuntimeInfo.GetTargetArchitecture();
return arch switch
{
RuntimeInfoArchitecture.X64 => 4, // RSP
RuntimeInfoArchitecture.X86 => 4, // ESP
RuntimeInfoArchitecture.Arm64 => 31, // SP
RuntimeInfoArchitecture.Arm => 13, // SP
_ => -1,
};
}
#endregion
}
|