From e55be4d4863c0fb2978024738e2e028b6e1e6691 Mon Sep 17 00:00:00 2001 From: Max Charlamb Date: Fri, 24 Apr 2026 14:16:30 -0400 Subject: [PATCH] [cDAC] Add RuntimeSignatureDecoder for ELEMENT_TYPE_INTERNAL support Custom signature decoder handling runtime-internal type codes (ELEMENT_TYPE_INTERNAL 0x21, ELEMENT_TYPE_CMOD_INTERNAL 0x22) which SRM's SignatureDecoder cannot parse. Replaces SRM in PromoteCallerStack. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../StackWalk/FrameHandling/FrameIterator.cs | 30 +- .../StackWalk/GC/GcSignatureTypeProvider.cs | 43 +- .../StackWalk/GC/RuntimeSignatureDecoder.cs | 422 ++++++++++++++++++ 3 files changed, 475 insertions(+), 20 deletions(-) create mode 100644 src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/GC/RuntimeSignatureDecoder.cs diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/FrameHandling/FrameIterator.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/FrameHandling/FrameIterator.cs index 62954af93c4e4e..0152698f6bc65c 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/FrameHandling/FrameIterator.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/FrameHandling/FrameIterator.cs @@ -580,9 +580,10 @@ private void PromoteCallerStack( return; ReadOnlySpan signature; + MetadataReader? metadataReader; try { - signature = GetMethodSignatureBytes(methodDescPtr); + signature = GetMethodSignatureBytes(methodDescPtr, out metadataReader); } catch (System.Exception) { @@ -595,20 +596,14 @@ private void PromoteCallerStack( MethodSignature methodSig; try { - unsafe - { - fixed (byte* pSig = signature) - { - BlobReader blobReader = new(pSig, signature.Length); - SignatureDecoder decoder = new( - GcSignatureTypeProvider.Instance, metadataReader: null!, genericContext: null); - methodSig = decoder.DecodeMethodSignature(ref blobReader); - } - } + RuntimeSignatureDecoder decoder = new( + GcSignatureTypeProvider.Instance, target, genericContext: null, + new SpanSignatureReader(signature, target.IsLittleEndian), metadataReader); + methodSig = decoder.DecodeMethodSignature(); } catch (System.Exception) { - // If signature decoding fails (e.g., ELEMENT_TYPE_INTERNAL), skip this frame. + // If signature decoding fails for any reason, skip this frame. // The GCRefMap path handles these cases when available. return; } @@ -707,8 +702,9 @@ private void PromoteCallerStackHelper( } } - private ReadOnlySpan GetMethodSignatureBytes(TargetPointer methodDescPtr) + private ReadOnlySpan GetMethodSignatureBytes(TargetPointer methodDescPtr, out MetadataReader? metadataReader) { + metadataReader = null; IRuntimeTypeSystem rts = target.Contracts.RuntimeTypeSystem; MethodDescHandle mdh = rts.GetMethodDescHandle(methodDescPtr); @@ -727,13 +723,13 @@ private ReadOnlySpan GetMethodSignatureBytes(TargetPointer methodDescPtr) ModuleHandle moduleHandle = loader.GetModuleHandleFromModulePtr(modulePtr); IEcmaMetadata ecmaMetadata = target.Contracts.EcmaMetadata; - MetadataReader? mdReader = ecmaMetadata.GetMetadata(moduleHandle); - if (mdReader is null) + metadataReader = ecmaMetadata.GetMetadata(moduleHandle); + if (metadataReader is null) return default; MethodDefinitionHandle methodDefHandle = MetadataTokens.MethodDefinitionHandle((int)(methodToken & 0x00FFFFFF)); - MethodDefinition methodDef = mdReader.GetMethodDefinition(methodDefHandle); - BlobReader blobReader = mdReader.GetBlobReader(methodDef.Signature); + MethodDefinition methodDef = metadataReader.GetMethodDefinition(methodDefHandle); + BlobReader blobReader = metadataReader.GetBlobReader(methodDef.Signature); return blobReader.ReadBytes(blobReader.Length); } diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/GC/GcSignatureTypeProvider.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/GC/GcSignatureTypeProvider.cs index 4edb08421a317a..46658212eb81e4 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/GC/GcSignatureTypeProvider.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/GC/GcSignatureTypeProvider.cs @@ -23,11 +23,12 @@ internal enum GcTypeKind /// /// Classifies signature types for GC scanning purposes. -/// Implements for use -/// with SRM's . +/// Implements which +/// is a superset of SRM's , +/// adding support for ELEMENT_TYPE_INTERNAL. /// internal sealed class GcSignatureTypeProvider - : ISignatureTypeProvider + : IRuntimeSignatureTypeProvider { public static readonly GcSignatureTypeProvider Instance = new(); @@ -60,5 +61,41 @@ public GcTypeKind GetGenericInstantiation(GcTypeKind genericType, ImmutableArray public GcTypeKind GetGenericTypeParameter(object? genericContext, int index) => GcTypeKind.Ref; public GcTypeKind GetFunctionPointerType(MethodSignature signature) => GcTypeKind.None; public GcTypeKind GetModifiedType(GcTypeKind modifier, GcTypeKind unmodifiedType, bool isRequired) => unmodifiedType; + public GcTypeKind GetInternalModifiedType(Target target, TargetPointer typeHandlePointer, GcTypeKind unmodifiedType, bool isRequired) => unmodifiedType; public GcTypeKind GetPinnedType(GcTypeKind elementType) => elementType; + + public GcTypeKind GetInternalType(Target target, TargetPointer typeHandlePointer) + { + if (typeHandlePointer == TargetPointer.Null) + return GcTypeKind.None; + + try + { + IRuntimeTypeSystem rts = target.Contracts.RuntimeTypeSystem; + TypeHandle th = rts.GetTypeHandle(typeHandlePointer); + CorElementType corType = rts.GetSignatureCorElementType(th); + + return corType switch + { + CorElementType.Void or CorElementType.Boolean or CorElementType.Char + or CorElementType.I1 or CorElementType.U1 + or CorElementType.I2 or CorElementType.U2 + or CorElementType.I4 or CorElementType.U4 + or CorElementType.I8 or CorElementType.U8 + or CorElementType.R4 or CorElementType.R8 + or CorElementType.I or CorElementType.U + or CorElementType.FnPtr or CorElementType.Ptr + => GcTypeKind.None, + + CorElementType.Byref => GcTypeKind.Interior, + CorElementType.ValueType => GcTypeKind.Other, + + _ => GcTypeKind.Ref, + }; + } + catch + { + return GcTypeKind.Ref; + } + } } diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/GC/RuntimeSignatureDecoder.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/GC/RuntimeSignatureDecoder.cs new file mode 100644 index 00000000000000..9998176d7e4f04 --- /dev/null +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/GC/RuntimeSignatureDecoder.cs @@ -0,0 +1,422 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Buffers.Binary; +using System.Collections.Immutable; +using System.Reflection.Metadata; +using System.Reflection.Metadata.Ecma335; + +namespace Microsoft.Diagnostics.DataContractReader.Contracts.StackWalkHelpers; + +/// +/// Superset of SRM's +/// that adds support for runtime-internal type codes (ELEMENT_TYPE_INTERNAL). +/// +/// +/// Providers implementing this interface automatically satisfy SRM's +/// and can be used +/// with both SRM's SignatureDecoder and our +/// . +/// +internal interface IRuntimeSignatureTypeProvider + : ISignatureTypeProvider +{ + /// + /// Classify an ELEMENT_TYPE_INTERNAL (0x21) type by resolving the + /// embedded TypeHandle pointer via the target's runtime type system. + /// + TType GetInternalType(Target target, TargetPointer typeHandlePointer); + + /// + /// Classify an ELEMENT_TYPE_CMOD_INTERNAL (0x22) custom modifier by + /// resolving the embedded TypeHandle pointer via the target's runtime type system. + /// + TType GetInternalModifiedType(Target target, TargetPointer typeHandlePointer, TType unmodifiedType, bool isRequired); +} + +/// +/// Abstraction for reading bytes from a signature blob. +/// +/// +/// Allows the decoder to read from different sources (in-memory spans, +/// target process memory) without allocating intermediate byte arrays. +/// +internal interface ISignatureReader +{ + byte ReadByte(); + byte PeekByte(); + int Remaining { get; } + + /// Reads a pointer-sized unsigned value (4 or 8 bytes). + ulong ReadPointerSized(int pointerSize); +} + +/// +/// Reads signature bytes from a . +/// +internal ref struct SpanSignatureReader : ISignatureReader +{ + private readonly ReadOnlySpan _blob; + private readonly bool _isLittleEndian; + private int _offset; + + public SpanSignatureReader(ReadOnlySpan blob, bool isLittleEndian = true) + { + _blob = blob; + _isLittleEndian = isLittleEndian; + _offset = 0; + } + + public int Remaining => _blob.Length - _offset; + + public byte ReadByte() + { + if (_offset >= _blob.Length) + throw new BadImageFormatException("Unexpected end of signature blob"); + return _blob[_offset++]; + } + + public byte PeekByte() + { + if (_offset >= _blob.Length) + throw new BadImageFormatException("Unexpected end of signature blob"); + return _blob[_offset]; + } + + public ulong ReadPointerSized(int pointerSize) + { + if (_offset + pointerSize > _blob.Length) + throw new BadImageFormatException("Unexpected end of signature blob"); + + ReadOnlySpan slice = _blob.Slice(_offset, pointerSize); + ulong val = pointerSize == 8 + ? (_isLittleEndian ? BinaryPrimitives.ReadUInt64LittleEndian(slice) : BinaryPrimitives.ReadUInt64BigEndian(slice)) + : (_isLittleEndian ? BinaryPrimitives.ReadUInt32LittleEndian(slice) : BinaryPrimitives.ReadUInt32BigEndian(slice)); + _offset += pointerSize; + return val; + } +} + +/// +/// Decodes method and local variable signatures, handling both standard ECMA-335 +/// types and runtime-internal types like ELEMENT_TYPE_INTERNAL (0x21). +/// +/// +/// +/// Handles the same ECMA-335 type codes as SRM's +/// , plus runtime-internal +/// types (ELEMENT_TYPE_INTERNAL 0x21 and ELEMENT_TYPE_CMOD_INTERNAL 0x22). +/// +/// +/// Internal custom modifiers (ELEMENT_TYPE_CMOD_INTERNAL) are skipped since +/// they carry runtime TypeHandle pointers that are not meaningful for type classification. +/// Standard custom modifiers (modreq/modopt) are decoded and dispatched +/// to . +/// +/// +internal ref struct RuntimeSignatureDecoder + where TReader : ISignatureReader, allows ref struct +{ + private const byte ELEMENT_TYPE_PTR = 0x0f; + private const byte ELEMENT_TYPE_BYREF = 0x10; + private const byte ELEMENT_TYPE_VALUETYPE = 0x11; + private const byte ELEMENT_TYPE_CLASS = 0x12; + private const byte ELEMENT_TYPE_VAR = 0x13; + private const byte ELEMENT_TYPE_ARRAY = 0x14; + private const byte ELEMENT_TYPE_GENERICINST = 0x15; + private const byte ELEMENT_TYPE_FNPTR = 0x1b; + private const byte ELEMENT_TYPE_SZARRAY = 0x1d; + private const byte ELEMENT_TYPE_MVAR = 0x1e; + private const byte ELEMENT_TYPE_CMOD_REQD = 0x1f; + private const byte ELEMENT_TYPE_CMOD_OPT = 0x20; + private const byte ELEMENT_TYPE_INTERNAL = 0x21; + private const byte ELEMENT_TYPE_CMOD_INTERNAL = 0x22; + private const byte ELEMENT_TYPE_SENTINEL = 0x41; + private const byte ELEMENT_TYPE_PINNED = 0x45; + + private readonly IRuntimeSignatureTypeProvider _provider; + private readonly MetadataReader? _metadataReader; + private readonly Target _target; + private readonly TGenericContext _genericContext; + private TReader _reader; + + public RuntimeSignatureDecoder( + IRuntimeSignatureTypeProvider provider, + Target target, + TGenericContext genericContext, + TReader reader, + MetadataReader? metadataReader = null) + { + _provider = provider; + _metadataReader = metadataReader; + _target = target; + _genericContext = genericContext; + _reader = reader; + } + + /// Decodes a method signature (MethodDefSig/MethodRefSig). + public MethodSignature DecodeMethodSignature() + { + byte rawHeader = _reader.ReadByte(); + SignatureHeader header = new(rawHeader); + + if (header.Kind is not SignatureKind.Method and not SignatureKind.Property) + throw new BadImageFormatException($"Unexpected signature header kind: {header.Kind}"); + + int genericParameterCount = 0; + if (header.IsGeneric) + genericParameterCount = ReadCompressedUInt(); + + int parameterCount = ReadCompressedUInt(); + if (parameterCount > _reader.Remaining) + throw new BadImageFormatException($"Parameter count {parameterCount} exceeds remaining signature bytes"); + TType returnType = DecodeType(); + + var parameterTypes = ImmutableArray.CreateBuilder(parameterCount); + int requiredParameterCount = parameterCount; + bool sentinelSeen = false; + + for (int i = 0; i < parameterCount; i++) + { + if (_reader.Remaining > 0 && _reader.PeekByte() == ELEMENT_TYPE_SENTINEL) + { + if (sentinelSeen) + throw new BadImageFormatException("Multiple sentinels in method signature"); + sentinelSeen = true; + requiredParameterCount = i; + _reader.ReadByte(); + } + parameterTypes.Add(DecodeType()); + } + + return new MethodSignature( + header, returnType, requiredParameterCount, genericParameterCount, + parameterTypes.MoveToImmutable()); + } + + /// Decodes a local variable signature (LocalVarSig). + public ImmutableArray DecodeLocalSignature() + { + byte header = _reader.ReadByte(); + if (header != 0x07) // IMAGE_CEE_CS_CALLCONV_LOCAL_SIG + throw new BadImageFormatException($"Expected LocalVarSig header (0x07), got 0x{header:X2}"); + + int count = ReadCompressedUInt(); + if (count == 0) + throw new BadImageFormatException("Local variable signature must have at least one entry"); + if (count > _reader.Remaining) + throw new BadImageFormatException($"Local count {count} exceeds remaining signature bytes"); + var locals = ImmutableArray.CreateBuilder(count); + for (int i = 0; i < count; i++) + locals.Add(DecodeType()); + return locals.MoveToImmutable(); + } + + /// Decodes a single type embedded in a signature. + public TType DecodeType() + { + // Handle custom modifiers (standard and internal) + while (_reader.Remaining > 0) + { + byte peek = _reader.PeekByte(); + if (peek is ELEMENT_TYPE_CMOD_REQD or ELEMENT_TYPE_CMOD_OPT) + { + bool isRequired = peek == ELEMENT_TYPE_CMOD_REQD; + _reader.ReadByte(); + TType modifier = DecodeTypeDefOrRefOrSpec(0); + TType unmodifiedType = DecodeType(); + return _provider.GetModifiedType(modifier, unmodifiedType, isRequired); + } + else if (peek == ELEMENT_TYPE_CMOD_INTERNAL) + { + _reader.ReadByte(); + bool isRequired = _reader.ReadByte() != 0; + ulong val = _reader.ReadPointerSized(_target.PointerSize); + TType unmodifiedType = DecodeType(); + return _provider.GetInternalModifiedType( + _target, new TargetPointer(val), unmodifiedType, isRequired); + } + else + { + break; + } + } + + byte typeCode = _reader.ReadByte(); + + switch (typeCode) + { + case (byte)SignatureTypeCode.Boolean: + case (byte)SignatureTypeCode.Char: + case (byte)SignatureTypeCode.SByte: + case (byte)SignatureTypeCode.Byte: + case (byte)SignatureTypeCode.Int16: + case (byte)SignatureTypeCode.UInt16: + case (byte)SignatureTypeCode.Int32: + case (byte)SignatureTypeCode.UInt32: + case (byte)SignatureTypeCode.Int64: + case (byte)SignatureTypeCode.UInt64: + case (byte)SignatureTypeCode.Single: + case (byte)SignatureTypeCode.Double: + case (byte)SignatureTypeCode.IntPtr: + case (byte)SignatureTypeCode.UIntPtr: + case (byte)SignatureTypeCode.Object: + case (byte)SignatureTypeCode.String: + case (byte)SignatureTypeCode.Void: + case (byte)SignatureTypeCode.TypedReference: + return _provider.GetPrimitiveType((PrimitiveTypeCode)typeCode); + + case ELEMENT_TYPE_CLASS: + case ELEMENT_TYPE_VALUETYPE: + return DecodeTypeDefOrRefOrSpec(typeCode); + + case ELEMENT_TYPE_PTR: + return _provider.GetPointerType(DecodeType()); + + case ELEMENT_TYPE_BYREF: + return _provider.GetByReferenceType(DecodeType()); + + case ELEMENT_TYPE_SZARRAY: + return _provider.GetSZArrayType(DecodeType()); + + case ELEMENT_TYPE_ARRAY: + { + TType elementType = DecodeType(); + ArrayShape shape = DecodeArrayShape(); + return _provider.GetArrayType(elementType, shape); + } + + case ELEMENT_TYPE_GENERICINST: + { + TType baseType = DecodeType(); + int count = ReadCompressedUInt(); + if (count == 0) + throw new BadImageFormatException("Generic instantiation must have at least one type argument"); + if (count > _reader.Remaining) + throw new BadImageFormatException($"Generic argument count {count} exceeds remaining signature bytes"); + var args = ImmutableArray.CreateBuilder(count); + for (int i = 0; i < count; i++) + args.Add(DecodeType()); + return _provider.GetGenericInstantiation(baseType, args.MoveToImmutable()); + } + + case ELEMENT_TYPE_VAR: + return _provider.GetGenericTypeParameter(_genericContext, ReadCompressedUInt()); + + case ELEMENT_TYPE_MVAR: + return _provider.GetGenericMethodParameter(_genericContext, ReadCompressedUInt()); + + case ELEMENT_TYPE_FNPTR: + { + MethodSignature fnSig = DecodeMethodSignature(); + return _provider.GetFunctionPointerType(fnSig); + } + + case ELEMENT_TYPE_PINNED: + return _provider.GetPinnedType(DecodeType()); + + case ELEMENT_TYPE_INTERNAL: + { + ulong val = _reader.ReadPointerSized(_target.PointerSize); + return _provider.GetInternalType(_target, new TargetPointer(val)); + } + + default: + throw new BadImageFormatException($"Unexpected signature type code: 0x{typeCode:X2}"); + } + } + + /// + /// Decodes a TypeDefOrRefOrSpecEncoded token (ECMA-335 II.23.2.8). + /// The compressed value encodes tag in the low 2 bits and RID in the upper bits. + /// + private TType DecodeTypeDefOrRefOrSpec(byte rawTypeKind) + { + int coded = ReadCompressedUInt(); + int tag = coded & 0x3; + int rid = coded >> 2; + + if (rid == 0) + throw new BadImageFormatException("Nil TypeDefOrRefOrSpecEncoded handle in signature"); + + if (rid > 0x00FFFFFF) + throw new BadImageFormatException($"TypeDefOrRefOrSpecEncoded RID out of range: {rid}"); + + return tag switch + { + 0 => _provider.GetTypeFromDefinition(_metadataReader!, MetadataTokens.TypeDefinitionHandle(rid), rawTypeKind), + 1 => _provider.GetTypeFromReference(_metadataReader!, MetadataTokens.TypeReferenceHandle(rid), rawTypeKind), + 2 => _provider.GetTypeFromSpecification(_metadataReader!, _genericContext, MetadataTokens.TypeSpecificationHandle(rid), rawTypeKind), + _ => _provider.GetPrimitiveType(PrimitiveTypeCode.Object), // tag=3 is BaseType in native + }; + } + + private ArrayShape DecodeArrayShape() + { + int rank = ReadCompressedUInt(); + int numSizes = ReadCompressedUInt(); + if (numSizes > _reader.Remaining) + throw new BadImageFormatException($"Array size count {numSizes} exceeds remaining signature bytes"); + var sizes = ImmutableArray.CreateBuilder(numSizes); + for (int i = 0; i < numSizes; i++) + sizes.Add(ReadCompressedUInt()); + int numLoBounds = ReadCompressedUInt(); + if (numLoBounds > _reader.Remaining) + throw new BadImageFormatException($"Array lower bound count {numLoBounds} exceeds remaining signature bytes"); + var loBounds = ImmutableArray.CreateBuilder(numLoBounds); + for (int i = 0; i < numLoBounds; i++) + loBounds.Add(ReadCompressedSignedInt()); + return new ArrayShape(rank, sizes.MoveToImmutable(), loBounds.MoveToImmutable()); + } + + /// + /// Reads a compressed unsigned integer per ECMA-335 II.23.2. + /// + private int ReadCompressedUInt() + { + byte first = _reader.ReadByte(); + if ((first & 0x80) == 0) + return first; + if ((first & 0xC0) == 0x80) + return ((first & 0x3F) << 8) | _reader.ReadByte(); + if ((first & 0xE0) == 0xC0) + return ((first & 0x1F) << 24) | (_reader.ReadByte() << 16) | (_reader.ReadByte() << 8) | _reader.ReadByte(); + + throw new BadImageFormatException("Invalid compressed integer encoding"); + } + + /// + /// Reads a compressed signed integer per ECMA-335 II.23.2. + /// Uses sign extension based on encoded width, matching SRM's BlobReader.ReadCompressedSignedInteger. + /// + private int ReadCompressedSignedInt() + { + byte first = _reader.ReadByte(); + + if ((first & 0x80) == 0) + { + // 1-byte: 7 bits, sign bit is bit 0 of the encoded value + int value = first >> 1; + return (first & 1) != 0 ? value - 0x40 : value; + } + + if ((first & 0xC0) == 0x80) + { + // 2-byte: 14 bits + int raw = ((first & 0x3F) << 8) | _reader.ReadByte(); + int value = raw >> 1; + return (raw & 1) != 0 ? value - 0x2000 : value; + } + + if ((first & 0xE0) == 0xC0) + { + // 4-byte: 29 bits + int raw = ((first & 0x1F) << 24) | (_reader.ReadByte() << 16) | (_reader.ReadByte() << 8) | _reader.ReadByte(); + int value = raw >> 1; + return (raw & 1) != 0 ? value - 0x10000000 : value; + } + + throw new BadImageFormatException("Invalid compressed signed integer encoding"); + } +}