File: Contracts\ExecutionManager\ExecutionManagerCore.ReadyToRunJitManager.cs
Web Access
Project: src\src\runtime\src\native\managed\cdac\Microsoft.Diagnostics.DataContractReader.Contracts\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);
        }

        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 imageBase, out uint index))
                return false;

            index = AdjustRuntimeFunctionIndexForHotCold(r2rInfo, index);
            index = AdjustRuntimeFunctionToMethodStart(r2rInfo, imageBase, index, out TargetPointer methodDesc);

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

            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(imageBase.Value + function.BeginAddress), Target);
            TargetNUInt relativeOffset = new TargetNUInt(addr - startAddress);

            // Take hot/cold splitting into account for the relative offset
            if (_hotCold.TryGetColdFunctionIndex(r2rInfo.NumHotColdMap, 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(imageBase.Value + coldFunction.BeginAddress), 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(imageBase, 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 imageBase, out uint index))
                return;

            if (_hotCold.TryGetColdFunctionIndex(r2rInfo.NumHotColdMap, 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 = coldStartFunc.BeginAddress;
                uint coldEndOffset = coldEndFunc.BeginAddress + _runtimeFunctions.GetFunctionLength(imageBase, coldEndFunc);
                coldSize = coldEndOffset - coldBeginOffset;
                coldStart = imageBase + 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);
            if (!GetRuntimeFunction(rangeSection, r2rInfo, jittedCodeAddress, out TargetPointer imageBase, out uint index))
                return TargetPointer.Null;

            index = AdjustRuntimeFunctionIndexForHotCold(r2rInfo, index);
            index = AdjustRuntimeFunctionToMethodStart(r2rInfo, imageBase, index, out _);

            Data.ImageDataDirectory debugInfoData = Target.ProcessedData.GetOrAdd<Data.ImageDataDirectory>(r2rInfo.DebugInfoSection);

            ILCompiler.Reflection.ReadyToRun.NativeReader imageReader = new(
                new TargetStream(Target, imageBase, 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 imageBase + debugInfoOffset;
        }

        public override CodeKind GetCodeKind(RangeSection rangeSection, TargetCodePointer codeAddress)
        {
            if (rangeSection.Data == null)
                return CodeKind.Unknown;
            return IsStubCodeBlockThunk(rangeSection.Data, 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 imageBase, out uint index))
                return;

            index = AdjustRuntimeFunctionIndexForHotCold(r2rInfo, index);
            index = AdjustRuntimeFunctionToMethodStart(r2rInfo, imageBase, index, out _);

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

            TargetPointer unwindInfo = runtimeFunction.UnwindData + imageBase;
            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
            {
                < 11 => 3,
                >= 11 => 4,
            };
        }

        #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 imageBase,
            out uint runtimeFunctionIndex)
        {
            imageBase = TargetPointer.Null;
            runtimeFunctionIndex = 0;

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

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

            // Find the relative address that we are looking for
            TargetPointer addr = CodePointerUtils.AddressFromCodePointer(jittedCodeAddress, Target);
            imageBase = rangeSection.Data.RangeBegin;
            TargetPointer relativeAddr = addr - imageBase;

            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, r2rInfo.HotColdMap, index);
            Debug.Assert(index < r2rInfo.NumRuntimeFunctions);
            return index;
        }

        private uint AdjustRuntimeFunctionToMethodStart(Data.ReadyToRunInfo r2rInfo, TargetPointer imageBase, uint index, out TargetPointer methodDesc)
        {
            methodDesc = GetMethodDescForRuntimeFunction(r2rInfo, imageBase, index);
            while (methodDesc == TargetPointer.Null)
            {
                // 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.
                index--;
                methodDesc = GetMethodDescForRuntimeFunction(r2rInfo, imageBase, index);
            }

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

        private bool IsStubCodeBlockThunk(Data.RangeSection rangeSection, Data.ReadyToRunInfo r2rInfo, TargetCodePointer jittedCodeAddress)
        {
            if (r2rInfo.DelayLoadMethodCallThunks == 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>(r2rInfo.DelayLoadMethodCallThunks);
            ulong rva = jittedCodeAddress - rangeSection.RangeBegin;
            return thunksData.VirtualAddress <= rva && rva < thunksData.VirtualAddress + thunksData.Size;
        }

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

            // ReadyToRunInfo::GetMethodDescForEntryPointInNativeImage
            TargetCodePointer startAddress = imageBase + function.BeginAddress;
            TargetPointer entryPoint = CodePointerUtils.AddressFromCodePointer(startAddress, Target);

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

            return methodDesc;
        }
        #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 = Target.GetTypeInfo(DataType.ExceptionLookupTableEntry).Size!.Value;

            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 / Target.GetTypeInfo(DataType.ExceptionLookupTableEntry).Size!.Value;
            ulong exceptionLookupTableAddr = section.VirtualAddress + r2rInfo.LoadedImageBase;

            GetMethodRVAAndRangeStart(cbh, out TargetPointer methodStart, out TargetPointer rangeStart);
            uint methodRVA = (uint)(methodStart - rangeStart);

            GetExceptionClauses(exceptionLookupTableAddr, count, rangeStart, 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 GetMethodRVAAndRangeStart(CodeBlockHandle cbh, out TargetPointer methodStart, out TargetPointer rangeStart)
        {
            IExecutionManager executionManager = Target.Contracts.ExecutionManager;
            methodStart = executionManager.GetStartAddress(cbh);
            rangeStart = executionManager.GetUnwindInfoBaseAddress(cbh);
        }

    }
}