From 610be95662edbf46948d6665d35b9e12e4445953 Mon Sep 17 00:00:00 2001 From: rcj1 Date: Sun, 12 Apr 2026 21:46:39 -0700 Subject: [PATCH 1/4] GetRuntimeNameByAddress --- docs/design/datacontracts/ExecutionManager.md | 41 ++++ docs/design/datacontracts/PrecodeStubs.md | 20 ++ src/coreclr/debug/daccess/daccess.cpp | 2 +- src/coreclr/inc/gfunc_list.h | 8 + src/coreclr/vm/codeman.h | 1 + .../vm/datadescriptor/datadescriptor.inc | 17 ++ src/coreclr/vm/loaderallocator.hpp | 7 + .../Contracts/IExecutionManager.cs | 21 ++ .../Contracts/IPrecodeStubs.cs | 2 + .../DataType.cs | 1 + .../Constants.cs | 5 + .../ExecutionManagerCore.EEJitManager.cs | 84 +++++++- ...ecutionManagerCore.ReadyToRunJitManager.cs | 7 + .../ExecutionManager/ExecutionManagerCore.cs | 61 ++++++ .../ExecutionManager/ExecutionManager_1.cs | 1 + .../ExecutionManager/ExecutionManager_2.cs | 1 + .../Contracts/PrecodeStubs_Common.cs | 18 ++ .../Data/CodeRangeMapRangeList.cs | 18 ++ .../Data/RangeSection.cs | 2 + .../SOSDacImpl.IXCLRDataProcess.cs | 182 +++++++++++++++++- .../ExecutionManager/ExecutionManagerTests.cs | 100 ++++++++++ .../MockDescriptors.ExecutionManager.cs | 53 +++++ 22 files changed, 646 insertions(+), 6 deletions(-) create mode 100644 src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/CodeRangeMapRangeList.cs diff --git a/docs/design/datacontracts/ExecutionManager.md b/docs/design/datacontracts/ExecutionManager.md index cf7163569789f9..cf430fac49d155 100644 --- a/docs/design/datacontracts/ExecutionManager.md +++ b/docs/design/datacontracts/ExecutionManager.md @@ -52,6 +52,10 @@ struct CodeBlockHandle // Get the exception clause info for the code block List GetExceptionClauses(CodeBlockHandle codeInfoHandle); + // Classify a code address as a known stub kind (precode, jump stub, VSD stub, etc.) + // or as managed code. Returns CodeBlockUnknown if the address is not recognized. + StubKind GetStubKind(TargetCodePointer jittedCodeAddress); + // Extension Methods (implemented in terms of other APIs) // Returns true if the code block is a funclet (exception handler, filter, or finally) bool IsFunclet(CodeBlockHandle codeInfoHandle); @@ -143,6 +147,8 @@ Data descriptors used: | `RangeSection` | `Flags` | Flags for the range section | | `RangeSection` | `HeapList` | Pointer to the heap list | | `RangeSection` | `R2RModule` | ReadyToRun module | +| `RangeSection` | `RangeList` | Pointer to the `CodeRangeMapRangeList` associated with this range section | +| `CodeRangeMapRangeList` | `RangeListType` | Integer identifying the stub code block kind for this range list | | `CodeHeapListNode` | `Next` | Next node | | `CodeHeapListNode` | `StartAddress` | Start address of the used portion of the code heap | | `CodeHeapListNode` | `EndAddress` | End address of the used portion of the code heap | @@ -219,6 +225,11 @@ Global variables used: | `GCInfoVersion` | uint32 | JITted code GCInfo version | | `FeatureOnStackReplacement` | uint8 | 1 if FEATURE_ON_STACK_REPLACEMENT is enabled, 0 otherwise | | `FeaturePortableEntrypoints` | uint8 | 1 if FEATURE_PORTABLE_ENTRYPOINTS is enabled, 0 otherwise | +| `ThePreStub` | TargetPointer | Address of the pre-stub entry point (only present when `FeaturePortableEntrypoints` is disabled) | +| `GenericPInvokeCalliHelper` | TargetPointer | Address of the generic PInvoke CALLI helper stub | +| `VarargPInvokeStub` | TargetPointer | Address of the vararg PInvoke stub | +| `VarargPInvokeStub_RetBuffArg` | TargetPointer | Address of the vararg PInvoke stub with return buffer argument (not present on x86, ARM64, LoongArch64, RISC-V) | +| `TailCallJitHelper` | TargetPointer | Address of the JIT tail call helper (only present on x86 Windows) | | `ObjectMethodTable` | TargetPointer | Pointer to the `System.Object` MethodTable, used for catch-all handler detection | Contract constants used: @@ -501,6 +512,36 @@ After obtaining the clause array bounds, the common iteration logic classifies e `IsFilterFunclet` first checks `IsFunclet`. If the code block is a funclet, it retrieves the EH clauses for the method and checks whether any filter clause's handler offset matches the funclet's relative offset. If a match is found, the funclet is a filter funclet. +### Stub Kind Classification + +`GetStubKind` classifies a code address as a known stub type or managed code. It first checks the address against well-known global stub pointers (`ThePreStub`, `VarargPInvokeStub`, `VarargPInvokeStub_RetBuffArg`, `GenericPInvokeCalliHelper`, `TailCallJitHelper`). If the address matches one of these, it returns the corresponding `StubKind` immediately. + +If no global match is found, the method looks up the address in the `RangeSectionMap`. If a `RangeSection` is found, the JIT manager for that section classifies the code: + +- **EEJitManager**: If the range section is a range list, reads the `CodeRangeMapRangeList.RangeListType` to determine the stub code block kind. Otherwise, it uses the nibble map to find the method code start, reads the code header indirect pointer, and checks whether it is a stub code block (value ≤ `StubCodeBlockLast`). If so, the value identifies the specific stub kind. +- **ReadyToRunJitManager**: Checks whether the address falls within a delay-load method call thunk region. + +```csharp +StubKind GetStubKind(TargetCodePointer jittedCodeAddress) +{ + TargetPointer address = CodePointerUtils.AddressFromCodePointer(jittedCodeAddress); + + // Check well-known global stubs + if (address == ThePreStub) return StubKind.PreStub; + if (address == VarargPInvokeStub || address == VarargPInvokeStub_RetBuffArg + || address == GenericPInvokeCalliHelper) + return StubKind.InteropDispatchStub; + if (address == TailCallJitHelper) return StubKind.TailCallStub; + + // Look up in range section map + RangeSection range = FindRangeSection(jittedCodeAddress); + if (range == null) return StubKind.CodeBlockUnknown; + + JitManager jitManager = GetJitManager(range); + return jitManager.GetStubCodeBlockKind(range, jittedCodeAddress); +} +``` + ### EE JIT Manager and Code Heap Info ```csharp diff --git a/docs/design/datacontracts/PrecodeStubs.md b/docs/design/datacontracts/PrecodeStubs.md index b336b433ac2e13..31be70145bf232 100644 --- a/docs/design/datacontracts/PrecodeStubs.md +++ b/docs/design/datacontracts/PrecodeStubs.md @@ -7,6 +7,13 @@ This contract provides support for examining [precode](../coreclr/botr/method-de ```csharp // Gets a pointer to the MethodDesc for a given stub entrypoint TargetPointer GetMethodDescFromStubAddress(TargetCodePointer entryPoint); + + // Enumerates candidate precode entry points near a given code address. + // The address is pointer-aligned and then iterated backwards in pointer-sized + // steps up to StubPrecodeSize / PointerSize candidates. This is used by + // GetRuntimeNameByAddress to resolve a code address that falls within a + // precode stub back to its entry point. + IEnumerable GetCandidateEntryPoints(TargetCodePointer address); ``` ## Version 1, 2, and 3 @@ -295,4 +302,17 @@ After the initial precode type is determined, for stub precodes a refined precod return precode.GetMethodDesc(_target, MachineDescriptor); } + + IEnumerable IPrecodeStubs.GetCandidateEntryPoints(TargetCodePointer address) + { + TargetPointer instrPointer = CodePointerReadableInstrPointer(address); + ulong aligned = instrPointer.Value & ~(ulong)(PointerSize - 1); + uint count = StubPrecodeSize / PointerSize; + + for (uint i = 0; i < count; i++) + { + TargetPointer candidateAddr = new TargetPointer(aligned - (i * PointerSize)); + yield return CodePointerFromAddress(candidateAddr); + } + } ``` diff --git a/src/coreclr/debug/daccess/daccess.cpp b/src/coreclr/debug/daccess/daccess.cpp index 0c8801da35fe27..12e0ff453b1f02 100644 --- a/src/coreclr/debug/daccess/daccess.cpp +++ b/src/coreclr/debug/daccess/daccess.cpp @@ -5327,7 +5327,7 @@ static int FormatCLRStubName( // Compute the address as a string safely. WCHAR addrString[Max64BitHexString + 1]; - FormatInteger(addrString, ARRAY_SIZE(addrString), "%p", stubAddr); + FormatInteger(addrString, ARRAY_SIZE(addrString), sizeof(void*) == 8 ? "%016llX" : "%08X", stubAddr); size_t addStringLen = u16_strlen(addrString); // Compute maximum length, include the null terminator. diff --git a/src/coreclr/inc/gfunc_list.h b/src/coreclr/inc/gfunc_list.h index 9631099ff92f46..44031c75a12332 100644 --- a/src/coreclr/inc/gfunc_list.h +++ b/src/coreclr/inc/gfunc_list.h @@ -12,6 +12,14 @@ DEFINE_DACGFN(DACNotifyCompilationFinished) DEFINE_DACGFN(ThePreStub) +DEFINE_DACGFN(GenericPInvokeCalliHelper) +DEFINE_DACGFN(VarargPInvokeStub) +#if !defined(TARGET_X86) && !defined(TARGET_ARM64) && !defined(TARGET_LOONGARCH64) && !defined(TARGET_RISCV64) +DEFINE_DACGFN(VarargPInvokeStub_RetBuffArg) +#endif +#if defined(TARGET_X86) && !defined(UNIX_X86_ABI) +DEFINE_DACGFN(JIT_TailCall) +#endif DEFINE_DACGFN(ThePreStubPatchLabel) #ifdef FEATURE_COMINTEROP diff --git a/src/coreclr/vm/codeman.h b/src/coreclr/vm/codeman.h index 668b95beb9ddef..6afa441a9d3839 100644 --- a/src/coreclr/vm/codeman.h +++ b/src/coreclr/vm/codeman.h @@ -793,6 +793,7 @@ template<> struct cdac_data static constexpr size_t Flags = offsetof(RangeSection, _flags); static constexpr size_t HeapList = offsetof(RangeSection, _pHeapList); static constexpr size_t R2RModule = offsetof(RangeSection, _pR2RModule); + static constexpr size_t RangeList = offsetof(RangeSection, _pRangeList); }; enum class RangeSectionLockState diff --git a/src/coreclr/vm/datadescriptor/datadescriptor.inc b/src/coreclr/vm/datadescriptor/datadescriptor.inc index a13b9b275ce31c..1837c8f3e4a8a2 100644 --- a/src/coreclr/vm/datadescriptor/datadescriptor.inc +++ b/src/coreclr/vm/datadescriptor/datadescriptor.inc @@ -805,8 +805,14 @@ CDAC_TYPE_FIELD(RangeSection, T_POINTER, JitManager, cdac_data::Ji CDAC_TYPE_FIELD(RangeSection, T_INT32, Flags, cdac_data::Flags) CDAC_TYPE_FIELD(RangeSection, T_POINTER, HeapList, cdac_data::HeapList) CDAC_TYPE_FIELD(RangeSection, T_POINTER, R2RModule, cdac_data::R2RModule) +CDAC_TYPE_FIELD(RangeSection, T_POINTER, RangeList, cdac_data::RangeList) CDAC_TYPE_END(RangeSection) +CDAC_TYPE_BEGIN(CodeRangeMapRangeList) +CDAC_TYPE_INDETERMINATE(CodeRangeMapRangeList) +CDAC_TYPE_FIELD(CodeRangeMapRangeList, T_INT32, RangeListType, cdac_data::RangeListType) +CDAC_TYPE_END(CodeRangeMapRangeList) + CDAC_TYPE_BEGIN(EEJitManager) CDAC_TYPE_INDETERMINATE(EEJitManager) CDAC_TYPE_FIELD(EEJitManager, T_BOOL, StoreRichDebugInfo, cdac_data::StoreRichDebugInfo) @@ -1412,6 +1418,17 @@ CDAC_GLOBAL(ArrayBaseSize, T_UINT32, ARRAYBASE_BASESIZE) CDAC_GLOBAL(SyncBlockValueToObjectOffset, T_UINT16, OBJHEADER_SIZE - cdac_data::SyncBlockValue) CDAC_GLOBAL(StubCodeBlockLast, T_UINT8, STUB_CODE_BLOCK_LAST) CDAC_GLOBAL(DefaultADID, T_UINT32, DefaultADID) +#ifndef FEATURE_PORTABLE_ENTRYPOINTS +CDAC_GLOBAL_POINTER(ThePreStub, &ThePreStub) +#endif // !FEATURE_PORTABLE_ENTRYPOINTS +CDAC_GLOBAL_POINTER(GenericPInvokeCalliHelper, &GenericPInvokeCalliHelper) +CDAC_GLOBAL_POINTER(VarargPInvokeStub, &VarargPInvokeStub) +#if !defined(TARGET_X86) && !defined(TARGET_ARM64) && !defined(TARGET_LOONGARCH64) && !defined(TARGET_RISCV64) +CDAC_GLOBAL_POINTER(VarargPInvokeStub_RetBuffArg, &VarargPInvokeStub_RetBuffArg) +#endif +#if defined(TARGET_X86) && !defined(UNIX_X86_ABI) +CDAC_GLOBAL_POINTER(TailCallJitHelper, &JIT_TailCall) +#endif // TARGET_X86 && !UNIX_X86_ABI #ifndef TARGET_UNIX CDAC_GLOBAL(SizeOfGenericModeBlock, T_UINT32, sizeof(GenericModeBlock)) #endif diff --git a/src/coreclr/vm/loaderallocator.hpp b/src/coreclr/vm/loaderallocator.hpp index 68c372185b4723..23b2da9157727f 100644 --- a/src/coreclr/vm/loaderallocator.hpp +++ b/src/coreclr/vm/loaderallocator.hpp @@ -182,6 +182,13 @@ class CodeRangeMapRangeList : public RangeList SArray _starts; void* _id; bool _collectible; + friend struct ::cdac_data; +}; + +template<> +struct cdac_data +{ + static constexpr size_t RangeListType = offsetof(CodeRangeMapRangeList, _rangeListType); }; // Iterator over Assemblies in the same ALC diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IExecutionManager.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IExecutionManager.cs index 0dddd31417e5ad..16678ce332e712 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IExecutionManager.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IExecutionManager.cs @@ -46,6 +46,26 @@ public interface ICodeHeapInfo { } +public enum StubKind : uint +{ + CodeBlockUnknown = 0, + CodeBlockJumpStub = 1, + CodeBlockDynamicHelper = 3, + CodeBlockPrecode = 4, + CodeBlockVSDDispatchStub = 5, + CodeBlockVSDResolveStub = 6, + CodeBlockVSDLookupStub = 7, + CodeBlockVSDVTableStub = 8, + CodeBlockCallCounting = 9, + CodeBlockStubLinkStub = 10, + CodeBlockMethodCallThunk = 11, + CodeBlockNoCode = 12, + CodeBlockManaged = 13, + PreStub = 14, + InteropDispatchStub = 15, + TailCallStub = 16, +} + public sealed class LoaderCodeHeapInfo : ICodeHeapInfo { public TargetPointer HeapAddress { get; } @@ -101,6 +121,7 @@ public interface IExecutionManager : IContract List GetExceptionClauses(CodeBlockHandle codeInfoHandle) => throw new NotImplementedException(); JitManagerInfo GetEEJitManagerInfo() => throw new NotImplementedException(); IEnumerable GetCodeHeapInfos() => throw new NotImplementedException(); + StubKind GetStubKind(TargetCodePointer jittedCodeAddress) => throw new NotImplementedException(); } public readonly struct ExecutionManager : IExecutionManager diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IPrecodeStubs.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IPrecodeStubs.cs index aced1d2316dd08..fce6ee7d12a874 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IPrecodeStubs.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IPrecodeStubs.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; +using System.Collections.Generic; namespace Microsoft.Diagnostics.DataContractReader.Contracts; @@ -9,6 +10,7 @@ public interface IPrecodeStubs : IContract { static string IContract.Name { get; } = nameof(PrecodeStubs); TargetPointer GetMethodDescFromStubAddress(TargetCodePointer entryPoint) => throw new NotImplementedException(); + IEnumerable GetCandidateEntryPoints(TargetCodePointer address) => throw new NotImplementedException(); } public readonly struct PrecodeStubs : IPrecodeStubs diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/DataType.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/DataType.cs index 521d3b56783f2d..190e85f07043fe 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/DataType.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/DataType.cs @@ -168,6 +168,7 @@ public enum DataType ComInterfaceEntry, InternalComInterfaceDispatch, AuxiliarySymbolInfo, + CodeRangeMapRangeList, /* GC Data Types */ diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Constants.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Constants.cs index 41eccea4c65d96..56985d783e3173 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Constants.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Constants.cs @@ -82,6 +82,11 @@ public static class Globals public const string ExecutionManagerCodeRangeMapAddress = nameof(ExecutionManagerCodeRangeMapAddress); public const string EEJitManagerAddress = nameof(EEJitManagerAddress); public const string StubCodeBlockLast = nameof(StubCodeBlockLast); + public const string ThePreStub = nameof(ThePreStub); + public const string VarargPInvokeStub = nameof(VarargPInvokeStub); + public const string VarargPInvokeStub_RetBuffArg = nameof(VarargPInvokeStub_RetBuffArg); + public const string GenericPInvokeCalliHelper = nameof(GenericPInvokeCalliHelper); + public const string TailCallJitHelper = nameof(TailCallJitHelper); public const string DefaultADID = nameof(DefaultADID); public const string StaticsPointerMask = nameof(StaticsPointerMask); public const string PtrArrayOffsetToDataArray = nameof(PtrArrayOffsetToDataArray); diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManagerCore.EEJitManager.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManagerCore.EEJitManager.cs index a3e73ed437a098..d0f5abf60856ad 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManagerCore.EEJitManager.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManagerCore.EEJitManager.cs @@ -20,6 +20,20 @@ public EEJitManager(Target target, INibbleMap nibbleMap) : base(target) _runtimeFunctions = RuntimeFunctionLookup.Create(target); } + private enum StubCodeBlockKind : int + { + Unknown = 0, + JumpStub = 1, + DynamicHelper = 3, + StubPrecode = 4, + FixupPrecode = 5, + VSDDispatchStub = 6, + VSDResolveStub = 7, + VSDLookupStub = 8, + VSDVTableStub = 9, + CallCountingStub = 10, + } + public override bool GetMethodInfo(RangeSection rangeSection, TargetCodePointer jittedCodeAddress, [NotNullWhen(true)] out CodeBlock? info) { info = null; @@ -117,6 +131,19 @@ public override TargetPointer GetDebugInfo(RangeSection rangeSection, TargetCode return realCodeHeader.DebugInfo; } + public override StubKind GetStubCodeBlockKind(RangeSection rangeSection, TargetCodePointer jittedCodeAddress) + { + if (rangeSection.IsRangeList) + { + Data.CodeRangeMapRangeList rangeList = Target.ProcessedData.GetOrAdd(rangeSection.Data!.RangeList); + return GetStubKind((StubCodeBlockKind)rangeList.RangeListType); + } + TargetPointer startAddr = FindMethodCode(rangeSection, jittedCodeAddress); // validate that the code address is within the method's code range + if (startAddr == TargetPointer.Null) + return StubKind.CodeBlockNoCode; + return GetCodeHeaderStubKind(rangeSection, startAddr); + } + public override void GetGCInfo(RangeSection rangeSection, TargetCodePointer jittedCodeAddress, out TargetPointer gcInfo, out uint gcVersion) { gcInfo = TargetPointer.Null; @@ -154,9 +181,9 @@ private TargetPointer FindMethodCode(RangeSection rangeSection, TargetCodePointe return _nibbleMap.FindMethodCode(heapListNode, jittedCodeAddress); } - private bool GetRealCodeHeader(RangeSection rangeSection, TargetPointer codeStart, [NotNullWhen(true)] out Data.RealCodeHeader? realCodeHeader) + private bool GetCodeHeaderAddress(RangeSection rangeSection, TargetPointer codeStart, out TargetPointer codeHeaderAddress) { - realCodeHeader = null; + codeHeaderAddress = TargetPointer.Null; // EEJitManager::JitCodeToMethodInfo if (rangeSection.IsRangeList) return false; @@ -170,15 +197,64 @@ private bool GetRealCodeHeader(RangeSection rangeSection, TargetPointer codeStar // See EEJitManager::GetCodeHeaderFromStartAddress in vm/codeman.h int codeHeaderOffset = Target.PointerSize; TargetPointer codeHeaderIndirect = new TargetPointer(codeStart - (ulong)codeHeaderOffset); - if (RangeSection.IsStubCodeBlock(Target, codeHeaderIndirect)) + codeHeaderAddress = Target.ReadPointer(codeHeaderIndirect); + return true; + } + + private bool GetRealCodeHeader(RangeSection rangeSection, TargetPointer codeStart, [NotNullWhen(true)] out Data.RealCodeHeader? realCodeHeader) + { + realCodeHeader = null; + if (!GetCodeHeaderAddress(rangeSection, codeStart, out TargetPointer codeHeaderAddress)) + { + return false; + } + if (RangeSection.IsStubCodeBlock(Target, codeHeaderAddress)) { return false; } - TargetPointer codeHeaderAddress = Target.ReadPointer(codeHeaderIndirect); realCodeHeader = Target.ProcessedData.GetOrAdd(codeHeaderAddress); return true; } + private static StubKind GetStubKind(StubCodeBlockKind stubCodeBlockKind) + { + switch (stubCodeBlockKind) + { + case StubCodeBlockKind.JumpStub: + return StubKind.CodeBlockJumpStub; + case StubCodeBlockKind.DynamicHelper: + return StubKind.CodeBlockDynamicHelper; + case StubCodeBlockKind.StubPrecode: + case StubCodeBlockKind.FixupPrecode: + return StubKind.CodeBlockPrecode; + case StubCodeBlockKind.VSDDispatchStub: + return StubKind.CodeBlockVSDDispatchStub; + case StubCodeBlockKind.VSDResolveStub: + return StubKind.CodeBlockVSDResolveStub; + case StubCodeBlockKind.VSDLookupStub: + return StubKind.CodeBlockVSDLookupStub; + case StubCodeBlockKind.VSDVTableStub: + return StubKind.CodeBlockVSDVTableStub; + case StubCodeBlockKind.CallCountingStub: + return StubKind.CodeBlockCallCounting; + default: + return StubKind.CodeBlockUnknown; + } + } + + private StubKind GetCodeHeaderStubKind(RangeSection rangeSection, TargetPointer codeStart) + { + if (GetCodeHeaderAddress(rangeSection, codeStart, out TargetPointer codeHeaderAddress)) + { + if (RangeSection.IsStubCodeBlock(Target, codeHeaderAddress)) + { + return GetStubKind((StubCodeBlockKind)codeHeaderAddress.Value); + } + return StubKind.CodeBlockManaged; + } + return StubKind.CodeBlockUnknown; + } + public override void GetExceptionClauses(RangeSection rangeSection, CodeBlockHandle codeInfoHandle, out TargetPointer startAddr, out TargetPointer endAddr) { startAddr = TargetPointer.Null; diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManagerCore.ReadyToRunJitManager.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManagerCore.ReadyToRunJitManager.cs index ff05f37b6c6628..af13dd60d354c4 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManagerCore.ReadyToRunJitManager.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManagerCore.ReadyToRunJitManager.cs @@ -142,6 +142,13 @@ public override TargetPointer GetDebugInfo(RangeSection rangeSection, TargetCode return imageBase + debugInfoOffset; } + public override StubKind GetStubCodeBlockKind(RangeSection rangeSection, TargetCodePointer jittedCodeAddress) + { + if (rangeSection.Data == null) + return StubKind.CodeBlockUnknown; + return IsStubCodeBlockThunk(rangeSection.Data, GetReadyToRunInfo(rangeSection), jittedCodeAddress) ? StubKind.CodeBlockMethodCallThunk : StubKind.CodeBlockUnknown; + } + public override void GetGCInfo(RangeSection rangeSection, TargetCodePointer jittedCodeAddress, out TargetPointer gcInfo, out uint gcVersion) { gcInfo = TargetPointer.Null; diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManagerCore.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManagerCore.cs index 43e89fbe2237e7..17e60ce099882d 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManagerCore.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManagerCore.cs @@ -21,6 +21,11 @@ internal sealed partial class ExecutionManagerCore : IExecutionManager private readonly ExecutionManagerHelpers.RangeSectionMap _rangeSectionMapLookup; private readonly EEJitManager _eeJitManager; private readonly ReadyToRunJitManager _r2rJitManager; + private readonly TargetPointer _thePreStub; + private readonly TargetPointer _varargPInvokeStub; + private readonly TargetPointer _varargPInvokeStub_RetBuffArg; + private readonly TargetPointer _genericPInvokeCalliHelper; + private readonly TargetPointer _tailCallJitHelper; public ExecutionManagerCore(Target target, Data.RangeSectionMap topRangeSectionMap) { @@ -30,6 +35,18 @@ public ExecutionManagerCore(Target target, Data.RangeSectionMap topRangeSectionM INibbleMap nibbleMap = T.Create(_target); _eeJitManager = new EEJitManager(_target, nibbleMap); _r2rJitManager = new ReadyToRunJitManager(_target); + _thePreStub = ReadOptionalGlobalPointer(_target, Constants.Globals.ThePreStub); + _varargPInvokeStub = ReadOptionalGlobalPointer(_target, Constants.Globals.VarargPInvokeStub); + _varargPInvokeStub_RetBuffArg = ReadOptionalGlobalPointer(_target, Constants.Globals.VarargPInvokeStub_RetBuffArg); + _genericPInvokeCalliHelper = ReadOptionalGlobalPointer(_target, Constants.Globals.GenericPInvokeCalliHelper); + _tailCallJitHelper = ReadOptionalGlobalPointer(_target, Constants.Globals.TailCallJitHelper); + } + + private static TargetPointer ReadOptionalGlobalPointer(Target target, string name) + { + return target.TryReadGlobalPointer(name, out TargetPointer? ptr) + ? ptr.Value + : TargetPointer.Null; } public void Flush() @@ -99,6 +116,7 @@ public abstract void GetMethodRegionInfo( public abstract TargetPointer GetDebugInfo(RangeSection rangeSection, TargetCodePointer jittedCodeAddress, out bool hasFlagByte); public abstract void GetGCInfo(RangeSection rangeSection, TargetCodePointer jittedCodeAddress, out TargetPointer gcInfo, out uint gcVersion); public abstract void GetExceptionClauses(RangeSection rangeSection, CodeBlockHandle codeInfoHandle, out TargetPointer startAddr, out TargetPointer endAddr); + public abstract StubKind GetStubCodeBlockKind(RangeSection rangeSection, TargetCodePointer jittedCodeAddress); } private sealed class RangeSection @@ -539,4 +557,47 @@ List IExecutionManager.GetExceptionClauses(CodeBlockHandle } return exceptionClauses; } + + private bool TryGetStubKindFromConstants(TargetPointer jittedCodeAddress, out StubKind stubKind) + { + stubKind = StubKind.CodeBlockUnknown; + if (jittedCodeAddress == TargetPointer.Null) + { + return false; + } + if (jittedCodeAddress == _thePreStub) + { + stubKind = StubKind.PreStub; + return true; + } + else if (jittedCodeAddress == _varargPInvokeStub + || jittedCodeAddress == _varargPInvokeStub_RetBuffArg + || jittedCodeAddress == _genericPInvokeCalliHelper) + { + stubKind = StubKind.InteropDispatchStub; + return true; + } + else if (jittedCodeAddress == _tailCallJitHelper) + { + stubKind = StubKind.TailCallStub; + return true; + } + return false; + } + + public StubKind GetStubKind(TargetCodePointer jittedCodeAddress) + { + TargetPointer jittedPtr = CodePointerUtils.AddressFromCodePointer(jittedCodeAddress, _target); + if (TryGetStubKindFromConstants(jittedPtr, out StubKind stubKind)) + { + return stubKind; + } + + RangeSection range = RangeSection.Find(_target, _topRangeSectionMap, _rangeSectionMapLookup, jittedCodeAddress); + if (range.Data == null) + return StubKind.CodeBlockUnknown; + + JitManager jitManager = GetJitManager(range.Data); + return jitManager.GetStubCodeBlockKind(range, jittedCodeAddress); + } } diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManager_1.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManager_1.cs index b636a2914c36ec..c23094f8c88c93 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManager_1.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManager_1.cs @@ -34,5 +34,6 @@ internal ExecutionManager_1(Target target) public List GetExceptionClauses(CodeBlockHandle codeInfoHandle) => _executionManagerCore.GetExceptionClauses(codeInfoHandle); public JitManagerInfo GetEEJitManagerInfo() => _executionManagerCore.GetEEJitManagerInfo(); public IEnumerable GetCodeHeapInfos() => _executionManagerCore.GetCodeHeapInfos(); + public StubKind GetStubKind(TargetCodePointer entryPoint) => _executionManagerCore.GetStubKind(entryPoint); public void Flush() => _executionManagerCore.Flush(); } diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManager_2.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManager_2.cs index 6b84fda982ab5e..ee17e586860b31 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManager_2.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManager_2.cs @@ -34,5 +34,6 @@ internal ExecutionManager_2(Target target) public List GetExceptionClauses(CodeBlockHandle codeInfoHandle) => _executionManagerCore.GetExceptionClauses(codeInfoHandle); public JitManagerInfo GetEEJitManagerInfo() => _executionManagerCore.GetEEJitManagerInfo(); public IEnumerable GetCodeHeapInfos() => _executionManagerCore.GetCodeHeapInfos(); + public StubKind GetStubKind(TargetCodePointer entryPoint) => _executionManagerCore.GetStubKind(entryPoint); public void Flush() => _executionManagerCore.Flush(); } diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/PrecodeStubs_Common.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/PrecodeStubs_Common.cs index c8b76481107e5c..7c3c78067ab5a1 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/PrecodeStubs_Common.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/PrecodeStubs_Common.cs @@ -3,6 +3,7 @@ using System; using System.Diagnostics; +using System.Collections.Generic; using Microsoft.Diagnostics.DataContractReader.Data; namespace Microsoft.Diagnostics.DataContractReader.Contracts; @@ -146,4 +147,21 @@ TargetPointer IPrecodeStubs.GetMethodDescFromStubAddress(TargetCodePointer entry return precode.GetMethodDesc(_target, MachineDescriptor); } + + IEnumerable IPrecodeStubs.GetCandidateEntryPoints(TargetCodePointer address) + { + uint pointerSize = (uint)_target.PointerSize; + TargetPointer instrPointer = CodePointerReadableInstrPointer(address); + ulong aligned = instrPointer.Value & ~(ulong)(pointerSize - 1); + + if (MachineDescriptor.StubPrecodeSize is not byte maxPrecodeSize) + throw new InvalidOperationException("StubPrecodeSize is required to enumerate candidate entry points"); + uint count = maxPrecodeSize / pointerSize; + + for (uint i = 0; i < count; i++) + { + TargetPointer candidateAddr = new TargetPointer(aligned - (i * pointerSize)); + yield return CodePointerUtils.CodePointerFromAddress(candidateAddr, _target); + } + } } diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/CodeRangeMapRangeList.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/CodeRangeMapRangeList.cs new file mode 100644 index 00000000000000..edcefc8f8d5114 --- /dev/null +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/CodeRangeMapRangeList.cs @@ -0,0 +1,18 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.Diagnostics.DataContractReader.Data; + +internal sealed class CodeRangeMapRangeList : IData +{ + static CodeRangeMapRangeList IData.Create(Target target, TargetPointer address) + => new CodeRangeMapRangeList(target, address); + + public CodeRangeMapRangeList(Target target, TargetPointer address) + { + Target.TypeInfo type = target.GetTypeInfo(DataType.CodeRangeMapRangeList); + RangeListType = target.ReadField(address, type, nameof(RangeListType)); + } + + public int RangeListType { get; init; } +} diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/RangeSection.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/RangeSection.cs index cf54d9ddcef9d6..f104ca8df6bae1 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/RangeSection.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/RangeSection.cs @@ -18,6 +18,7 @@ public RangeSection(Target target, TargetPointer address) Flags = target.ReadField(address, type, nameof(Flags)); HeapList = target.ReadPointerField(address, type, nameof(HeapList)); R2RModule = target.ReadPointerField(address, type, nameof(R2RModule)); + RangeList = target.ReadPointerField(address, type, nameof(RangeList)); } public TargetPointer RangeBegin { get; init; } @@ -27,4 +28,5 @@ public RangeSection(Target target, TargetPointer address) public TargetPointer HeapList { get; init; } public int Flags { get; init; } public TargetPointer R2RModule { get; init; } + public TargetPointer RangeList { get; init; } } diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/SOSDacImpl.IXCLRDataProcess.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/SOSDacImpl.IXCLRDataProcess.cs index 590e529ffc480f..1f0e7c9652f455 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/SOSDacImpl.IXCLRDataProcess.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/SOSDacImpl.IXCLRDataProcess.cs @@ -7,6 +7,7 @@ using System.Linq; using System.Runtime.InteropServices; using System.Runtime.InteropServices.Marshalling; +using System.Text; using Microsoft.Diagnostics.DataContractReader.Contracts; using Microsoft.Diagnostics.DataContractReader.Contracts.Extensions; @@ -101,7 +102,186 @@ int IXCLRDataProcess.GetRuntimeNameByAddress( uint* nameLen, char* nameBuf, ClrDataAddress* displacement) - => LegacyFallbackHelper.CanFallback() && _legacyProcess is not null ? _legacyProcess.GetRuntimeNameByAddress(address, flags, bufLen, nameLen, nameBuf, displacement) : HResults.E_NOTIMPL; + { + if (displacement is not null) + *displacement = default; + int hr = HResults.S_OK; + try + { + if (flags != 0) + throw new ArgumentException("Flags are not supported", nameof(flags)); + + TargetCodePointer codeAddr = address.ToTargetCodePointer(_target); + + // IsPossibleCodeAddress - validate the address is readable + if (!_target.TryRead(codeAddr, out byte _)) + throw new ArgumentException("Address is not readable", nameof(address)); + + IExecutionManager eman = _target.Contracts.ExecutionManager; + string? resultName = null; + + // First, try to find it as managed code (equivalent of EECodeInfo path in legacy DAC) + CodeBlockHandle? cbh = eman.GetCodeBlockHandle(codeAddr); + if (cbh is CodeBlockHandle codeBlock) + { + TargetPointer methodDescPtr = eman.GetMethodDesc(codeBlock); + if (displacement is not null) + { + TargetNUInt relOffset = eman.GetRelativeOffset(codeBlock); + *displacement = relOffset.Value; + } + + resultName = GetNameFromMethodDesc(methodDescPtr, address); + } + else + { + // Try stub classification + StubKind stubKind = eman.GetStubKind(codeAddr); + + if (stubKind == StubKind.CodeBlockPrecode) + { + // For precode stubs, resolve the MethodDesc from the precode entry point + IPrecodeStubs precodeStubs = _target.Contracts.PrecodeStubs; + foreach (TargetCodePointer potentialEntryPoint in precodeStubs.GetCandidateEntryPoints(codeAddr)) + { + TargetPointer methodDescPtr = eman.NonVirtualEntry2MethodDesc(potentialEntryPoint); + if (methodDescPtr != TargetPointer.Null) + { + if (displacement is not null) + *displacement = codeAddr.Value - potentialEntryPoint.Value; + + resultName = GetNameFromMethodDesc(methodDescPtr, address); + break; + } + } + resultName ??= FormatCLRStubName(GetStubName(stubKind)!, address); + } + else + { + // Known stub type - format as CLRStub[]@ + string? stubName = GetStubName(stubKind); + + if (stubName is not null) + resultName = FormatCLRStubName(stubName, address); + else if (_target.Contracts.AuxiliarySymbols.TryGetAuxiliarySymbolName(address.ToTargetPointer(_target), out string? auxSymbolName)) + { + resultName = auxSymbolName; + } + } + if (resultName is null) + { + throw new InvalidCastException(); + } + } + + OutputBufferHelpers.CopyStringToBuffer(nameBuf, bufLen, nameLen, resultName); + + if (nameBuf is not null && bufLen < resultName.Length + 1) + hr = HResults.S_FALSE; + } + catch (System.Exception ex) + { + hr = ex.HResult; + } + +#if DEBUG + if (_legacyProcess is not null) + { + uint nameLenLocal = 0; + char[] nameBufLocal = new char[bufLen > 0 ? bufLen : 1]; + ClrDataAddress displacementLocal = default; + int hrLocal; + fixed (char* pNameBufLocal = nameBufLocal) + { + hrLocal = _legacyProcess.GetRuntimeNameByAddress( + address, flags, bufLen, + &nameLenLocal, + nameBuf is null ? null : pNameBufLocal, + displacement is null ? null : &displacementLocal); + } + + Debug.ValidateHResult(hr, hrLocal); + if (hr == HResults.S_OK || hr == HResults.S_FALSE) + { + Debug.Assert(nameLen is null || *nameLen == nameLenLocal); + if (nameBuf is not null) + { + Debug.Assert(new ReadOnlySpan(nameBuf, (int)nameLenLocal) + .SequenceEqual(nameBufLocal.AsSpan(0, (int)nameLenLocal))); + } + if (displacement is not null) + { + Debug.Assert(*displacement == displacementLocal); + } + } + } +#endif + + return hr; + } + + private static string? GetStubName(Contracts.StubKind stubKind) + { + return stubKind switch + { + Contracts.StubKind.CodeBlockJumpStub => "JumpStub", + Contracts.StubKind.CodeBlockPrecode => "MethodDescPrestub", + Contracts.StubKind.CodeBlockVSDDispatchStub => "VSD_DispatchStub", + Contracts.StubKind.CodeBlockVSDResolveStub => "VSD_ResolveStub", + Contracts.StubKind.CodeBlockVSDLookupStub => "VSD_LookupStub", + Contracts.StubKind.CodeBlockVSDVTableStub => "VSD_VTableStub", + Contracts.StubKind.CodeBlockCallCounting => "CallCountingStub", + Contracts.StubKind.CodeBlockStubLinkStub => "StubLinkStub", + Contracts.StubKind.CodeBlockMethodCallThunk => "MethodCallThunk", + Contracts.StubKind.PreStub => "ThePreStub", + Contracts.StubKind.InteropDispatchStub => "InteropDispatchStub", + Contracts.StubKind.TailCallStub => "TailCallStub", + _ => null, + }; + } + + private string GetNameFromMethodDesc(TargetPointer methodDescPtr, ClrDataAddress address) + { + IRuntimeTypeSystem rts = _target.Contracts.RuntimeTypeSystem; + MethodDescHandle mdHandle = rts.GetMethodDescHandle(methodDescPtr); + + // Dynamic methods with no stored signature get formatted as CLRStub@ + if (rts.IsNoMetadataMethod(mdHandle, out _) + && rts.IsStoredSigMethodDesc(mdHandle, out ReadOnlySpan signature) + && signature.IsEmpty) + { + return FormatCLRStubName(null, address); + } + + // Get the full method name + StringBuilder sb = new(); + TypeNameBuilder.AppendMethodInternal( + _target, + sb, + mdHandle, + TypeNameFormat.FormatSignature | + TypeNameFormat.FormatNamespace | + TypeNameFormat.FormatFullInst); + + return sb.ToString(); + } + + private string FormatCLRStubName(string? stubName, ClrDataAddress address) + { + // Format: "CLRStub[]@" or "CLRStub@" + StringBuilder sb = new(); + sb.Append("CLRStub"); + if (!string.IsNullOrEmpty(stubName)) + { + sb.Append('['); + sb.Append(stubName); + sb.Append(']'); + } + sb.Append('@'); + sb.Append(address.Value.ToString($"X{_target.PointerSize * 2}")); + + return sb.ToString(); + } int IXCLRDataProcess.StartEnumAppDomains(ulong* handle) => LegacyFallbackHelper.CanFallback() && _legacyProcess is not null ? _legacyProcess.StartEnumAppDomains(handle) : HResults.E_NOTIMPL; diff --git a/src/native/managed/cdac/tests/ExecutionManager/ExecutionManagerTests.cs b/src/native/managed/cdac/tests/ExecutionManager/ExecutionManagerTests.cs index 7f8b5cff5b5603..a4bf40160359c4 100644 --- a/src/native/managed/cdac/tests/ExecutionManager/ExecutionManagerTests.cs +++ b/src/native/managed/cdac/tests/ExecutionManager/ExecutionManagerTests.cs @@ -29,6 +29,7 @@ public class ExecutionManagerTests [DataType.ReadyToRunInfo] = TargetTestHelpers.CreateTypeInfo(emBuilder.ReadyToRunInfoLayout), [DataType.EEJitManager] = TargetTestHelpers.CreateTypeInfo(emBuilder.EEJitManagerLayout), [DataType.Module] = TargetTestHelpers.CreateTypeInfo(emBuilder.ModuleLayout), + [DataType.CodeRangeMapRangeList] = TargetTestHelpers.CreateTypeInfo(emBuilder.CodeRangeMapRangeListLayout), [DataType.HashMap] = TargetTestHelpers.CreateTypeInfo(MockHashMap.CreateLayout(helpers.Arch)), [DataType.Bucket] = TargetTestHelpers.CreateTypeInfo(MockHashMapBucket.CreateLayout(helpers.Arch)), }; @@ -655,6 +656,105 @@ public void GetCodeHeapList_LinkedList_TwoNodes(string version, MockTarget.Archi Assert.Equal(currentAddr, hostInfo.CurrentAddress); } + [Theory] + [MemberData(nameof(StdArchAllVersions))] + public void TryGetStubKind_GlobalConstants(string version, MockTarget.Architecture arch) + { + IExecutionManager em = CreateExecutionManagerContract(version, arch); + + Assert.Equal(StubKind.PreStub, em.TryGetStubKind(new TargetCodePointer(0x00aa_1000))); + Assert.Equal(StubKind.InteropDispatchStub, em.TryGetStubKind(new TargetCodePointer(0x00aa_2000))); + Assert.Equal(StubKind.InteropDispatchStub, em.TryGetStubKind(new TargetCodePointer(0x00aa_3000))); + Assert.Equal(StubKind.InteropDispatchStub, em.TryGetStubKind(new TargetCodePointer(0x00aa_4000))); + Assert.Equal(StubKind.TailCallStub, em.TryGetStubKind(new TargetCodePointer(0x00aa_5000))); + Assert.Equal(StubKind.CodeBlockUnknown, em.TryGetStubKind(new TargetCodePointer(0x00aa_9000))); + } + + [Theory] + [MemberData(nameof(StdArchAllVersions))] + public void TryGetStubKind_RangeListStubs(string version, MockTarget.Architecture arch) + { + const ulong codeRangeStart = 0x0a0a_0000u; + const uint codeRangeSize = 0x4000u; + const ulong jitManagerAddress = 0x000b_ff00; + const int stubCodeBlockKindPrecode = 4; // STUB_CODE_BLOCK_STUBPRECODE + + IExecutionManager em = CreateExecutionManagerContract( + version, + arch, + emBuilder => + { + var jittedCode = emBuilder.AllocateJittedCodeRange(codeRangeStart, codeRangeSize); + MockRangeSection rangeSection = emBuilder.AddRangeListRangeSection(jittedCode, jitManagerAddress, stubCodeBlockKindPrecode); + _ = emBuilder.AddRangeSectionFragment(jittedCode, rangeSection.Address); + }); + + StubKind kind = em.TryGetStubKind(new TargetCodePointer(codeRangeStart + 0x100)); + Assert.Equal(StubKind.CodeBlockPrecode, kind); + } + + [Theory] + [MemberData(nameof(StdArchAllVersions))] + public void TryGetStubKind_CodeHeapStubs(string version, MockTarget.Architecture arch) + { + const ulong codeRangeStart = 0x0a0a_0000u; + const uint codeRangeSize = 0xc000u; + const uint stubSize = 0x20; + const ulong jitManagerAddress = 0x000b_ff00; + const int stubCodeBlockKindJumpStub = 1; // STUB_CODE_BLOCK_JUMPSTUB + ulong stubCodeAddress = 0; + + IExecutionManager em = CreateExecutionManagerContract( + version, + arch, + emBuilder => + { + var jittedCode = emBuilder.AllocateJittedCodeRange(codeRangeStart, codeRangeSize); + stubCodeAddress = emBuilder.AddStubCodeBlock(jittedCode, stubSize, stubCodeBlockKindJumpStub).CodeAddress; + + NibbleMapTestBuilderBase nibBuilder = emBuilder.CreateNibbleMap(codeRangeStart, codeRangeSize); + nibBuilder.AllocateCodeChunk(new TargetCodePointer(stubCodeAddress), stubSize); + + MockCodeHeapListNode codeHeapListNode = emBuilder.AddCodeHeapListNode(0, codeRangeStart, codeRangeStart + codeRangeSize, codeRangeStart, nibBuilder.NibbleMapFragment.Address); + MockRangeSection rangeSection = emBuilder.AddRangeSection(jittedCode, jitManagerAddress, codeHeapListNode.Address); + _ = emBuilder.AddRangeSectionFragment(jittedCode, rangeSection.Address); + }); + + StubKind kind = em.TryGetStubKind(new TargetCodePointer(stubCodeAddress)); + Assert.Equal(StubKind.CodeBlockJumpStub, kind); + } + + [Theory] + [MemberData(nameof(StdArchAllVersions))] + public void TryGetStubKind_ManagedCode(string version, MockTarget.Architecture arch) + { + const ulong codeRangeStart = 0x0a0a_0000u; + const uint codeRangeSize = 0xc000u; + const uint methodSize = 0x450; + const ulong jitManagerAddress = 0x000b_ff00; + const ulong expectedMethodDescAddress = 0x0101_aaa0; + ulong methodStart = 0; + + IExecutionManager em = CreateExecutionManagerContract( + version, + arch, + emBuilder => + { + var jittedCode = emBuilder.AllocateJittedCodeRange(codeRangeStart, codeRangeSize); + methodStart = emBuilder.AddJittedMethod(jittedCode, methodSize, expectedMethodDescAddress).CodeAddress; + + NibbleMapTestBuilderBase nibBuilder = emBuilder.CreateNibbleMap(codeRangeStart, codeRangeSize); + nibBuilder.AllocateCodeChunk(new TargetCodePointer(methodStart), methodSize); + + MockCodeHeapListNode codeHeapListNode = emBuilder.AddCodeHeapListNode(0, codeRangeStart, codeRangeStart + codeRangeSize, codeRangeStart, nibBuilder.NibbleMapFragment.Address); + MockRangeSection rangeSection = emBuilder.AddRangeSection(jittedCode, jitManagerAddress, codeHeapListNode.Address); + _ = emBuilder.AddRangeSectionFragment(jittedCode, rangeSection.Address); + }); + + StubKind kind = em.TryGetStubKind(new TargetCodePointer(methodStart)); + Assert.Equal(StubKind.CodeBlockManaged, kind); + } + public static IEnumerable StdArchAllVersions() { const int highestVersion = 2; diff --git a/src/native/managed/cdac/tests/MockDescriptors/MockDescriptors.ExecutionManager.cs b/src/native/managed/cdac/tests/MockDescriptors/MockDescriptors.ExecutionManager.cs index c72126d962f939..772477d988b8e1 100644 --- a/src/native/managed/cdac/tests/MockDescriptors/MockDescriptors.ExecutionManager.cs +++ b/src/native/managed/cdac/tests/MockDescriptors/MockDescriptors.ExecutionManager.cs @@ -99,6 +99,7 @@ internal sealed class MockRangeSection : TypedView private const string FlagsFieldName = "Flags"; private const string HeapListFieldName = "HeapList"; private const string R2RModuleFieldName = "R2RModule"; + private const string RangeListFieldName = "RangeList"; public static Layout CreateLayout(MockTarget.Architecture architecture) => new SequentialLayoutBuilder("RangeSection", architecture) @@ -109,6 +110,7 @@ public static Layout CreateLayout(MockTarget.Architecture arch .AddUInt32Field(FlagsFieldName) .AddPointerField(HeapListFieldName) .AddPointerField(R2RModuleFieldName) + .AddPointerField(RangeListFieldName) .Build(); public ulong RangeBegin @@ -146,6 +148,28 @@ public ulong R2RModule get => ReadPointerField(R2RModuleFieldName); set => WritePointerField(R2RModuleFieldName, value); } + + public ulong RangeList + { + get => ReadPointerField(RangeListFieldName); + set => WritePointerField(RangeListFieldName, value); + } +} + +internal sealed class MockCodeRangeMapRangeList : TypedView +{ + private const string RangeListTypeFieldName = "RangeListType"; + + public static Layout CreateLayout(MockTarget.Architecture architecture) + => new SequentialLayoutBuilder("CodeRangeMapRangeList", architecture) + .AddUInt32Field(RangeListTypeFieldName) + .Build(); + + public int RangeListType + { + get => (int)ReadUInt32Field(RangeListTypeFieldName); + set => WriteUInt32Field(RangeListTypeFieldName, (uint)value); + } } internal sealed class MockCodeHeapListNode : TypedView @@ -442,6 +466,7 @@ public Memory CodeBytes internal sealed class MockExecutionManagerBuilder { private const uint CodeHeapRangeSectionFlag = 0x02; + private const uint RangeListRangeSectionFlag = 0x04; private const string EEJitManagerGlobalName = "EEJitManagerGlobalPointer"; private const int RangeSectionMapBitsPerLevel = 8; @@ -501,6 +526,7 @@ internal readonly struct JittedCodeRange internal Layout ReadyToRunInfoLayout { get; } internal Layout EEJitManagerLayout { get; } internal Layout ModuleLayout { get; } + internal Layout CodeRangeMapRangeListLayout { get; } internal Layout RuntimeFunctionLayout => _runtimeFunctions.RuntimeFunctionLayout; internal Layout UnwindInfoLayout => _runtimeFunctions.UnwindInfoLayout; internal (string Name, ulong Value)[] Globals { get; } @@ -555,6 +581,7 @@ internal MockExecutionManagerBuilder(string version, MockMemorySpace.Builder bui ReadyToRunInfoLayout = MockReadyToRunInfo.CreateLayout(architecture, hashMapStride); EEJitManagerLayout = MockEEJitManager.CreateLayout(architecture); ModuleLayout = MockLoaderModule.CreateLayout(architecture); + CodeRangeMapRangeListLayout = MockCodeRangeMapRangeList.CreateLayout(architecture); _eeJitManager = AllocateAndCreate(EEJitManagerLayout, "EEJitManager"); _eeJitManager.AllCodeHeaps = allCodeHeaps; @@ -569,6 +596,11 @@ internal MockExecutionManagerBuilder(string version, MockMemorySpace.Builder bui }; globals.Add((nameof(Constants.Globals.HashMapSlotsPerBucket), MockHashMapBucket.SlotsPerBucket)); globals.Add((nameof(Constants.Globals.HashMapValueMask), Builder.TargetTestHelpers.MaxSignedTargetAddress)); + globals.Add((nameof(Constants.Globals.ThePreStub), 0x00aa_1000)); + globals.Add((nameof(Constants.Globals.GenericPInvokeCalliHelper), 0x00aa_2000)); + globals.Add((nameof(Constants.Globals.VarargPInvokeStub), 0x00aa_3000)); + globals.Add((nameof(Constants.Globals.VarargPInvokeStub_RetBuffArg), 0x00aa_4000)); + globals.Add((nameof(Constants.Globals.TailCallJitHelper), 0x00aa_5000)); Globals = [.. globals]; } @@ -614,6 +646,27 @@ public MockRangeSection AddReadyToRunRangeSection(JittedCodeRange jittedCodeRang return rangeSection; } + public MockRangeSection AddRangeListRangeSection(JittedCodeRange jittedCodeRange, ulong jitManagerAddress, int rangeListType) + { + MockCodeRangeMapRangeList rangeList = AllocateAndCreate(CodeRangeMapRangeListLayout, "CodeRangeMapRangeList"); + rangeList.RangeListType = rangeListType; + + MockRangeSection rangeSection = AllocateAndCreate(RangeSectionLayout, "RangeSection (RangeList)", _rangeSectionMapAllocator); + rangeSection.RangeBegin = jittedCodeRange.RangeStart; + rangeSection.RangeEndOpen = jittedCodeRange.RangeEnd; + rangeSection.Flags = RangeListRangeSectionFlag; + rangeSection.RangeList = rangeList.Address; + rangeSection.JitManager = jitManagerAddress; + return rangeSection; + } + + public MockJittedMethod AddStubCodeBlock(JittedCodeRange jittedCodeRange, uint codeSize, int stubCodeBlockKind) + { + MockJittedMethod stub = AllocateJittedMethod(jittedCodeRange, codeSize, "Stub Code Block"); + stub.CodeHeader = (ulong)stubCodeBlockKind; + return stub; + } + public MockRangeSectionFragment AddRangeSectionFragment(JittedCodeRange jittedCodeRange, ulong rangeSectionAddress) => AddRangeSectionFragment(jittedCodeRange, rangeSectionAddress, insertIntoMap: true); From 1b691c22cbfc5ea686a5a8ad353e2da9eb95c5b9 Mon Sep 17 00:00:00 2001 From: Rachel Date: Sun, 19 Apr 2026 22:50:45 -0700 Subject: [PATCH 2/4] Update PrecodeStubs.md --- docs/design/datacontracts/PrecodeStubs.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/docs/design/datacontracts/PrecodeStubs.md b/docs/design/datacontracts/PrecodeStubs.md index 31be70145bf232..a664e3b759a2ea 100644 --- a/docs/design/datacontracts/PrecodeStubs.md +++ b/docs/design/datacontracts/PrecodeStubs.md @@ -9,9 +9,7 @@ This contract provides support for examining [precode](../coreclr/botr/method-de TargetPointer GetMethodDescFromStubAddress(TargetCodePointer entryPoint); // Enumerates candidate precode entry points near a given code address. - // The address is pointer-aligned and then iterated backwards in pointer-sized - // steps up to StubPrecodeSize / PointerSize candidates. This is used by - // GetRuntimeNameByAddress to resolve a code address that falls within a + // This is used to resolve a code address that falls within a // precode stub back to its entry point. IEnumerable GetCandidateEntryPoints(TargetCodePointer address); ``` From f01a100c17687f07304008db394b8f44e5cd50db Mon Sep 17 00:00:00 2001 From: Rachel Jarvi Date: Sun, 19 Apr 2026 23:12:53 -0700 Subject: [PATCH 3/4] Update src/native/managed/cdac/tests/ExecutionManager/ExecutionManagerTests.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../cdac/tests/ExecutionManager/ExecutionManagerTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/native/managed/cdac/tests/ExecutionManager/ExecutionManagerTests.cs b/src/native/managed/cdac/tests/ExecutionManager/ExecutionManagerTests.cs index a4bf40160359c4..7f1beb7c2cee25 100644 --- a/src/native/managed/cdac/tests/ExecutionManager/ExecutionManagerTests.cs +++ b/src/native/managed/cdac/tests/ExecutionManager/ExecutionManagerTests.cs @@ -751,7 +751,7 @@ public void TryGetStubKind_ManagedCode(string version, MockTarget.Architecture a _ = emBuilder.AddRangeSectionFragment(jittedCode, rangeSection.Address); }); - StubKind kind = em.TryGetStubKind(new TargetCodePointer(methodStart)); + StubKind kind = em.GetStubKind(new TargetCodePointer(methodStart)); Assert.Equal(StubKind.CodeBlockManaged, kind); } From 71221612889603c973fd7b077d19d76d30eb8287 Mon Sep 17 00:00:00 2001 From: rcj1 Date: Mon, 20 Apr 2026 10:36:36 -0700 Subject: [PATCH 4/4] fix build and comments --- src/coreclr/vm/cgensys.h | 7 +++++++ src/coreclr/vm/class.h | 3 +++ src/coreclr/vm/datadescriptor/datadescriptor.inc | 10 +++++----- src/coreclr/vm/jitinterface.h | 4 ++++ .../ExecutionManager/ExecutionManagerCore.cs | 7 ++++--- .../Contracts/PrecodeStubs_Common.cs | 5 ++++- .../Data/PrecodeMachineDescriptor.cs | 6 +++--- .../ExecutionManager/ExecutionManagerTests.cs | 16 ++++++++-------- .../MockDescriptors.ExecutionManager.cs | 10 +++++----- 9 files changed, 43 insertions(+), 25 deletions(-) diff --git a/src/coreclr/vm/cgensys.h b/src/coreclr/vm/cgensys.h index 59c451b0a90ba7..38e82565cead5c 100644 --- a/src/coreclr/vm/cgensys.h +++ b/src/coreclr/vm/cgensys.h @@ -56,6 +56,13 @@ extern "C" void STDCALL GenericPInvokeCalliHelper(void); extern "C" PCODE STDCALL ExternalMethodFixupWorker(TransitionBlock * pTransitionBlock, TADDR pIndirection, DWORD sectionIndex, Module * pModule); +// cDAC-visible TADDR wrappers for function entrypoints. +inline const TADDR g_cdacGenericPInvokeCalliHelper = (TADDR)(void*)GenericPInvokeCalliHelper; +inline const TADDR g_cdacVarargPInvokeStub = (TADDR)(void*)VarargPInvokeStub; +#if !defined(TARGET_X86) && !defined(TARGET_ARM64) && !defined(TARGET_LOONGARCH64) && !defined(TARGET_RISCV64) +inline const TADDR g_cdacVarargPInvokeStub_RetBuffArg = (TADDR)(void*)VarargPInvokeStub_RetBuffArg; +#endif + extern "C" void STDCALL VirtualMethodFixupStub(void); extern "C" void STDCALL VirtualMethodFixupPatchLabel(void); diff --git a/src/coreclr/vm/class.h b/src/coreclr/vm/class.h index 13d1ee37a8d3cd..917394985901ed 100644 --- a/src/coreclr/vm/class.h +++ b/src/coreclr/vm/class.h @@ -1955,6 +1955,9 @@ inline BOOL EEClass::IsInt128OrHasInt128Fields() VOID InitPreStubManager(); EXTERN_C void STDCALL ThePreStub(); +#ifndef FEATURE_PORTABLE_ENTRYPOINTS +inline const TADDR g_cdacThePreStub = (TADDR)(void*)ThePreStub; +#endif // !FEATURE_PORTABLE_ENTRYPOINTS inline PCODE GetPreStubEntryPoint() { diff --git a/src/coreclr/vm/datadescriptor/datadescriptor.inc b/src/coreclr/vm/datadescriptor/datadescriptor.inc index 1837c8f3e4a8a2..dd0ab088a5f58b 100644 --- a/src/coreclr/vm/datadescriptor/datadescriptor.inc +++ b/src/coreclr/vm/datadescriptor/datadescriptor.inc @@ -1419,15 +1419,15 @@ CDAC_GLOBAL(SyncBlockValueToObjectOffset, T_UINT16, OBJHEADER_SIZE - cdac_data IPrecodeStubs.GetCandidateEntryPoints(TargetCodeP for (uint i = 0; i < count; i++) { - TargetPointer candidateAddr = new TargetPointer(aligned - (i * pointerSize)); + ulong offset = i * pointerSize; + if (aligned < offset) + yield break; + TargetPointer candidateAddr = new TargetPointer(aligned - offset); yield return CodePointerUtils.CodePointerFromAddress(candidateAddr, _target); } } diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/PrecodeMachineDescriptor.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/PrecodeMachineDescriptor.cs index 840059e1511c95..4413f639c3f02c 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/PrecodeMachineDescriptor.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/PrecodeMachineDescriptor.cs @@ -43,7 +43,7 @@ public PrecodeMachineDescriptor(Target target, TargetPointer address) if (type.Fields.ContainsKey(nameof(StubPrecodeSize))) { - StubPrecodeSize = target.ReadField(address, type, nameof(FixupStubPrecodeSize)); + StubPrecodeSize = target.ReadField(address, type, nameof(StubPrecodeSize)); StubBytes = new byte[StubPrecodeSize.Value]; target.ReadBuffer(address + (ulong)type.Fields[nameof(StubBytes)].Offset, StubBytes); StubIgnoredBytes = new byte[StubPrecodeSize.Value]; @@ -52,8 +52,8 @@ public PrecodeMachineDescriptor(Target target, TargetPointer address) else { StubPrecodeSize = null; - FixupBytes = null; - FixupIgnoredBytes = null; + StubBytes = null; + StubIgnoredBytes = null; } PInvokeImportPrecodeType = MaybeGetPrecodeType(target, address, nameof(PInvokeImportPrecodeType)); diff --git a/src/native/managed/cdac/tests/ExecutionManager/ExecutionManagerTests.cs b/src/native/managed/cdac/tests/ExecutionManager/ExecutionManagerTests.cs index 7f1beb7c2cee25..3effcbbe31190f 100644 --- a/src/native/managed/cdac/tests/ExecutionManager/ExecutionManagerTests.cs +++ b/src/native/managed/cdac/tests/ExecutionManager/ExecutionManagerTests.cs @@ -662,12 +662,12 @@ public void TryGetStubKind_GlobalConstants(string version, MockTarget.Architectu { IExecutionManager em = CreateExecutionManagerContract(version, arch); - Assert.Equal(StubKind.PreStub, em.TryGetStubKind(new TargetCodePointer(0x00aa_1000))); - Assert.Equal(StubKind.InteropDispatchStub, em.TryGetStubKind(new TargetCodePointer(0x00aa_2000))); - Assert.Equal(StubKind.InteropDispatchStub, em.TryGetStubKind(new TargetCodePointer(0x00aa_3000))); - Assert.Equal(StubKind.InteropDispatchStub, em.TryGetStubKind(new TargetCodePointer(0x00aa_4000))); - Assert.Equal(StubKind.TailCallStub, em.TryGetStubKind(new TargetCodePointer(0x00aa_5000))); - Assert.Equal(StubKind.CodeBlockUnknown, em.TryGetStubKind(new TargetCodePointer(0x00aa_9000))); + Assert.Equal(StubKind.PreStub, em.GetStubKind(new TargetCodePointer(0x00aa_1000))); + Assert.Equal(StubKind.InteropDispatchStub, em.GetStubKind(new TargetCodePointer(0x00aa_2000))); + Assert.Equal(StubKind.InteropDispatchStub, em.GetStubKind(new TargetCodePointer(0x00aa_3000))); + Assert.Equal(StubKind.InteropDispatchStub, em.GetStubKind(new TargetCodePointer(0x00aa_4000))); + Assert.Equal(StubKind.TailCallStub, em.GetStubKind(new TargetCodePointer(0x00aa_5000))); + Assert.Equal(StubKind.CodeBlockUnknown, em.GetStubKind(new TargetCodePointer(0x00aa_9000))); } [Theory] @@ -689,7 +689,7 @@ public void TryGetStubKind_RangeListStubs(string version, MockTarget.Architectur _ = emBuilder.AddRangeSectionFragment(jittedCode, rangeSection.Address); }); - StubKind kind = em.TryGetStubKind(new TargetCodePointer(codeRangeStart + 0x100)); + StubKind kind = em.GetStubKind(new TargetCodePointer(codeRangeStart + 0x100)); Assert.Equal(StubKind.CodeBlockPrecode, kind); } @@ -720,7 +720,7 @@ public void TryGetStubKind_CodeHeapStubs(string version, MockTarget.Architecture _ = emBuilder.AddRangeSectionFragment(jittedCode, rangeSection.Address); }); - StubKind kind = em.TryGetStubKind(new TargetCodePointer(stubCodeAddress)); + StubKind kind = em.GetStubKind(new TargetCodePointer(stubCodeAddress)); Assert.Equal(StubKind.CodeBlockJumpStub, kind); } diff --git a/src/native/managed/cdac/tests/MockDescriptors/MockDescriptors.ExecutionManager.cs b/src/native/managed/cdac/tests/MockDescriptors/MockDescriptors.ExecutionManager.cs index 772477d988b8e1..8ae48812d16e1f 100644 --- a/src/native/managed/cdac/tests/MockDescriptors/MockDescriptors.ExecutionManager.cs +++ b/src/native/managed/cdac/tests/MockDescriptors/MockDescriptors.ExecutionManager.cs @@ -596,11 +596,11 @@ internal MockExecutionManagerBuilder(string version, MockMemorySpace.Builder bui }; globals.Add((nameof(Constants.Globals.HashMapSlotsPerBucket), MockHashMapBucket.SlotsPerBucket)); globals.Add((nameof(Constants.Globals.HashMapValueMask), Builder.TargetTestHelpers.MaxSignedTargetAddress)); - globals.Add((nameof(Constants.Globals.ThePreStub), 0x00aa_1000)); - globals.Add((nameof(Constants.Globals.GenericPInvokeCalliHelper), 0x00aa_2000)); - globals.Add((nameof(Constants.Globals.VarargPInvokeStub), 0x00aa_3000)); - globals.Add((nameof(Constants.Globals.VarargPInvokeStub_RetBuffArg), 0x00aa_4000)); - globals.Add((nameof(Constants.Globals.TailCallJitHelper), 0x00aa_5000)); + globals.Add((nameof(Constants.Globals.ThePreStub), AddPointerGlobal(0x00aa_1000, "ThePreStub"))); + globals.Add((nameof(Constants.Globals.GenericPInvokeCalliHelper), AddPointerGlobal(0x00aa_2000, "GenericPInvokeCalliHelper"))); + globals.Add((nameof(Constants.Globals.VarargPInvokeStub), AddPointerGlobal(0x00aa_3000, "VarargPInvokeStub"))); + globals.Add((nameof(Constants.Globals.VarargPInvokeStub_RetBuffArg), AddPointerGlobal(0x00aa_4000, "VarargPInvokeStub_RetBuffArg"))); + globals.Add((nameof(Constants.Globals.TailCallJitHelper), AddPointerGlobal(0x00aa_5000, "TailCallJitHelper"))); Globals = [.. globals]; }