Skip to content
Closed
Show file tree
Hide file tree
Changes from 2 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
142 changes: 141 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 IP with no valid codeblock
TargetPointer NonVirtualEntry2MethodDesc(TargetCodePointer ip);

// 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,8 @@ 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 |

Global variables used:
| Global Name | Type | Purpose |
Expand Down Expand Up @@ -220,6 +229,137 @@ 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_PJIT for the R2RJitManager, and TYPE_UNKNOWN for any other value.
```csharp
private enum JITTypes
{
TYPE_UNKNOWN = 0,
TYPE_JIT = 1,
TYPE_PJIT = 2,
TYPE_INTERPRETER = 3
};
```
`NonVirtualEntry2MethodDesc` attempts to find a method desc from an IP that has no valid code block We attempt to find a method desc from a stub address, and if this fails we return TargetPointer.Null.
```csharp
TargetPointer IExecutionManager.NonVirtualEntry2MethodDesc(TargetCodePointer ip)
{
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.GetMethodDescFromStubAddress(ip);
}
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 +424,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
9 changes: 8 additions & 1 deletion src/coreclr/vm/datadescriptor/datadescriptor.inc
Original file line number Diff line number Diff line change
Expand Up @@ -581,12 +581,19 @@ CDAC_TYPE_FIELD(RuntimeFunction, /*uint32*/, UnwindData, offsetof(RUNTIME_FUNCTI
CDAC_TYPE_END(RuntimeFunction)

CDAC_TYPE_BEGIN(UnwindInfo)
CDAC_TYPE_INDETERMINATE(UnwindInfo)
CDAC_TYPE_SIZE(sizeof(UNWIND_INFO))
#ifdef TARGET_X86
CDAC_TYPE_FIELD(UnwindInfo, /*uint32*/, FunctionLength, offsetof(UNWIND_INFO, FunctionLength))
#else
CDAC_TYPE_FIELD(UnwindInfo, /*uint8*/, CountOfUnwindCodes, offsetof(UNWIND_INFO, CountOfUnwindCodes))
CDAC_TYPE_FIELD(UnwindInfo, /*uint8*/, UnwindCodeOffset, offsetof(UNWIND_INFO, UnwindCode))
#endif
CDAC_TYPE_END(UnwindInfo)

CDAC_TYPE_BEGIN(UnwindCode)
CDAC_TYPE_SIZE(sizeof(UNWIND_CODE))
CDAC_TYPE_END(UnwindCode)

CDAC_TYPE_BEGIN(HashMap)
CDAC_TYPE_INDETERMINATE(HashMap)
CDAC_TYPE_FIELD(HashMap, /*pointer*/, Buckets, cdac_data<HashMap>::Buckets)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ 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 ip) => 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();
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 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
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,43 @@ 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;
coldSize = 0;
coldStart = TargetPointer.Null;

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

if (_hotCold.TryGetHotColdStartEnd(r2rInfo.NumHotColdMap, r2rInfo.HotColdMap, index, r2rInfo.NumRuntimeFunctions, out uint hotIdx, out uint coldStartIdx, out uint coldEndIdx))
{

Data.RuntimeFunction function = _runtimeFunctions.GetRuntimeFunction(r2rInfo.RuntimeFunctions, hotIdx);
hotSize = _runtimeFunctions.GetFunctionLength(function);
// Sum up the lengths of all the runtime functions to get the cold size
for (uint i = coldStartIdx; i <= coldEndIdx; i++)
{
function = _runtimeFunctions.GetRuntimeFunction(r2rInfo.RuntimeFunctions, i);
coldSize += _runtimeFunctions.GetFunctionLength(function);
}
coldStart = imageBase + _runtimeFunctions.GetRuntimeFunction(r2rInfo.RuntimeFunctions, coldStartIdx).BeginAddress;
}
else
{
// No hot/cold splitting for this method, sum up the hot size only
uint hotStartIdx = AdjustRuntimeFunctionToMethodStart(r2rInfo, imageBase, index, out _);
uint hotEndIdx = AdjustRuntimeFunctionToMethodEnd(r2rInfo, imageBase, hotStartIdx);

for (uint i = hotStartIdx; i <= hotEndIdx; i++)
{
Data.RuntimeFunction function = _runtimeFunctions.GetRuntimeFunction(r2rInfo.RuntimeFunctions, i);
hotSize += _runtimeFunctions.GetFunctionLength(function);
}
}
}

public override TargetPointer GetUnwindInfo(RangeSection rangeSection, TargetCodePointer jittedCodeAddress)
{
// ReadyToRunJitManager::JitCodeToMethodInfo
Expand Down Expand Up @@ -120,7 +157,7 @@ public override void GetGCInfo(RangeSection rangeSection, TargetCodePointer jitt
Data.RuntimeFunction runtimeFunction = _runtimeFunctions.GetRuntimeFunction(r2rInfo.RuntimeFunctions, index);

TargetPointer unwindInfo = runtimeFunction.UnwindData + imageBase;
uint unwindDataSize = GetUnwindDataSize();
uint unwindDataSize = UnwindDataSize.GetUnwindDataSize(Target, unwindInfo, imageBase, Target.Contracts.RuntimeInfo.GetTargetArchitecture());
gcInfo = unwindInfo + unwindDataSize;
gcVersion = GetR2RGCInfoVersion(r2rInfo);
}
Expand All @@ -141,16 +178,6 @@ private uint GetR2RGCInfoVersion(Data.ReadyToRunInfo r2rInfo)
};
}

private uint GetUnwindDataSize()
{
RuntimeInfoArchitecture arch = Target.Contracts.RuntimeInfo.GetTargetArchitecture();
return arch switch
{
RuntimeInfoArchitecture.X86 => sizeof(uint),
_ => throw new NotSupportedException($"GetUnwindDataSize not supported for architecture: {arch}")
};
}

#region RuntimeFunction Helpers

private Data.ReadyToRunInfo GetReadyToRunInfo(RangeSection rangeSection)
Expand Down Expand Up @@ -214,6 +241,25 @@ private uint AdjustRuntimeFunctionToMethodStart(Data.ReadyToRunInfo r2rInfo, Tar
return index;
}

private uint AdjustRuntimeFunctionToMethodEnd(Data.ReadyToRunInfo r2rInfo, TargetPointer imageBase, uint index)
{
TargetPointer methodDesc;
do
{
// 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 increment the index to find the actual function / method desc for the funclet.
index++;
if (index >= r2rInfo.NumRuntimeFunctions)
{
break;
}
methodDesc = GetMethodDescForRuntimeFunction(r2rInfo, imageBase, index);
} while (methodDesc == TargetPointer.Null);

return index - 1;
}

private bool IsStubCodeBlockThunk(Data.RangeSection rangeSection, Data.ReadyToRunInfo r2rInfo, TargetCodePointer jittedCodeAddress)
{
if (r2rInfo.DelayLoadMethodCallThunks == TargetPointer.Null)
Expand Down
Loading