Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
149 changes: 148 additions & 1 deletion docs/design/datacontracts/ExecutionManager.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,13 @@ struct CodeBlockHandle
TargetCodePointer GetStartAddress(CodeBlockHandle codeInfoHandle);
// Get the instruction pointer address of the start of the funclet containing the code block
TargetCodePointer GetFuncletStartAddress(CodeBlockHandle codeInfoHandle);
// Get the method region info (hot and cold code size, and cold code start address)
void GetMethodRegionInfo(CodeBlockHandle codeInfoHandle, out uint hotSize, out TargetPointer coldStart, out uint coldSize);
// Get the JIT type
uint GetJITType(CodeBlockHandle codeInfoHandle);
// Attempt to get the method desc of an entrypoint
TargetPointer NonVirtualEntry2MethodDesc(TargetCodePointer entrypoint);

// Gets the unwind info of the code block at the specified code pointer
TargetPointer GetUnwindInfo(CodeBlockHandle codeInfoHandle);
// Gets the base address the UnwindInfo of codeInfoHandle is relative to
Expand Down Expand Up @@ -96,6 +103,9 @@ Data descriptors used:
| `Bucket` | `Keys` | Array of keys of `HashMapSlotsPerBucket` length |
| `Bucket` | `Values` | Array of values of `HashMapSlotsPerBucket` length |
| `UnwindInfo` | `FunctionLength` | Length of the associated function in bytes. Only exists on some platforms |
| `UnwindInfo` | `CountOfUnwindCodes` | Number of unwind codes in the unwind info. Only exists on some platforms |
| `UnwindInfo` | `UnwindCodeOffset` | Offset of UnwindCodeOffset in the UnwindInfo data struct. Only exists on some platforms |
| `PortableEntryPoint` | `MethodDesc` | Method desc of portable entrypoint (only defined if `FeaturePortableEntrypoints` is enabled) |

Global variables used:
| Global Name | Type | Purpose |
Expand All @@ -107,6 +117,7 @@ Global variables used:
| `FeatureEHFunclets` | uint8 | 1 if EH funclets are enabled, 0 otherwise |
| `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 |

Contracts used:
| Contract Name |
Expand Down Expand Up @@ -220,6 +231,142 @@ bool GetMethodInfo(TargetPointer rangeSection, TargetCodePointer jittedCodeAddre
}
```

The EE JitManager `GetMethodRegionInfo` sums up the lengths of each runtime function in a given method, attempting to find the runtime function length in several ways depending on the data availale on the platform.

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

TargetPointer start = // look up jittedCodeAddress in nibble map for rangeSection - see NibbleMap below
if (start == TargetPointer.Null)
return false;

TargetNUInt relativeOffset = jittedCodeAddress - start;
int codeHeaderOffset = Target.PointerSize;
TargetPointer codeHeaderIndirect = start - codeHeaderOffset;

// Check if address is in a stub code block
if (codeHeaderIndirect < Target.ReadGlobal<byte>("StubCodeBlockLast"))
return false;

TargetPointer codeHeaderAddress = Target.ReadPointer(codeHeaderIndirect);
uint numUnwindInfos = Target.ReadPointer(codeHeaderAddress + /* RealCodeHeader::NumUnwindInfos offset */);

if (numUnwindInfos == 0)
{
return;
}
TypeInfo type = target.GetTypeInfo(DataType.RuntimeFunction);
TypeInfo unwindType = target.GetTypeInfo(DataType.RuntimeFunction);

