File: Contracts\ExecutionManager\ExecutionManagerCore.ReadyToRunJitManager.cs
Web Access
Project: Microsoft.Diagnostics.DataContractReader.Contracts.csproj (Microsoft.Diagnostics.DataContractReader.Contracts)
// 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.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using Microsoft.Diagnostics.DataContractReader.Data;
using Microsoft.Diagnostics.DataContractReader.ExecutionManagerHelpers;

namespace Microsoft.Diagnostics.DataContractReader.Contracts;

internal partial class ExecutionManagerCore<T> : IExecutionManager
{
    private sealed class ReadyToRunJitManager : JitManager
    {
        private readonly PtrHashMapLookup _hashMap;
        private readonly HotColdLookup _hotCold;
        private readonly RuntimeFunctionLookup _runtimeFunctions;

        public ReadyToRunJitManager(Target target) : base(target)
        {
            _hashMap = PtrHashMapLookup.Create(target);
            _hotCold = HotColdLookup.Create(target);
            _runtimeFunctions = RuntimeFunctionLookup.Create(
                target,
                target.Contracts.RuntimeInfo.GetTargetArchitecture() == RuntimeInfoArchitecture.Wasm);
        }

        private enum ReadyToRunSectionType
        {
            ExceptionInfo = 104,
        }

        public override bool GetMethodInfo(RangeSection rangeSection, TargetCodePointer jittedCodeAddress, [NotNullWhen(true)] out CodeBlock? info)
        {
            // ReadyToRunJitManager::JitCodeToMethodInfo
            info = default;

            Data.ReadyToRunInfo r2rInfo = GetReadyToRunInfo(rangeSection);
            if (!GetRuntimeFunction(rangeSection, r2rInfo, jittedCodeAddress, out TargetPointer codeBase, out uint index))
                return false;

            index = AdjustRuntimeFunctionIndexForHotCold(r2rInfo, index);
            if (!TryAdjustRuntimeFunctionToMethodStart(r2rInfo, codeBase, index, out index, out TargetPointer methodDesc))
                return false;

            Data.RuntimeFunction function = _runtimeFunctions.GetRuntimeFunction(r2rInfo.RuntimeFunctions, index);
            uint functionBeginAddress = _runtimeFunctions.GetBeginAddress(function);

            TargetPointer addr = CodePointerUtils.AddressFromCodePointer(jittedCodeAddress, Target);
            // The R2R RUNTIME_FUNCTION.BeginAddress encodes thumb code with the thumb bit set on
            // ARM32. Native RUNTIME_FUNCTION__BeginAddress uses ThumbCodeToDataPointer to strip it
            // (clrnt.h, ARM32 branch). Mirror that so we store a raw TADDR-style start address.
            TargetPointer startAddress = CodePointerUtils.AddressFromCodePointer(
                new TargetCodePointer(codeBase.Value + functionBeginAddress), Target);
            TargetNUInt relativeOffset = new TargetNUInt(addr - startAddress);

            // Take hot/cold splitting into account for the relative offset
            if (_hotCold.TryGetColdFunctionIndex(r2rInfo.NumHotColdMap ?? 0, r2rInfo.HotColdMap, index, out uint coldFunctionIndex))
            {
                Debug.Assert(coldFunctionIndex < r2rInfo.NumRuntimeFunctions);
                Data.RuntimeFunction coldFunction = _runtimeFunctions.GetRuntimeFunction(r2rInfo.RuntimeFunctions, coldFunctionIndex);
                TargetPointer coldStart = CodePointerUtils.AddressFromCodePointer(
                    new TargetCodePointer(codeBase.Value + _runtimeFunctions.GetBeginAddress(coldFunction)), Target);
                if (addr >= coldStart)
                {
                    // If the address is in the cold part, the relative offset is the size of the
                    // hot part plus the offset from the address to the start of the cold part
                    uint hotSize = _runtimeFunctions.GetFunctionLength(GetLoadedImageBase(rangeSection, r2rInfo), function);
                    relativeOffset = new TargetNUInt(hotSize + addr - coldStart);
                }
            }

            info = new CodeBlock(startAddress, methodDesc, relativeOffset, rangeSection.Data!.JitManager);
            return true;
        }

