File: EnumMemory\MemoryRegionEmitter.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.Runtime.InteropServices;

namespace Microsoft.Diagnostics.DataContractReader.Legacy.EnumMemory;

internal sealed unsafe class MemoryRegionEmitter(nint callback, uint pointerSize, DumpType dumpType = DumpType.Mini)
{
    private const uint MaxMiniDumpRegion = 4 * 1024 * 1024 - 3;
    private static readonly Guid s_ICLRDataEnumMemoryRegionsCallback2_Iid = new("3721A26F-8B91-4D98-A388-DB17B356FADB");

    private readonly delegate* unmanaged[MemberFunction]<nint, Guid*, nint*, int> _queryInterface =
        (delegate* unmanaged[MemberFunction]<nint, Guid*, nint*, int>)(*(nint**)callback)[0];
    // ICLRDataEnumMemoryRegionsCallback::EnumMemoryRegion follows the three IUnknown vtable slots.
    private readonly delegate* unmanaged[MemberFunction]<nint, ulong, uint, int> _enumMemoryRegion =
        (delegate* unmanaged[MemberFunction]<nint, ulong, uint, int>)(*(nint**)callback)[3];
    private readonly List<TargetSpan> _metadataRanges = [];

    public int Result { get; private set; }

    public void Add(ulong address, uint size) => Add(address, (ulong)size);

    public void Add(ulong address, ulong size)
    {
        if (address == 0 || size == 0)
            return;

        // Like the native DAC, treat unusually large mini/triage regions as likely target corruption
        // and truncate them to keep dumps small. The distinctive 4 MiB minus 3 size identifies truncation.
        if (dumpType is DumpType.Mini or DumpType.Triage)
            size = Math.Min(size, MaxMiniDumpRegion);

        while (size != 0)
        {
            uint chunkSize = (uint)Math.Min(size, uint.MaxValue);
            int hr = _enumMemoryRegion(callback, ToClrDataAddress(address), chunkSize);
            if (hr == HResults.COR_E_OPERATIONCANCELED)
                Marshal.ThrowExceptionForHR(hr);

            if (hr < 0 && Result >= 0)
                Result = hr;

            address = checked(address + chunkSize);
            size -= chunkSize;
        }
    }

    public void RegisterMetadataRange(TargetSpan range)
    {
        if (range.Address != TargetPointer.Null && range.Size != 0)
            _metadataRanges.Add(range);
    }

    public bool ShouldEmitTargetRead(ulong address, ulong size)
    {
        foreach (TargetSpan range in _metadataRanges)
        {
            if (address < range.Address.Value)
                continue;

            ulong offset = address - range.Address.Value;
            if (offset <= range.Size && size <= range.Size - offset)
                return false;
        }

        return true;
    }

    public bool Update(ulong address, ReadOnlySpan<byte> buffer)
    {
        nint callback2 = 0;
        Guid iid = s_ICLRDataEnumMemoryRegionsCallback2_Iid;
        int hr = _queryInterface(callback, &iid, &callback2);

        if (hr < 0 || callback2 == 0)
            return false;

        try
        {
            delegate* unmanaged[MemberFunction]<nint, ulong, uint, byte*, int> updateMemoryRegion =
                (delegate* unmanaged[MemberFunction]<nint, ulong, uint, byte*, int>)(*(nint**)callback2)[4];
            fixed (byte* bufferPointer = buffer)
            {
                hr = updateMemoryRegion(
                    callback2,
                    ToClrDataAddress(address),
                    (uint)buffer.Length,
                    bufferPointer);
            }

            if (hr < 0)
                return false;

            return true;
        }
        finally
        {
            delegate* unmanaged[MemberFunction]<nint, uint> release =
                (delegate* unmanaged[MemberFunction]<nint, uint>)(*(nint**)callback2)[2];
            release(callback2);
        }
    }

    internal ulong ToClrDataAddress(ulong address) =>
        pointerSize == sizeof(uint) ? (ulong)(long)(int)address : address;
}