// Sum up the lengths of all the runtime functions to get the hot size
for (uint i = 0; i < numUnwindInfos; i++)
{
TargetPointer addr = runtimeFunctions + (index * type.Size!.Value);
if (/* function has end address */)
hotSize += target.Read<uint>(addr + /* RuntimeFunction::EndAddress offset */) - target.Read<uint>(addr + /* RuntimeFunction::BeginAddress offset */)

Data.UnwindInfo unwindInfo = _target.ProcessedData.GetOrAdd<Data.UnwindInfo>(function.UnwindData);
TargetPointer unwindAddr = target.Read<uint>(addr + /* RuntimeFunction::UnwindData offset */);

if (/* unwind datatype has function length */)
hotSize += target.Read<uint>(unwindAddr + /* UnwindInfo::FunctionLength offset */)

// First 18 bits are function length / (pointer size / 2).
// See UnwindFragmentInfo::Finalize
uint funcLengthInHeader = target.Read<uint>(unwindAddr + /* UnwindInfo::Header offset */) & ((1 << 18) - 1);
hotSize += (uint)(funcLengthInHeader * (_target.PointerSize / 2));
}
return hotSize;
}
```

The R2R JitManager `GetMethodRegionInfo` has different behavior depending on whether the method was found in the hot cold map. If it was found, we find the bounds of the hot and cold regions by finding the start indices of the next hot and cold regions. If it is not in the map, we simply add the lengths of the runtime functions as shown above.
```csharp
public override void GetMethodRegionInfo(TargetPointer rangeSection, TargetCodePointer jittedCodeAddress, out uint hotSize, out TargetPointer coldStart, out uint coldSize)
{
hotSize = 0;
coldSize = 0;
coldStart = TargetPointer.Null;

info = default;

TargetPointer r2rModule = Target.ReadPointer(/* range section address + RangeSection::R2RModule offset */);
TargetPointer r2rInfo = Target.ReadPointer(r2rModule + /* Module::ReadyToRunInfo offset */);

// Check if address is in a thunk
if (/* jittedCodeAddress is in ReadyToRunInfo::DelayLoadMethodCallThunks */)
return false;

// Find the relative address that we are looking for
TargetCodePointer addr = /* code pointer from jittedCodeAddress using PlatformMetadata.GetCodePointerFlags */
TargetPointer imageBase = Target.ReadPointer(/* range section address + RangeSection::RangeBegin offset */);
TargetPointer relativeAddr = addr - imageBase;

TargetPointer runtimeFunctions = Target.ReadPointer(r2rInfo + /* ReadyToRunInfo::RuntimeFunctions offset */);
int index = // Iterate through runtimeFunctions and find index of function with relativeAddress

// look up hot and cold start and end indices in hot/cold map (hotIdx, coldStartIdx, coldEndIdx)
if (/* found in hot/cold map */)
{
hotSize = /* runtime function length at index hotIdx */ ;
// Sum up the lengths of all the runtime functions to get the cold size
for (uint i = coldStartIdx; i <= coldEndIdx; i++)
{
coldSize += // add up lengths of runtime functions at index i as shown above in the EEJitManager
}
coldStart = imageBase + /* start address of runtime function at index coldStartIdx */;
}
else
{
// No hot/cold splitting for this method, sum up the hot size only
uint hotStartIdx = // iterate through until you reach a valid MethodDesc signifying beginning of hot area
uint hotEndIdx = // iterate through until you reach a valid MethodDesc signifying beginning of next hot area
for (uint i = hotStartIdx; i <= hotEndIdx; i++)
{
hotSize += // add up lengths of runtime functions at index i as shown above in the EEJitManager
}
}
}