        public override void GetMethodRegionInfo(
            RangeSection rangeSection,
            TargetCodePointer jittedCodeAddress,
            out uint hotSize,
            out TargetPointer coldStart,
            out uint coldSize)
        {
            coldSize = 0;
            coldStart = TargetPointer.Null;

            IGCInfo gcInfo = Target.Contracts.GCInfo;
            GetGCInfo(rangeSection, jittedCodeAddress, out TargetPointer pGcInfo, out uint gcVersion);
            IGCInfoHandle gcInfoHandle = gcInfo.DecodePlatformSpecificGCInfo(pGcInfo, gcVersion);
            hotSize = gcInfo.GetCodeLength(gcInfoHandle);

            Data.ReadyToRunInfo r2rInfo = GetReadyToRunInfo(rangeSection);
            if (!GetRuntimeFunction(rangeSection, r2rInfo, jittedCodeAddress, out TargetPointer codeBase, out uint index))
                return;

            if (_hotCold.TryGetColdFunctionIndex(r2rInfo.NumHotColdMap ?? 0, r2rInfo.HotColdMap, index, r2rInfo.NumRuntimeFunctions, out uint coldStartIdx, out uint coldEndIdx))
            {
                Data.RuntimeFunction coldStartFunc = _runtimeFunctions.GetRuntimeFunction(r2rInfo.RuntimeFunctions, coldStartIdx);
                Data.RuntimeFunction coldEndFunc = _runtimeFunctions.GetRuntimeFunction(r2rInfo.RuntimeFunctions, coldEndIdx);
                uint coldBeginOffset = _runtimeFunctions.GetBeginAddress(coldStartFunc);
                uint coldEndOffset = _runtimeFunctions.GetBeginAddress(coldEndFunc)
                    + _runtimeFunctions.GetFunctionLength(GetLoadedImageBase(rangeSection, r2rInfo), coldEndFunc);
                coldSize = coldEndOffset - coldBeginOffset;
                coldStart = codeBase + coldBeginOffset;

                hotSize -= coldSize;
            }
        }

        public override TargetPointer GetUnwindInfo(RangeSection rangeSection, TargetCodePointer jittedCodeAddress)
        {
            // ReadyToRunJitManager::JitCodeToMethodInfo
            Data.ReadyToRunInfo r2rInfo = GetReadyToRunInfo(rangeSection);
            if (!GetRuntimeFunction(rangeSection, r2rInfo, jittedCodeAddress, out TargetPointer _, out uint index))
                return TargetPointer.Null;

            return _runtimeFunctions.GetRuntimeFunctionAddress(r2rInfo.RuntimeFunctions, index);
        }

        public override TargetPointer GetDebugInfo(RangeSection rangeSection, TargetCodePointer jittedCodeAddress, out bool hasFlagByte)
        {
            // ReadyToRun does not contain PatchpointInfo
            hasFlagByte = false;

            // ReadyToRunJitManager::GetDebugInfo
            Data.ReadyToRunInfo r2rInfo = GetReadyToRunInfo(rangeSection);

            // Images compiled with --strip-debug-info have no DebugInfo section
            if (r2rInfo.DebugInfoSection == TargetPointer.Null)
                return TargetPointer.Null;

            if (!GetRuntimeFunction(rangeSection, r2rInfo, jittedCodeAddress, out TargetPointer codeBase, out uint index))
                return TargetPointer.Null;

            index = AdjustRuntimeFunctionIndexForHotCold(r2rInfo, index);
            if (!TryAdjustRuntimeFunctionToMethodStart(r2rInfo, codeBase, index, out index, out _))
                return TargetPointer.Null;

            Data.ImageDataDirectory debugInfoData = Target.ProcessedData.GetOrAdd<Data.ImageDataDirectory>(r2rInfo.DebugInfoSection);
            TargetPointer loadedImageBase = GetLoadedImageBase(rangeSection, r2rInfo);

            ILCompiler.Reflection.ReadyToRun.NativeReader imageReader = new(
                new TargetStream(Target, loadedImageBase, debugInfoData.VirtualAddress + debugInfoData.Size),
                Target.IsLittleEndian
            );
            ILCompiler.Reflection.ReadyToRun.NativeArray debugInfoArray = new(imageReader, debugInfoData.VirtualAddress);

            int offset = 0;
            if (!debugInfoArray.TryGetAt(index, ref offset))
                // If the index is not found in the debug info array, return null
                return TargetPointer.Null;

            uint lookBack = 0;
            uint debugInfoOffset = imageReader.DecodeUnsigned((uint)offset, ref lookBack);
            if (lookBack != 0)
                debugInfoOffset = (uint)offset - lookBack;

            return loadedImageBase + debugInfoOffset;
        }

