Skip to content
Open
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
44 changes: 44 additions & 0 deletions docs/design/datacontracts/ExecutionManager.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,10 @@ struct CodeBlockHandle
// Get the exception clause info for the code block
List<ExceptionClauseInfo> GetExceptionClauses(CodeBlockHandle codeInfoHandle);

// Classify a code address as a known stub kind (precode, jump stub, VSD stub, etc.).
// Returns Unknown if the address is not a recognized stub.
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);
Expand Down Expand Up @@ -119,6 +123,21 @@ public struct ExceptionClauseInfo
public TargetNUInt? TypeHandle;
public TargetPointer? ModuleAddr;
}

public enum StubKind : uint
{
Unknown = 0,
JumpStub = 1,
DynamicHelper = 3,
Prestub = 4,
VSD_DispatchStub = 5,
VSD_ResolveStub = 6,
VSD_LookupStub = 7,
VSD_VTableStub = 8,
CallCountingStub = 9,
StubLinkStub = 10,
MethodCallThunk = 11,
}
```

## Version 1
Expand All @@ -143,6 +162,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 |
Expand Down Expand Up @@ -501,6 +522,29 @@ 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 returns `Unknown` if the address is not recognized.

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);

// Look up in range section map
RangeSection range = FindRangeSection(jittedCodeAddress);
if (range == null) return StubKind.Unknown;

JitManager jitManager = GetJitManager(range);
return jitManager.GetStubCodeBlockKind(range, jittedCodeAddress);
}
```

### EE JIT Manager and Code Heap Info