```

`GetJitType` returns the JIT type by finding the JIT manager for the data range containing the relevant code block. We return TYPE_JIT for the EEJitManager, TYPE_R2R for the R2RJitManager, and TYPE_UNKNOWN for any other value.
```csharp
private enum JITTypes
{
TYPE_UNKNOWN = 0,
TYPE_JIT = 1,
TYPE_R2R = 2,
TYPE_INTERPRETER = 3
};
```
`NonVirtualEntry2MethodDesc` attempts to find a method desc from an entrypoint. If portable entrypoints are enabled, we attempt to read the entrypoint data structure to find the method table. We also attempt to find the method desc from a precode stub. Finally, we attempt to find the method desc using `GetMethodInfo` as described above.
```csharp
TargetPointer IExecutionManager.NonVirtualEntry2MethodDesc(TargetCodePointer entrypoint)
{
TargetPointer rangeSection = // find range section corresponding to jittedCodeAddress - see RangeSectionMap
if (/* no corresponding range section */)
return null;

if (/* range flags indicate RangeList */)
Copy link
Contributor Author

@rcj1 rcj1 Sep 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@elinor-fung @jkotas any way to simplify this, can these checks be absorbed into GetMethodDescFromStubAddress?

In any case I think we need the capability to turn this additional search on and off because SOS uses the failure case of GetMethodDescPtrFromIP to indicate some things about the type of method we are looking at. Here is an (undocumented) example but there are more:

https://github.com/dotnet/diagnostics/blob/23d4e5f6eadc48f12ce8cda6174e3a8b85e3c638/src/SOS/Strike/strike.cpp#L9262

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we need to have two different non-legacy methods:

  • API that takes IP that points to the start or into middle of an actual method code, and maps it MethodDesc, hot/cold region sizes, GCInfo, etc. GetMethodRegionInfo looks close.

  • API that take method entrypoint (that may not actually be a pointer to the actual code in some configuration - see my other comment), and maps it to MethodDesc. Equivalent of runtime's NonVirtualEntry2MethodDesc.

The legacy methods can be hopefully implemented using these two APIs in a compatible-enough way.

{
IPrecodeStubs precodeStubs = _target.Contracts.PrecodeStubs;
return precodeStubs.GetMethodDescFromPrecode(entrypoint);
}
else
{
// get the jit manager
// attempt to get the method info from a code block
}
return TargetPointer.Null;
}
```


The `CodeBlock` encapsulates the `MethodDesc` data from the target runtime together with the start of the jitted method

```csharp
Expand Down Expand Up @@ -284,7 +431,7 @@ For R2R images, `hasFlagByte` is always `false`.

* For jitted code (`EEJitManager`) a pointer to the `GCInfo` is stored on the `RealCodeHeader` which is accessed in the same way as `GetMethodInfo` described above. This can simply be returned as is. The `GCInfoVersion` is defined by the runtime global `GCInfoVersion`.

* For R2R code (`ReadyToRunJitManager`), the `GCInfo` is stored directly after the `UnwindData`. This in turn is found by looking up the `UnwindInfo` (`RUNTIME_FUNCTION`) and reading the `UnwindData` offset. We find the `UnwindInfo` as described above in `IExecutionManager.GetUnwindInfo`. Once we have the relevant unwind data, we calculate the size of the unwind data and return a pointer to the following byte (first byte of the GCInfo). The size of the unwind data is a platform specific. Currently only X86 is supported with a constant unwind data size of 32-bits.
* For R2R code (`ReadyToRunJitManager`), the `GCInfo` is stored directly after the `UnwindData`. This in turn is found by looking up the `UnwindInfo` (`RUNTIME_FUNCTION`) and reading the `UnwindData` offset. We find the `UnwindInfo` as described above in `IExecutionManager.GetUnwindInfo`. Once we have the relevant unwind data, we calculate the size of the unwind data and return a pointer to the following byte (first byte of the GCInfo). The size of the unwind data is a platform specific. See src/coreclr/vm/codeman.cpp GetUnwindDataBlob for more details.
* The `GCInfoVersion` of R2R code is mapped from the R2R MajorVersion and MinorVersion which is read from the ReadyToRunHeader which itself is read from the ReadyToRunInfo (can be found as in GetMethodInfo). The current GCInfoVersion mapping is:
* MajorVersion >= 11 and MajorVersion < 15 => 4