        public override CodeKind GetCodeKind(RangeSection rangeSection, TargetCodePointer codeAddress)
        {
            if (rangeSection.Data == null)
                return CodeKind.Unknown;
            return IsStubCodeBlockThunk(rangeSection, GetReadyToRunInfo(rangeSection), codeAddress) ? CodeKind.MethodCallThunk : CodeKind.ReadyToRun;
        }

        public override void GetGCInfo(RangeSection rangeSection, TargetCodePointer jittedCodeAddress, out TargetPointer gcInfo, out uint gcVersion)
        {
            gcInfo = TargetPointer.Null;
            gcVersion = 0;

            // ReadyToRunJitManager::GetGCInfoToken
            Data.ReadyToRunInfo r2rInfo = GetReadyToRunInfo(rangeSection);
            if (!GetRuntimeFunction(rangeSection, r2rInfo, jittedCodeAddress, out TargetPointer codeBase, out uint index))
                return;

            index = AdjustRuntimeFunctionIndexForHotCold(r2rInfo, index);
            if (!TryAdjustRuntimeFunctionToMethodStart(r2rInfo, codeBase, index, out index, out _))
                return;

            Data.RuntimeFunction runtimeFunction = _runtimeFunctions.GetRuntimeFunction(r2rInfo.RuntimeFunctions, index);

            TargetPointer unwindInfo = runtimeFunction.UnwindData + GetLoadedImageBase(rangeSection, r2rInfo);
            uint unwindDataSize = UnwindDataSize.GetUnwindDataSize(Target, unwindInfo, Target.Contracts.RuntimeInfo.GetTargetArchitecture());
            gcInfo = unwindInfo + unwindDataSize;
            gcVersion = GetR2RGCInfoVersion(r2rInfo);
        }

        // This function must be kept up to date with R2R version changes.
        // When the R2R version is bumped, it must be mapped to the correct GCInfo version.
        // Adding "MINIMUM_READYTORUN_MAJOR_VERSION" to ensure this is updated according
        // to instructions in readytorun.h
        private uint GetR2RGCInfoVersion(Data.ReadyToRunInfo r2rInfo)
        {
            Data.ReadyToRunHeader header = Target.ProcessedData.GetOrAdd<Data.ReadyToRunHeader>(r2rInfo.ReadyToRunHeader);

            // see readytorun.h for the versioning details
            return header.MajorVersion switch
            {
                >= 21 => 5,
                >= 11 => 4,
                 < 11 => 3,
            };
        }

        #region RuntimeFunction Helpers

        private Data.ReadyToRunInfo GetReadyToRunInfo(RangeSection rangeSection)
        {
            if (rangeSection.Data == null)
                throw new ArgumentException(nameof(rangeSection));

            Debug.Assert(rangeSection.Data.R2RModule != TargetPointer.Null);

            Data.Module r2rModule = Target.ProcessedData.GetOrAdd<Data.Module>(rangeSection.Data.R2RModule);
            Debug.Assert(r2rModule.ReadyToRunInfo != TargetPointer.Null);
            return Target.ProcessedData.GetOrAdd<Data.ReadyToRunInfo>(r2rModule.ReadyToRunInfo);
        }