```csharp
Expand Down
183 changes: 12 additions & 171 deletions src/coreclr/debug/daccess/daccess.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5309,86 +5309,6 @@ ClrDataAccess::GetJitHelperName(IN TADDR address)
return NULL;
}

// This function expects more memory than maybe needed.
static int FormatCLRStubName(
_In_opt_z_ LPCWSTR stubNameMaybe,
_In_ TADDR stubAddr,
_In_ ULONG32 bufLen,
_Out_ ULONG32 *symbolLen,
_Out_writes_bytes_opt_(bufLen) WCHAR* symbolBuf)
{
// Parts needed to construct a name:
// With stub manager name: "CLRStub[%s]@%p"
// No stub manager name: "CLRStub@%p"
const WCHAR formatName_Prefix[] = W("CLRStub");
const WCHAR formatName_OpenBracket[] = W("[");
const WCHAR formatName_CloseBracket[] = W("]");
const WCHAR formatName_PrefixEnd[] = W("@");

// Compute the address as a string safely.
WCHAR addrString[Max64BitHexString + 1];
FormatInteger(addrString, ARRAY_SIZE(addrString), "%p", stubAddr);
size_t addStringLen = u16_strlen(addrString);

// Compute maximum length, include the null terminator.
size_t formatName_MaxLen = ARRAY_SIZE(formatName_Prefix) // Include trailing null
+ ARRAY_SIZE(formatName_PrefixEnd) - 1
+ addStringLen;

// Consider stub manager name
size_t stubManagedNameLen = 0;
if (stubNameMaybe != NULL)
{
stubManagedNameLen = u16_strlen(stubNameMaybe);
formatName_MaxLen += ARRAY_SIZE(formatName_OpenBracket) - 1;
formatName_MaxLen += ARRAY_SIZE(formatName_CloseBracket) - 1;
}

HRESULT hr = S_FALSE;

// Compute the exact length needed.
const size_t lenNeeded = formatName_MaxLen + stubManagedNameLen;
if (lenNeeded <= bufLen)
{
size_t written = 0;

// Set the prefix
wcscpy_s(symbolBuf, bufLen - written, formatName_Prefix);
written += ARRAY_SIZE(formatName_Prefix) - 1;

// Add the name
if (stubManagedNameLen > 0)
{
wcscat_s(symbolBuf, bufLen - written, formatName_OpenBracket);
written += ARRAY_SIZE(formatName_OpenBracket) - 1;
wcscat_s(symbolBuf, bufLen - written, stubNameMaybe);
written += stubManagedNameLen;
wcscat_s(symbolBuf, bufLen - written, formatName_CloseBracket);
written += ARRAY_SIZE(formatName_CloseBracket) - 1;
}

// Append the prefix end
wcscat_s(symbolBuf, bufLen - written, formatName_PrefixEnd);
written += ARRAY_SIZE(formatName_PrefixEnd) - 1;

// Append the address
wcscat_s(symbolBuf, bufLen - written, addrString);
written += addStringLen;

hr = S_OK;
}

if (symbolLen)
{
if (!FitsIn<ULONG32>(lenNeeded))
return COR_E_OVERFLOW;

*symbolLen = (ULONG32)lenNeeded;
}

return hr;
}

HRESULT
ClrDataAccess::RawGetMethodName(
/* [in] */ CLRDATA_ADDRESS address,
Expand Down Expand Up @@ -5422,120 +5342,41 @@ ClrDataAccess::RawGetMethodName(
}

PTR_StubManager pStubManager;
MethodDesc* methodDesc = NULL;

{
EECodeInfo codeInfo(GetInterpreterCodeFromInterpreterPrecodeIfPresent(TO_TADDR(address)));
if (codeInfo.IsValid())
{
if (displacement)
{
*displacement = codeInfo.GetRelOffset();
}

methodDesc = codeInfo.GetMethodDesc();
goto NameFromMethodDesc;
}
}

pStubManager = StubManager::FindStubManager(TO_TADDR(address));
if (pStubManager != NULL)
{
if (displacement)
{
*displacement = 0;
}

//
// Special-cased stub managers
//
if (pStubManager == PrecodeStubManager::g_pManager)
LPCWSTR wszStubManagerName = pStubManager->GetStubManagerName(TO_TADDR(address));
_ASSERTE(wszStubManagerName != NULL);
if (u16_strcmp(wszStubManagerName, W("ThePreStub")) != 0 && u16_strcmp(wszStubManagerName, W("InteropDispatchStub")) != 0 && u16_strcmp(wszStubManagerName, W("TailCallStub")) != 0)
{
PCODE alignedAddress = AlignDown(TO_TADDR(address), PRECODE_ALIGNMENT);

#ifdef TARGET_ARM
alignedAddress += THUMB_CODE;
#endif

SIZE_T maxPrecodeSize = sizeof(StubPrecode);

#ifdef HAS_THISPTR_RETBUF_PRECODE
maxPrecodeSize = max((size_t)maxPrecodeSize, sizeof(ThisPtrRetBufPrecode));
#endif

for (SIZE_T i = 0; i < maxPrecodeSize / PRECODE_ALIGNMENT; i++)
// Skip the stubs that are just assembly helpers.
Copy link

Copilot AI Apr 27, 2026

Choose a reason for hiding this comment

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

The comment "Skip the stubs that are just assembly helpers." doesn't match the logic: the code returns the stub manager name when it is not one of the listed assembly-helper names. Please update the comment to reflect the actual behavior (or invert the condition if the comment is what was intended).

Suggested change
// Skip the stubs that are just assembly helpers.
// Return the stub manager name for stubs other than these assembly-helper stubs.

Copilot uses AI. Check for mistakes.
wcscpy_s(symbolBuf, bufLen, wszStubManagerName);
Copy link

Copilot AI Apr 22, 2026

Choose a reason for hiding this comment

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

In RawGetMethodName, the stub-manager branch copies into symbolBuf without checking symbolBuf for null and without setting *symbolLen. This can dereference a null optional output buffer and also returns S_OK even when the buffer is too small (since wcscpy_s failure is ignored). Please mirror the existing string-output pattern used by GetFullMethodName/ConvertUtf8: compute/set symbolLen when requested, only write when symbolBuf is non-null, and return S_FALSE on truncation.

Suggested change
wcscpy_s(symbolBuf, bufLen, wszStubManagerName);
ULONG32 stubManagerNameLen = static_cast<ULONG32>(wcslen(wszStubManagerName) + 1);
if (symbolLen != NULL)
{
*symbolLen = stubManagerNameLen;
}
if (symbolBuf != NULL)
{
if (wcscpy_s(symbolBuf, bufLen, wszStubManagerName) != 0)
{
return S_FALSE;
}
}

Copilot uses AI. Check for mistakes.
if (displacement)
{
EX_TRY
{
// Try to find matching precode entrypoint
Precode* pPrecode = Precode::GetPrecodeFromEntryPoint(alignedAddress, TRUE);
if (pPrecode != NULL && pPrecode->GetType() != PRECODE_UMENTRY_THUNK)
{
methodDesc = pPrecode->GetMethodDesc();
if (methodDesc != NULL)
{
if (DacValidateMD(methodDesc))
{
if (displacement)
{
*displacement = TO_TADDR(address) - PCODEToPINSTR(alignedAddress);
}
goto NameFromMethodDesc;
}
}
}
alignedAddress -= PRECODE_ALIGNMENT;
}
EX_CATCH
{
}
EX_END_CATCH
*displacement = 0;
}
return S_OK;
}
Comment on lines +5349 to 5360
Copy link

Copilot AI Apr 27, 2026

Choose a reason for hiding this comment

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

The stub-manager path copies wszStubManagerName into symbolBuf unconditionally and returns S_OK, but it doesn't (1) handle the common "size query" pattern where symbolBuf is null / bufLen is 0, (2) set *symbolLen, or (3) return S_FALSE when the buffer is too small. This can lead to null dereferences, missing length output, and incorrect HRESULTs. Please follow the same output contract as GetFullMethodName/ConvertUtf8: compute required length, set symbolLen when non-null, write only when symbolBuf is non-null and bufLen is sufficient (or truncate + S_FALSE as appropriate).

Copilot uses AI. Check for mistakes.

LPCWSTR wszStubManagerName = pStubManager->GetStubManagerName(TO_TADDR(address));
_ASSERTE(wszStubManagerName != NULL);

return FormatCLRStubName(
wszStubManagerName,
TO_TADDR(address),
bufLen,
symbolLen,
symbolBuf);
}

// Do not waste time looking up name for static helper. Debugger can get the actual name from .pdb.
PCSTR pHelperName;
pHelperName = GetJitHelperName(TO_TADDR(address));
if (pHelperName != NULL)
{
if (displacement)
{
*displacement = 0;
}

HRESULT hr = ConvertUtf8(pHelperName, bufLen, symbolLen, symbolBuf);
if (FAILED(hr))
return S_FALSE;

if (displacement)
{
*displacement = 0;
}
return S_OK;
}

return E_NOINTERFACE;

NameFromMethodDesc:
if (methodDesc->GetClassification() == mcDynamic
&& methodDesc->GetSigParser().IsNull())
{
return FormatCLRStubName(
NULL,
TO_TADDR(address),
bufLen,
symbolLen,
symbolBuf);
}

return GetFullMethodName(methodDesc, bufLen, symbolLen, symbolBuf);
}

HRESULT
Expand Down
6 changes: 3 additions & 3 deletions src/coreclr/vm/codeman.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4728,9 +4728,9 @@ StubCodeBlockKind EEJitManager::GetStubCodeBlockKind(RangeSection * pRangeSectio

TADDR start = dac_cast<PTR_EEJitManager>(pRangeSection->_pjit)->FindMethodCode(pRangeSection, currentPC);
if (start == (TADDR)0)
return STUB_CODE_BLOCK_NOCODE;
return STUB_CODE_BLOCK_UNKNOWN;
CodeHeader * pCHdr = PTR_CodeHeader(start - sizeof(CodeHeader));
return pCHdr->IsStubCodeBlock() ? pCHdr->GetStubCodeBlockKind() : STUB_CODE_BLOCK_MANAGED;
return pCHdr->IsStubCodeBlock() ? pCHdr->GetStubCodeBlockKind() : STUB_CODE_BLOCK_UNKNOWN;
}


Expand All @@ -4744,7 +4744,7 @@ TADDR EECodeGenManager::FindMethodCode(PCODE currentPC)

RangeSection * pRS = ExecutionManager::FindCodeRange(currentPC, ExecutionManager::GetScanFlags());
if (pRS == NULL || (pRS->_flags & RangeSection::RANGE_SECTION_CODEHEAP) == 0)
return STUB_CODE_BLOCK_NOCODE;
return STUB_CODE_BLOCK_UNKNOWN;
return dac_cast<PTR_EECodeGenManager>(pRS->_pjit)->FindMethodCode(pRS, currentPC);
}

Expand Down
5 changes: 1 addition & 4 deletions src/coreclr/vm/codeman.h
Original file line number Diff line number Diff line change
Expand Up @@ -113,8 +113,6 @@ enum StubCodeBlockKind : int
// Last valid value. Note that the definition is duplicated in debug\daccess\fntableaccess.cpp
STUB_CODE_BLOCK_LAST = 0xF,
// Placeholders returned by code:GetStubCodeBlockKind
STUB_CODE_BLOCK_NOCODE = 0x10,
STUB_CODE_BLOCK_MANAGED = 0x11,
STUB_CODE_BLOCK_STUBLINK = 0x12,
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Is this actually used anywhere? I see it used in a few case statements, but not actually passed in anywhere.

// Placeholder used by ReadyToRun images
STUB_CODE_BLOCK_METHOD_CALL_THUNK = 0x13,
Expand All @@ -128,8 +126,6 @@ inline const char *GetStubCodeBlockKindString(StubCodeBlockKind kind)
return "JumpStub";
case STUB_CODE_BLOCK_STUBLINK:
return "StubLinkStub";
case STUB_CODE_BLOCK_MANAGED:
return "Managed";
case STUB_CODE_BLOCK_METHOD_CALL_THUNK:
return "MethodCallThunk";
#ifdef FEATURE_TIERED_COMPILATION
Expand Down Expand Up @@ -793,6 +789,7 @@ template<> struct cdac_data<RangeSection>
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
Expand Down
6 changes: 6 additions & 0 deletions src/coreclr/vm/datadescriptor/datadescriptor.inc
Original file line number Diff line number Diff line change
Expand Up @@ -805,8 +805,14 @@ CDAC_TYPE_FIELD(RangeSection, T_POINTER, JitManager, cdac_data<RangeSection>::Ji
CDAC_TYPE_FIELD(RangeSection, T_INT32, Flags, cdac_data<RangeSection>::Flags)
CDAC_TYPE_FIELD(RangeSection, T_POINTER, HeapList, cdac_data<RangeSection>::HeapList)
CDAC_TYPE_FIELD(RangeSection, T_POINTER, R2RModule, cdac_data<RangeSection>::R2RModule)
CDAC_TYPE_FIELD(RangeSection, T_POINTER, RangeList, cdac_data<RangeSection>::RangeList)
CDAC_TYPE_END(RangeSection)

CDAC_TYPE_BEGIN(CodeRangeMapRangeList)
CDAC_TYPE_INDETERMINATE(CodeRangeMapRangeList)
CDAC_TYPE_FIELD(CodeRangeMapRangeList, T_INT32, RangeListType, cdac_data<CodeRangeMapRangeList>::RangeListType)
CDAC_TYPE_END(CodeRangeMapRangeList)

CDAC_TYPE_BEGIN(EEJitManager)
CDAC_TYPE_INDETERMINATE(EEJitManager)
CDAC_TYPE_FIELD(EEJitManager, T_BOOL, StoreRichDebugInfo, cdac_data<EEJitManager>::StoreRichDebugInfo)
Expand Down
7 changes: 7 additions & 0 deletions src/coreclr/vm/loaderallocator.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,13 @@ class CodeRangeMapRangeList : public RangeList
SArray<TADDR> _starts;
void* _id;
bool _collectible;
friend struct ::cdac_data<CodeRangeMapRangeList>;
};

template<>
struct cdac_data<CodeRangeMapRangeList>
{
static constexpr size_t RangeListType = offsetof(CodeRangeMapRangeList, _rangeListType);
};

// Iterator over Assemblies in the same ALC
Expand Down
2 changes: 1 addition & 1 deletion src/coreclr/vm/stubmgr.h
Original file line number Diff line number Diff line change
Expand Up @@ -432,7 +432,7 @@ class PrecodeStubManager : public StubManager

protected:
virtual LPCWSTR GetStubManagerName(PCODE addr)
{ LIMITED_METHOD_CONTRACT; return W("MethodDescPrestub"); }
{ LIMITED_METHOD_CONTRACT; return W("Prestub"); }
#endif
};
#endif // !FEATURE_PORTABLE_ENTRYPOINTS
Expand Down
Loading
Loading