File: EnumMemory\MethodCollector.cs
Web Access
Project: Microsoft.Diagnostics.DataContractReader.Legacy.csproj (Microsoft.Diagnostics.DataContractReader.Legacy)
// 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.Text;
using Microsoft.Diagnostics.DataContractReader.Contracts;
using Microsoft.Diagnostics.DataContractReader.Contracts.Extensions;
using Microsoft.Diagnostics.DataContractReader.Legacy;
using ContractModuleHandle = Microsoft.Diagnostics.DataContractReader.Contracts.ModuleHandle;

namespace Microsoft.Diagnostics.DataContractReader.Legacy.EnumMemory;

internal sealed class MethodCollector(CollectorTarget target)
{
    private readonly CollectorTarget _collectorTarget = target;
    private readonly HashSet<TargetPointer> _captured = [];
    private readonly HashSet<TargetPointer> _capturedCodeBlocks = [];

    public void CaptureMethod(TargetPointer methodDesc, TargetCodePointer instructionPointer)
    {
        if (instructionPointer != TargetCodePointer.Null)
        {
            EnumerateInstructionPointer(instructionPointer);
            EnumerateCodeDependencies(instructionPointer);
        }

        if (methodDesc == TargetPointer.Null || !_captured.Add(methodDesc))
            return;

        EnumerateMethodDependencies(methodDesc);
        EnumerateMethodDescDataDependencies(methodDesc);
        CacheMethodName(methodDesc);
    }

    private void EnumerateInstructionPointer(TargetCodePointer instructionPointer)
    {
        ulong address = instructionPointer.ToAddress(_collectorTarget).Value;
        uint size = (uint)_collectorTarget.Target.PointerSize;
        ulong maxAddress = _collectorTarget.Target.PointerSize == sizeof(uint) ? uint.MaxValue : ulong.MaxValue;
        if (address > maxAddress - size)
            return;

        _collectorTarget.Emitter.Add(address, size);
    }

    private void EnumerateCodeDependencies(TargetCodePointer instructionPointer)
    {
        IExecutionManager executionManager = _collectorTarget.Target.Contracts.ExecutionManager;
        if (executionManager.GetCodeBlockHandle(instructionPointer) is not CodeBlockHandle codeBlock
            || !_capturedCodeBlocks.Add(codeBlock.Address))
            return;

        executionManager.GetGCInfo(codeBlock, out _, out _);

        IDebugInfo debugInfo = _collectorTarget.Target.Contracts.DebugInfo;
        if (!debugInfo.HasDebugInfo(instructionPointer))
            return;

        foreach (OffsetMapping _ in debugInfo.GetMethodNativeMap(instructionPointer, preferUninstrumented: true, out _))
        {
        }

        foreach (DebugVarInfo _ in debugInfo.GetMethodVarInfo(instructionPointer, out _))
        {
        }
    }

    private void EnumerateMethodDependencies(TargetPointer methodDesc)
    {
        IRuntimeTypeSystem types = _collectorTarget.Target.Contracts.RuntimeTypeSystem;
        MethodDescHandle method = types.GetMethodDescHandle(methodDesc);
        if (types.IsNoMetadataMethod(method, out _))
            return;

        uint token = types.GetMethodToken(method);
        TargetPointer methodTable = types.GetMethodTable(method);
        TargetPointer module = types.GetModule(types.GetTypeHandle(methodTable));
        ILoader loader = _collectorTarget.Target.Contracts.Loader;
        ContractModuleHandle moduleHandle = loader.GetModuleHandleFromModulePtr(module);
        loader.GetAssembly(moduleHandle);
        loader.GetFlags(moduleHandle);
        if (_collectorTarget.DumpType != DumpType.Triage)
            loader.GetPath(moduleHandle);
        loader.IsProbeExtensionResultValid(moduleHandle);
        loader.GetModuleLookupMapElement(moduleHandle, ModuleLookupMapKind.MethodDefToDesc, token, out _);
        loader.GetILHeader(moduleHandle, token);
    }

    private void EnumerateMethodDescDataDependencies(TargetPointer methodDesc)
    {
        IRuntimeTypeSystem types = _collectorTarget.Target.Contracts.RuntimeTypeSystem;
        MethodDescHandle method = types.GetMethodDescHandle(methodDesc);
        ICodeVersions codeVersions = _collectorTarget.Target.Contracts.CodeVersions;
        NativeCodeVersionHandle codeVersion = codeVersions.GetActiveNativeCodeVersion(methodDesc);

        types.IsDynamicMethod(method);
        types.GetSlotNumber(method);
        if (codeVersion.Valid)
        {
            TargetCodePointer nativeCode = codeVersions.GetNativeCode(codeVersion);
            if (nativeCode != TargetCodePointer.Null)
                _collectorTarget.Target.Contracts.PrecodeStubs.GetInterpreterCodeFromInterpreterPrecodeIfPresent(nativeCode);

            codeVersions.GetGCStressCodeCopy(codeVersion);
        }
        if (types.HasNativeCodeSlot(method))
            types.GetAddressOfNativeCodeSlot(method);
    }

    private void CacheMethodName(TargetPointer methodDesc)
    {
        if (_collectorTarget.MiniMetadata.Contains(methodDesc))
            return;

        IRuntimeTypeSystem types = _collectorTarget.Target.Contracts.RuntimeTypeSystem;
        MethodDescHandle method = types.GetMethodDescHandle(methodDesc);
        if (types.IsNoMetadataMethod(method, out _) && !types.IsILStub(method))
            return;

        try
        {
            StringBuilder name = new();
            TypeNameBuilder.AppendMethodInternal(
                _collectorTarget,
                name,
                method,
                TypeNameFormat.FormatSignature |
                TypeNameFormat.FormatNamespace |
                TypeNameFormat.FormatFullInst);
            if (_collectorTarget.DumpType is DumpType.Mini or DumpType.Triage)
                Sanitizer.StripArgumentsFromMethodName(name);
            if (name.Length != 0)
                _collectorTarget.MiniMetadata.Add(methodDesc, name.ToString());
        }
        catch (System.Exception ex) when (ex.HResult != HResults.COR_E_OPERATIONCANCELED)
        {
        }
    }
}