        private bool GetRuntimeFunction(
            RangeSection rangeSection,
            Data.ReadyToRunInfo r2rInfo,
            TargetCodePointer jittedCodeAddress,
            out TargetPointer codeBase,
            out uint runtimeFunctionIndex)
        {
            codeBase = TargetPointer.Null;
            runtimeFunctionIndex = 0;

            if (rangeSection.Data == null)
                throw new ArgumentException(nameof(rangeSection));

            // Check if address is in a thunk
            if (IsStubCodeBlockThunk(rangeSection, r2rInfo, jittedCodeAddress))
                return false;

            // Find the relative address that we are looking for
            TargetPointer addr = CodePointerUtils.AddressFromCodePointer(jittedCodeAddress, Target);
            codeBase = GetCodeBase(rangeSection, r2rInfo);
            if (codeBase == TargetPointer.Null)
                return false;

            TargetPointer relativeAddr = addr - codeBase;

            return _runtimeFunctions.TryGetRuntimeFunctionIndexForAddress(r2rInfo.RuntimeFunctions, r2rInfo.NumRuntimeFunctions, relativeAddr, out runtimeFunctionIndex);
        }

        private uint AdjustRuntimeFunctionIndexForHotCold(Data.ReadyToRunInfo r2rInfo, uint index)
        {
            // Look up index in hot/cold map - if the function is in the cold part, get the index of the hot part.
            index = _hotCold.GetHotFunctionIndex(r2rInfo.NumHotColdMap ?? 0, r2rInfo.HotColdMap, index);
            Debug.Assert(index < r2rInfo.NumRuntimeFunctions);
            return index;
        }

        private bool TryAdjustRuntimeFunctionToMethodStart(
            Data.ReadyToRunInfo r2rInfo,
            TargetPointer codeBase,
            uint index,
            out uint methodIndex,
            out TargetPointer methodDesc)
        {
            methodIndex = index;
            methodDesc = GetMethodDescForRuntimeFunction(r2rInfo, codeBase, methodIndex);
            while (methodDesc == TargetPointer.Null)
            {
                if (methodIndex == 0)
                    return false;

                // Funclets won't have a direct entry in the map of runtime function entry point to method desc.
                // The funclet's address (and index) will be greater than that of the corresponding function, so
                // we decrement the index to find the actual function / method desc for the funclet.
                methodIndex--;
                methodDesc = GetMethodDescForRuntimeFunction(r2rInfo, codeBase, methodIndex);
            }

            Debug.Assert(methodDesc != TargetPointer.Null);
            return true;
        }

        private bool IsStubCodeBlockThunk(RangeSection rangeSection, Data.ReadyToRunInfo r2rInfo, TargetCodePointer jittedCodeAddress)
        {
            if (rangeSection.IsVirtualIP)
                return false;

            if (r2rInfo.DelayLoadMethodCallThunks is not { } thunks || thunks == TargetPointer.Null)
                return false;

            // Check if the address is in the region containing thunks for READYTORUN_HELPER_DelayLoad_MethodCall
            Data.ImageDataDirectory thunksData = Target.ProcessedData.GetOrAdd<Data.ImageDataDirectory>(thunks);
            ulong rva = jittedCodeAddress - GetLoadedImageBase(rangeSection, r2rInfo);
            return thunksData.VirtualAddress <= rva && rva < thunksData.VirtualAddress + thunksData.Size;
        }

        private TargetPointer GetMethodDescForRuntimeFunction(Data.ReadyToRunInfo r2rInfo, TargetPointer codeBase, uint runtimeFunctionIndex)
        {
            Data.RuntimeFunction function = _runtimeFunctions.GetRuntimeFunction(r2rInfo.RuntimeFunctions, runtimeFunctionIndex);

            // ReadyToRunInfo::GetMethodDescForEntryPointInNativeImage
            TargetCodePointer startAddress = codeBase + _runtimeFunctions.GetBeginAddress(function);
            TargetPointer entryPoint = CodePointerUtils.AddressFromCodePointer(startAddress, Target);

            TargetPointer methodDesc = _hashMap.GetValue(r2rInfo.EntryPointToMethodDescMap, entryPoint);
            if (methodDesc == (ulong)HashMapLookup.SpecialKeys.InvalidEntry)
                return TargetPointer.Null;

            return methodDesc;
        }