Expand Down
4 changes: 2 additions & 2 deletions docs/design/datacontracts/PrecodeStubs.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ 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);
TargetPointer GetMethodDescFromPrecode(TargetCodePointer entryPoint);
```

## Version 1, 2, and 3
Expand Down Expand Up @@ -289,7 +289,7 @@ After the initial precode type is determined, for stub precodes a refined precod
throw new InvalidOperationException($"Invalid precode type 0x{instrPointer:x16}");
}

TargetPointer IPrecodeStubs.GetMethodDescFromStubAddress(TargetCodePointer entryPoint)
TargetPointer IPrecodeStubs.GetMethodDescFromPrecode(TargetCodePointer entryPoint)
{
ValidPrecode precode = GetPrecodeFromEntryPoint(entryPoint);

Expand Down
2 changes: 1 addition & 1 deletion docs/design/datacontracts/RuntimeTypeSystem.md
Original file line number Diff line number Diff line change
Expand Up @@ -1421,7 +1421,7 @@ Getting a MethodDesc for a certain slot in a MethodTable

// Stub path, read address as a Precode and get the MethodDesc from it
{
TargetPointer methodDescPtr = _target.Contracts.PrecodeStubs.GetMethodDescFromStubAddress(pCode);
TargetPointer methodDescPtr = _target.Contracts.PrecodeStubs.GetMethodDescFromPrecode(pCode);
return methodDescPtr;
}
}
Expand Down
15 changes: 14 additions & 1 deletion src/coreclr/vm/datadescriptor/datadescriptor.inc
Original file line number Diff line number Diff line change
Expand Up @@ -481,6 +481,13 @@ CDAC_TYPE_BEGIN(CodePointer)
CDAC_TYPE_SIZE(sizeof(PCODE))
CDAC_TYPE_END(CodePointer)

#ifdef FEATURE_PORTABLE_ENTRYPOINTS
CDAC_TYPE_BEGIN(PortableEntryPoint)
CDAC_TYPE_INDETERMINATE(PortableEntryPoint)
CDAC_TYPE_FIELD(PortableEntryPoint, /*pointer*/, MethodDesc, cdac_data<PortableEntryPoint>::MethodDesc)
CDAC_TYPE_END(PortableEntryPoint)
#endif

CDAC_TYPE_BEGIN(MethodDescCodeData)
CDAC_TYPE_INDETERMINATE(MethodDescCodeData)
CDAC_TYPE_FIELD(MethodDescCodeData, /*CodePointer*/, TemporaryEntryPoint, offsetof(MethodDescCodeData,TemporaryEntryPoint))
Expand Down Expand Up @@ -585,12 +592,13 @@ CDAC_TYPE_FIELD(RuntimeFunction, /*uint32*/, EndAddress, offsetof(RUNTIME_FUNCTI
CDAC_TYPE_FIELD(RuntimeFunction, /*uint32*/, UnwindData, offsetof(RUNTIME_FUNCTION, UnwindData))
CDAC_TYPE_END(RuntimeFunction)

#if defined(TARGET_AMD64) || defined(TARGET_X86)
CDAC_TYPE_BEGIN(UnwindInfo)
CDAC_TYPE_INDETERMINATE(UnwindInfo)
#ifdef TARGET_X86
CDAC_TYPE_FIELD(UnwindInfo, /*uint32*/, FunctionLength, offsetof(UNWIND_INFO, FunctionLength))
#endif
CDAC_TYPE_END(UnwindInfo)
#endif

CDAC_TYPE_BEGIN(HashMap)
CDAC_TYPE_INDETERMINATE(HashMap)
Expand Down Expand Up @@ -965,6 +973,11 @@ CDAC_GLOBAL(ObjectToMethodTableUnmask, uint8, 1 | 1 << 1 | 1 << 2)
#else
CDAC_GLOBAL(ObjectToMethodTableUnmask, uint8, 1 | 1 << 1)
#endif //TARGET_64BIT
#ifdef FEATURE_PORTABLE_ENTRYPOINTS
CDAC_GLOBAL(FeaturePortableEntrypoints, uint8, 1)
#else
CDAC_GLOBAL(FeaturePortableEntrypoints, uint8, 0)
#endif // FEATURE_PORTABLE_ENTRYPOINTS
CDAC_GLOBAL(SOSBreakingChangeVersion, uint8, SOS_BREAKING_CHANGE_VERSION)
CDAC_GLOBAL(DirectorySeparator, uint8, (uint8_t)DIRECTORY_SEPARATOR_CHAR_A)
CDAC_GLOBAL(HashMapSlotsPerBucket, uint32, SLOTS_PER_BUCKET)
Expand Down
8 changes: 8 additions & 0 deletions src/coreclr/vm/precode_portable.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#ifndef FEATURE_PORTABLE_ENTRYPOINTS
#error Requires FEATURE_PORTABLE_ENTRYPOINTS to be set
#endif // !FEATURE_PORTABLE_ENTRYPOINTS
#include "cdacdata.h"

class PortableEntryPoint final
{
Expand Down Expand Up @@ -62,7 +63,14 @@ class PortableEntryPoint final
// pActualCode is a managed calling convention -> interpreter executor call stub in this case.
return _pInterpreterData != nullptr && _pActualCode != nullptr;
}
friend struct ::cdac_data<PortableEntryPoint>;
};
template<>
struct cdac_data<PortableEntryPoint>
{
static constexpr size_t MethodDesc = offsetof(PortableEntryPoint, _pMD);
};


extern InterleavedLoaderHeapConfig s_stubPrecodeHeapConfig;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,12 @@ public interface IExecutionManager : IContract
TargetPointer GetMethodDesc(CodeBlockHandle codeInfoHandle) => throw new NotImplementedException();
TargetCodePointer GetStartAddress(CodeBlockHandle codeInfoHandle) => throw new NotImplementedException();
TargetCodePointer GetFuncletStartAddress(CodeBlockHandle codeInfoHandle) => throw new NotImplementedException();
void GetMethodRegionInfo(CodeBlockHandle codeInfoHandle, out uint hotSize, out TargetPointer coldStart, out uint coldSize) => throw new NotImplementedException();
uint GetJITType(CodeBlockHandle codeInfoHandle) => throw new NotImplementedException();
TargetPointer NonVirtualEntry2MethodDesc(TargetCodePointer entrypoint) => throw new NotImplementedException();
TargetPointer GetUnwindInfo(CodeBlockHandle codeInfoHandle) => throw new NotImplementedException();
TargetPointer GetUnwindInfoBaseAddress(CodeBlockHandle codeInfoHandle) => throw new NotImplementedException();
TargetPointer GetDebugInfo(CodeBlockHandle codeInfoHandle, out bool hasFlagByte) => throw new NotImplementedException();
// **Currently GetGCInfo only supports X86**
void GetGCInfo(CodeBlockHandle codeInfoHandle, out TargetPointer gcInfo, out uint gcVersion) => throw new NotImplementedException();
TargetNUInt GetRelativeOffset(CodeBlockHandle codeInfoHandle) => throw new NotImplementedException();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ namespace Microsoft.Diagnostics.DataContractReader.Contracts;
public interface IPrecodeStubs : IContract
{
static string IContract.Name { get; } = nameof(PrecodeStubs);
TargetPointer GetMethodDescFromStubAddress(TargetCodePointer entryPoint) => throw new NotImplementedException();
TargetPointer GetMethodDescFromPrecode(TargetCodePointer entryPoint) => throw new NotImplementedException();
}

public readonly struct PrecodeStubs : IPrecodeStubs
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ public enum DataType
HashMap,
Bucket,
UnwindInfo,
UnwindCode,
NonVtableSlot,
MethodImpl,
NativeCodeSlot,
Expand All @@ -114,6 +115,7 @@ public enum DataType
InstMethodHashTable,
EEJitManager,
PatchpointInfo,
PortableEntryPoint,

TransitionBlock,
DebuggerEval,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ public static class Globals

public const string FeatureCOMInterop = nameof(FeatureCOMInterop);
public const string FeatureOnStackReplacement = nameof(FeatureOnStackReplacement);
public const string FeaturePortableEntrypoints = nameof(FeaturePortableEntrypoints);

public const string ObjectToMethodTableUnmask = nameof(ObjectToMethodTableUnmask);
public const string SOSBreakingChangeVersion = nameof(SOSBreakingChangeVersion);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,37 @@ public override bool GetMethodInfo(RangeSection rangeSection, TargetCodePointer
return true;
}

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

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

TargetPointer codeStart = FindMethodCode(rangeSection, jittedCodeAddress);
if (codeStart == TargetPointer.Null)
return;
Debug.Assert(codeStart.Value <= jittedCodeAddress.Value);

if (!GetRealCodeHeader(rangeSection, codeStart, out Data.RealCodeHeader? realCodeHeader))
return;

if (realCodeHeader.NumUnwindInfos == 0)
{
return;
}
// Sum up the lengths of all the runtime functions to get the hot size
for (uint i = 0; i < realCodeHeader.NumUnwindInfos; i++)
{
Data.RuntimeFunction function = _runtimeFunctions.GetRuntimeFunction(realCodeHeader.UnwindInfos, i);
hotSize += _runtimeFunctions.GetFunctionLength(function);
}
}

public override TargetPointer GetUnwindInfo(RangeSection rangeSection, TargetCodePointer jittedCodeAddress)
{
if (rangeSection.IsRangeList)
Expand Down
Loading
Loading