        private static TargetPointer GetCodeBase(RangeSection rangeSection, Data.ReadyToRunInfo r2rInfo)
            => rangeSection.IsVirtualIP ? r2rInfo.MinVirtualIP ?? TargetPointer.Null : rangeSection.Data!.RangeBegin;

        private static TargetPointer GetLoadedImageBase(RangeSection rangeSection, Data.ReadyToRunInfo r2rInfo)
            => rangeSection.IsVirtualIP ? r2rInfo.LoadedImageBase : rangeSection.Data!.RangeBegin;
        #endregion

        private void GetExceptionClauses(TargetPointer exceptionLookupTableAddr, uint count, TargetPointer rangeStart, uint methodRVA, out TargetPointer startExInfoRVA, out TargetPointer endExInfoRVA)
        {
            startExInfoRVA = TargetPointer.Null;
            endExInfoRVA = TargetPointer.Null;
            if (count < 2)
                return;

            uint entrySize = Data.ExceptionLookupTableEntry.GetSize(Target);

            Data.ExceptionLookupTableEntry GetEntry(uint index)
                => Target.ProcessedData.GetOrAdd<Data.ExceptionLookupTableEntry>(exceptionLookupTableAddr + (index * entrySize));

            if (!BinaryThenLinearSearch.Search(
                0,
                count - 2,
                index => methodRVA < GetEntry(index).MethodStartRVA,
                index => methodRVA == GetEntry(index).MethodStartRVA,
                out uint foundIndex))
                return;

            Data.ExceptionLookupTableEntry entry = GetEntry(foundIndex);
            Data.ExceptionLookupTableEntry nextEntry = GetEntry(foundIndex + 1);
            startExInfoRVA = new TargetPointer(entry.ExceptionInfoRVA + rangeStart);
            endExInfoRVA = new TargetPointer(nextEntry.ExceptionInfoRVA + rangeStart);
        }

        public override void GetExceptionClauses(RangeSection range, CodeBlockHandle cbh, out TargetPointer startAddr, out TargetPointer endAddr)
        {
            Data.ReadyToRunInfo r2rInfo = GetReadyToRunInfo(range);
            ImageDataDirectory? section = FindSection(r2rInfo, (uint)ReadyToRunSectionType.ExceptionInfo);
            if (section == null)
            {
                startAddr = TargetPointer.Null;
                endAddr = TargetPointer.Null;
                return;
            }

            uint count = section.Size / Data.ExceptionLookupTableEntry.GetSize(Target);
            TargetPointer loadedImageBase = GetLoadedImageBase(range, r2rInfo);
            ulong exceptionLookupTableAddr = section.VirtualAddress + loadedImageBase;

            GetMethodRVAAndImageBase(range, r2rInfo, cbh, out uint methodRVA, out TargetPointer imageBase);

            GetExceptionClauses(exceptionLookupTableAddr, count, imageBase, methodRVA, out startAddr, out endAddr);
        }

        private ImageDataDirectory? FindSection(Data.ReadyToRunInfo r2rInfo, uint sectionType)
        {
            Data.ReadyToRunCoreInfo coreInfo = Target.ProcessedData.GetOrAdd<Data.ReadyToRunCoreInfo>(r2rInfo.Composite);
            foreach (Data.ReadyToRunSection section in coreInfo.Header.Sections)
            {
                if (section.Type == sectionType)
                    return section.Section;
            }
            return null;
        }

        private void GetMethodRVAAndImageBase(
            RangeSection range,
            Data.ReadyToRunInfo r2rInfo,
            CodeBlockHandle cbh,
            out uint methodRVA,
            out TargetPointer imageBase)
        {
            IExecutionManager executionManager = Target.Contracts.ExecutionManager;
            TargetPointer methodStart = executionManager.GetStartAddress(cbh);
            TargetPointer codeBase = GetCodeBase(range, r2rInfo);
            methodRVA = checked((uint)(methodStart - codeBase));
            imageBase = GetLoadedImageBase(range, r2rInfo);
        }

    }
}