Skip to content

[cDAC] implement GetRuntimeNameByAddress#127134

Open
rcj1 wants to merge 8 commits intodotnet:mainfrom
rcj1:rawgetmethodname2
Open

[cDAC] implement GetRuntimeNameByAddress#127134
rcj1 wants to merge 8 commits intodotnet:mainfrom
rcj1:rawgetmethodname2

Conversation

@rcj1
Copy link
Copy Markdown
Contributor

@rcj1 rcj1 commented Apr 20, 2026

This method will annotate stubs with the stub type name: for example "CallCountingStub" "VSD_DispatchStub", as well as providing auxiliary symbol names.

The native implementation currently attempts to follow PrecodeStubs and to resolve normal managed code addresses. The former only appears to yield meaningful, human-readable names on some managed JitHelpers. I am not sure why we consult or require this API for some JitHelpers (but not others), and when we would call this API with a normal managed code address (have never seen it happen). In the interest of time, both of these issues will be left for a follow-up.

Copilot AI review requested due to automatic review settings April 20, 2026 05:49
@dotnet-policy-service
Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @steveisok, @tommcdon, @dotnet/dotnet-diag
See info in area-owners.md if you want to be subscribed.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR adds cDAC support needed to implement IXCLRDataProcess::GetRuntimeNameByAddress by enabling stub-kind classification in the ExecutionManager contract, exposing additional RangeSection/range-list metadata, and wiring up the corresponding test and native data-descriptor updates.

Changes:

  • Implement GetRuntimeNameByAddress in the managed legacy SOS DAC implementation using cDAC contracts (ExecutionManager, PrecodeStubs, AuxiliarySymbols).
  • Add stub classification (StubKind, GetStubKind) and the data plumbing needed for stub range lists (RangeSection.RangeList, CodeRangeMapRangeList).
  • Extend tests/mocks and native CDAC descriptors/globals to support the new data contract fields and globals.

Reviewed changes

Copilot reviewed 22 out of 22 changed files in this pull request and generated 10 comments.

Show a summary per file
File Description
src/native/managed/cdac/tests/MockDescriptors/MockDescriptors.ExecutionManager.cs Extends mock RangeSection with RangeList and adds mock CodeRangeMapRangeList layout + builder helpers.
src/native/managed/cdac/tests/ExecutionManager/ExecutionManagerTests.cs Adds tests for stub-kind classification and registers the new mock datatype.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/SOSDacImpl.IXCLRDataProcess.cs Implements GetRuntimeNameByAddress using cDAC contracts and formats CLRStub names.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/RangeSection.cs Adds RangeList pointer field to the managed RangeSection data model.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/CodeRangeMapRangeList.cs Introduces managed data model for CodeRangeMapRangeList.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/PrecodeStubs_Common.cs Adds GetCandidateEntryPoints enumeration support for precode name resolution.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManager_1.cs Exposes GetStubKind in v1 contract wrapper.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManager_2.cs Exposes GetStubKind in v2 contract wrapper.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManagerCore.cs Implements GetStubKind logic (globals + range-section lookup) and reads new optional globals.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManagerCore.ReadyToRunJitManager.cs Adds stub-kind classification for R2R thunk regions.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManagerCore.EEJitManager.cs Adds stub-kind classification for range-list stubs and code-header stub code blocks.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Constants.cs Adds new global names for well-known stubs/helpers.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/DataType.cs Adds CodeRangeMapRangeList to the DataType enum.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IPrecodeStubs.cs Adds GetCandidateEntryPoints API to support precode resolution.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IExecutionManager.cs Adds StubKind enum and GetStubKind API.
src/coreclr/vm/loaderallocator.hpp Exposes CodeRangeMapRangeList::_rangeListType via cdac_data<>.
src/coreclr/vm/datadescriptor/datadescriptor.inc Adds RangeSection.RangeList, introduces CodeRangeMapRangeList type descriptor, and exports new stub globals.
src/coreclr/vm/codeman.h Adds cdac_data<RangeSection>::RangeList offset.
src/coreclr/inc/gfunc_list.h Adds DACGFN entries for new helper globals (platform-conditional).
src/coreclr/debug/daccess/daccess.cpp Adjusts CLRStub address formatting to fixed-width hex.
docs/design/datacontracts/PrecodeStubs.md Documents new GetCandidateEntryPoints API and its intended behavior.
docs/design/datacontracts/ExecutionManager.md Documents GetStubKind, new RangeList/CodeRangeMapRangeList descriptors, and new globals.

Comment thread src/native/managed/cdac/tests/ExecutionManager/ExecutionManagerTests.cs Outdated
Comment thread src/native/managed/cdac/tests/ExecutionManager/ExecutionManagerTests.cs Outdated
Comment thread src/native/managed/cdac/tests/ExecutionManager/ExecutionManagerTests.cs Outdated
Comment thread src/native/managed/cdac/tests/ExecutionManager/ExecutionManagerTests.cs Outdated
…rTests.cs

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings April 20, 2026 06:12
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 22 out of 22 changed files in this pull request and generated 1 comment.


### 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.
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.

Are we doing this classification just to match the name returned by the legacy DAC?

I think we should depend on coreclr.dll symbols for the names of these assembly helpers (similar to what we have done for JIT helpers).

Copy link
Copy Markdown
Contributor Author

@rcj1 rcj1 Apr 20, 2026

Choose a reason for hiding this comment

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

Yes, we are matching the DAC. The DAC uses FindStubManager, which does find these helpers. We can modify the DAC to exclude those, and rely on the debugger to fall back to coreclr symbols.

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.

I assume that this is only about symbols displayed by disassembly windbg. We do not need to match 1:1 what windbg displays in disassembly. We just need to make sure that it displays it is reasonable.

If we do nothing (ie stop recognizing these addresses in GetRuntimeNameByAddress), what is going to break?

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.

BTW: windbg disassembly experience that involves this API is very poor today. When I run !u on a method that just calls Console.ReadLine, I see this:

00007ff9`c0cb4780 55              push    rbp
00007ff9`c0cb4781 4883ec20        sub     rsp,20h
00007ff9`c0cb4785 488d6c2420      lea     rbp,[rsp+20h]
00007ff9`c0cb478a 48894d10        mov     qword ptr [rbp+10h],rcx
>>> 00007ff9`c0cb478e ff155c051d00    call    qword ptr [CLRStub[MethodDescPrestub]@00007FF9C0E84CF0 (00007ff9`c0e84cf0)]
00007ff9`c0cb4794 90              nop
00007ff9`c0cb4795 4883c420        add     rsp,20h
00007ff9`c0cb4799 5d              pop     rbp
00007ff9`c0cb479a c3              ret

I assume that MethodDescPrestub in the disassembly comes from this API. It is close to useless information. Raw address with no symbols name would be about as useful.

I would like to see something like call qword ptr [System_Console!System.Console.ReadLine (00007ff9c0e84cf0)]` in the disassembly, similar to what it was the case in .NET Framework. The contract of these DAC APIs is poorly defined and windbg makes a lot of assumptions about what they do. These details changed as the runtime evolved and ended up breaking the experience. Instead of trying to match 1:1 what these APIs do currently, we should focus on what they need to do to produce human friendly disassembly in windbg in common cases.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

@jkotas could you share your repro? I tried

public class Program
{
    public static void Main()
    {
        Console.ReadLine();
    }
}

in Release with tiered compilation off and got this:

Normal JIT generated code
Program.Main()
ilAddr is 000001DA1E9B2050 pImport is 000002500E7A4970
Begin 00007FF92052BD40, size 10

D:\git\console\Program.cs @ 4309:
>>> 00007ff9`2052bd40 4883ec28        sub     rsp,28h
00007ff9`2052bd44 ff152ebf7d00    call    qword ptr [CLRStub[MethodDescPrestub]@00007FF920D07C78 (00007ff9`20d07c78)] (System.Console.ReadLine(), mdToken: 0000000006000084)

D:\git\console\Program.cs @ 4310:
00007ff9`2052bd4a 90              nop
00007ff9`2052bd4b 4883c428        add     rsp,28h
00007ff9`2052bd4f c3              ret

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.

Minimal implementation that only deals with cases that are provably useful, and leaving the rest for the later, sounds good to me. It matches the point that I was trying to make - avoid spending time on implementing functionality that is not valuable.

Maybe these callcounting stub annotations

When I am looking at a simple call in the disassembly, I want to know the actual method name that it is going to call. It is not really interesting to know whether that call is going through a call counting stub or not. It is ok to include that information, but it should not be dominating the name, e.g. instead of CLRStub[CallCountingStub]@00007FF8D2160000, it can be just CallCountingStub.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I wonder if these belong in the JitHelpers

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.

Are you thinking about AuxiliarySymbols? Auxiliary symbols are meant to be used for a small number of global helpers. Call counting stubs are not like that. Call counting stubs are dynamically annotated per method and there is a lot of them. A good human readable name of the call counting stub would include the name of the method that it is counting. For example, something like:

00007ff8`d186bec0 48b950eaa42765020000 mov rcx,26527A4EA50h ("Hello, World!")
00007ff8`d186beca ff1590bd7b00    call    qword ptr [00007FF8D2027C60] (CallCountingStub System.Console.WriteLine ...)
00007ff8`d186bed0 90              nop

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Sorry, GitHub wasn't refreshed for me; I was referring to ThrowIndexOutOfRangeException. As we discussed, I'm not sure why we use this API to resolve this method. Maybe we shouldn't, but we can leave that for another day.

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.

ThrowIndexOutOfRangeException is a JIT helper implemented in C#, so it is both a managed method and a JIT helper. I think windbg ends up resolving the address of ThrowIndexOutOfRangeException because of it is the first call that recognizes the address and returns a name for it.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 26 out of 26 changed files in this pull request and generated 1 comment.

Comment thread src/coreclr/debug/daccess/daccess.cpp Outdated
// 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);
Copy link

Copilot AI Apr 21, 2026

Choose a reason for hiding this comment

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

FormatInteger is passed stubAddr (a TADDR/uintptr_t) with the format string %016llX on 64-bit. On non-Windows 64-bit platforms, uintptr_t is typically unsigned long, which does not match %llX and can cause undefined behavior in sprintf_s. Use a format specifier that matches uintptr_t (e.g., PRIxPTR) or cast stubAddr to an explicit unsigned long long (and similarly to unsigned int for the 32-bit branch) so the varargs type always matches the format string.

Suggested change
FormatInteger(addrString, ARRAY_SIZE(addrString), sizeof(void*) == 8 ? "%016llX" : "%08X", stubAddr);
#ifdef HOST_64BIT
FormatInteger(addrString, ARRAY_SIZE(addrString), "%016llX", static_cast<unsigned long long>(stubAddr));
#else
FormatInteger(addrString, ARRAY_SIZE(addrString), "%08X", static_cast<unsigned int>(stubAddr));
#endif

Copilot uses AI. Check for mistakes.
// Enumerates candidate precode entry points near a given code address.
// This is used to resolve a code address that falls within a
// precode stub back to its entry point.
IEnumerable<TargetCodePointer> GetCandidateEntryPoints(TargetCodePointer address);
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.

We should be able to find the precode start address using some address math rather than having to do guess-and-check repeatedly. I think the algorithm would look something like this:

TADDR GetStubStartFromInterior(RangeSection* pRS, TADDR interiorPrecodeAddress)
 {
     if (pRS == nullptr || !(pRS->_flags & RangeSection::RANGE_SECTION_RANGELIST))
         return 0;
     size_t stubSize = 0;
     switch (pRS->_pRangeList->GetCodeBlockKind())
     {
     case STUB_CODE_BLOCK_FIXUPPRECODE:
         stubSize = FixupPrecode::CodeSize;
         break;
     case STUB_CODE_BLOCK_STUBPRECODE:
         stubSize = StubPrecode::CodeSize;
         break;
     default:
         return 0;
     }
     const size_t pageMask = GetStubCodePageSize() - 1;
     const TADDR  pageBase = interiorPrecodeAddress & ~static_cast<TADDR>(pageMask);
     const size_t offset   = static_cast<size_t>(interiorPrecodeAddress - pageBase);
     return pageBase + (offset / stubSize) * stubSize;
 }

Comment thread docs/design/datacontracts/ExecutionManager.md
Copilot AI review requested due to automatic review settings April 22, 2026 23:21
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 18 out of 18 changed files in this pull request and generated 2 comments.

if (wcscmp(wszStubManagerName, W("ThePreStub")) != 0 && wcscmp(wszStubManagerName, W("InteropDispatchStub")) != 0 && wcscmp(wszStubManagerName, W("TailCallStub")) != 0)
{
// Skip the stubs that are just assembly helpers.
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.
- **ReadyToRunJitManager**: Checks whether the address falls within a delay-load method call thunk region.

```csharp
string? GetStubSymbol(TargetCodePointer jittedCodeAddress)
Copy link
Copy Markdown
Member

@jkotas jkotas Apr 22, 2026

Choose a reason for hiding this comment

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

Formatting of the stub symbol name is a debugger policy decision. I do not think the algorithm to format the stub symbol name should be part of the data contract. I think the data contract level should be about:

  • Tell me what kind of code is at this address. This can be an enum.
  • Tell me the range of that code (where does it start)
  • Tell me the MethodDesc (or other data) associated with this stub if there is one

And the debugger will assemble a name that it likes out of these.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

@noahfalk I think we can make this work and be more robust if we start by using ToString on the enum values

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.

"Symbol" probably wasn't the right term, name is more what I was thinking. For example names like "FixupPrecode", not "FixupPrecide(Foo.Bar)@0xdeadbeef". The reason I was resistant to an enum is because I fear it won't be stable over time and enumerations often imply a fixed set of options. If we want to be very explicit that the enum is open-ended and we won't consider it a breaking change to add new values then I'm fine with it.

=> LegacyFallbackHelper.CanFallback() && _legacyProcess is not null ? _legacyProcess.GetRuntimeNameByAddress(address, flags, bufLen, nameLen, nameBuf, displacement) : HResults.E_NOTIMPL;
{
if (displacement is not null)
*displacement = default;
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.

Can you check the windbg disassembly for some stubs with multiple instructions? I suspect that setting the displacement to 0 may produce a named label at each instruction that would be an experience regression.

Copy link
Copy Markdown
Contributor Author

@rcj1 rcj1 Apr 23, 2026

Choose a reason for hiding this comment

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

With (top) and without (bottom) setting displacement to 0:
Screenshot 2026-04-23 111619
Screenshot 2026-04-23 125132

Copy link
Copy Markdown
Member

@jkotas jkotas Apr 23, 2026

Choose a reason for hiding this comment

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

There is a regular windbg u and .NET custom !u. The two resolve symbols differently. u symbol resolution is more messy and complicated. Can you try regular windbg u as well?

I have done a quick ad-hoc test:

.NET 11 P3:

0:000> u 00007ff9`6d220960
System.Console.WriteLine():
00007ff9`6d220960 ff25fa3f0000    jmp     qword ptr [CLRStub[MethodDescPrestub]@00007FF96D224960 (00007ff9`6d224960)]
00007ff9`6d220966 4c8b15fb3f0000  mov     r10,qword ptr [CLRStub[MethodDescPrestub]@00007FF96D224968 (00007ff9`6d224968)]
00007ff9`6d22096d ff25fd3f0000    jmp     qword ptr [CLRStub[MethodDescPrestub]@00007FF96D224970 (00007ff9`6d224970)]

With if (displacement != nullptr) *displacement = 0; at the end of ClrDataAccess::GetRuntimeNameByAddress - it assigns wrong symbol to the first instruction and then gives a label to every single instruction after that:

0:000> u 00007ff9`6d7f2534
System.Threading.Thread.Sleep(Int32):
00007ff9`6d7f2534 66666666ff25fa3f0000 jmp word ptr [CLRStub[MethodDescPrestub]@00007FF96D7F6538 (00007ff9`6d7f6538)]
Console.WriteLine():
00007ff9`6d7f253e 4c8b15fb3f0000  mov     r10,qword ptr [CLRStub[MethodDescPrestub]@00007FF96D7F6540 (00007ff9`6d7f6540)]
Console.WriteLine():
00007ff9`6d7f2545 ff25fd3f0000    jmp     qword ptr [CLRStub[MethodDescPrestub]@00007FF96D7F6548 (00007ff9`6d7f6548)]
Console.WriteLine():

I have not tried with your full delta. I expect that it is going to be the same.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I see, I'll revert back to only setting it when we succeed in resolving a symbol.

Comment on lines +55 to +57
// 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);
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.

Suggested change
// 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);
// 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.
CodeKind GetCodeKind(TargetCodePointer codeAddress);

Can we generalize to deal with code in general? Here is a code address, tell me whether it is code owned by the runtime and what kind of code is that.

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.

What classifications do you imagine CodeKind would contain beyond the stubs we are classifying now? My worry is that any of the classifications I can think of that go deeper than "not a stub" start being facts that are also true of some stubs. This would make CodeKind more like CodeFlags.

For me this method does raise a question about whether it should classify stubs implemented via IL too. I think it would be fine if it didn't initially because we are just trying to hit parity with current experiences, but that seems like a natural evolution.

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.

I think this API should describe what the code is, not how it was generated (e.g. whether it was generated from static IL or runtime generated IL).

Managed code kinds (JITed, R2R, Interpreted) have GCInfo, UnwindInfo, MethodDesc, ... . Once you know that it is managed code, you can get more information about it from different *infos or MethodDesc.

The other code kinds do not have any of that.

Once we do the range section lookup, we know the code kind at the given IP. We can certainly map the managed code kinds to unknown. It looks wasteful to throw away the information since we have it.

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 the suggestion to add a CodeKind.Managed to the enum? If a stub that currently isn't jitted from IL became jitted in the future would we stop reporting it as a specific kind of stub and report it as Managed instead?

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.

It looks wasteful to throw away the information since we have it.

One of my original suggestions is that this API should consume a CodeBlockHandle parameter rather than a TargetPointer so that all the work we do to lookup code information (range section, code start, code header, etc) gets cached. Regardless whether we put more info in one API or spread it over multiple I think that would help ensure the info stays cached and quickly accessible.

Copy link
Copy Markdown
Member

@jkotas jkotas Apr 27, 2026

Choose a reason for hiding this comment

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

Is the suggestion to add a CodeKind.Managed to the enum?

Yes. I would make it more fine-grained and split it into JIT/R2R/Interpreter.

If a stub that currently isn't jitted from IL became jitted in the future would we stop reporting it as a specific kind of stub and report it as Managed instead?

StubLink stubs are like that today. Whether or not we generate StubLink stub or IL stub in a given situation depends on architecture and the stub signature in some cases.

One of my original suggestions is that this API should consume a CodeBlockHandle parameter rather than a TargetPointer so that all the work we do to lookup code information (range section, code start, code header, etc) gets cached.

CodeBlockHandle is a strong-types wrapper around IP. I do not think it is really needed for caching in the current setup.

CodeBlockHandle seems to be coupled with managed code kinds currently. We would have to fix that to generalized it cover the other code kinds.

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.

StubLink stubs are like that today. Whether or not we generate StubLink stub or IL stub in a given situation depends on architecture and the stub signature in some cases.

I think there are two directions we can go with this API that seem mutually exclusive:

  1. We can have it focus on identifying stub identity regardless of how that stub's code was generated (StubLinker, Jit, R2R). On this path we would expand the API to include various forms of ILStubs or TransientIL stubs.
  2. We can have it focus on RangeSection/HeapList distinctions. In this model it only classifies stubs to the degree that RangeSection/HeapList already do so. In order to get a full stub classification the consumer would need to be aware of code-generation distinctions and chain together multiple APIs to further refine the Jitted/R2R categories.

I was imagining this API as being (1), @jkotas it appears you are imagining it being (2). Ultimately both seem useful so I'm fine if this API serves role (2) and we create some other API in the future when we care about IL stub classification for (1). I do think if this API becomes option (2) then we should either get rid of GetJITType or re-implement it in terms of this API.

CodeBlockHandle seems to be coupled with managed code kinds currently. We would have to fix that to generalized it cover the other code kinds.

Yep, I expected thats what we'd do.

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.

classifies stubs to the degree that RangeSection/HeapList already do so

Most stubs are in codeblocks with a code header. To classify a code address, we need to find a range section and then find the code block within the range section using a nibble map (or the R2R equivalent). The code block is going to have a code header that may or may not point to a methoddesc. If it has methoddesc, it is regular managed code and you can find more about it from methoddesc. If it does not have a method desc, it is some other code kind (STUB_CODE_BLOCK... enum). It is where I would stop.

If we keep going and interrogate MethodDesc in this API too, we are going to be duplicating existing APIs (IRuntimeTypeSystem.IsILStub, IRuntimeTypeSystem.IsWrapperStub, IRuntimeTypeSystem.IsAsyncThunkMethod, and we are likely going to have more). It seems useful for MethodDesc classification to live in separate API(s).

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.

I think we are in agreement that this method will do option (2) which would not look at MethodDesc info.

CallCountingStub = 9,
StubLinkStub = 10,
MethodCallThunk = 11,
NoCodeStub = 12,
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.

How is this different from UnknownStub?

Copy link
Copy Markdown
Contributor Author

@rcj1 rcj1 Apr 27, 2026

Choose a reason for hiding this comment

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

Good point, in practice they mean the same thing, either no code range found or no stub found or both. Maybe the intent was to separate "this stub doesn't make sense" from "we couldn't find a code range". I'm not sure we really need this distinction, maybe we should just consolidate the two.

StubLinkStub = 10,
MethodCallThunk = 11,
NoCodeStub = 12,
Managed = 13,
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.

We may want to return more granularity here: Jitted, ReadyToRun, Interpreted

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.

I'd suggest that we remove 'Managed' and probably 'NoCodeStub' too, returning 'Unknown' for those instead. We have the GetJITType() API that can provide Jitted/R2R/Interpretted classification.

I'm suspicious that at some point we may want to classify IL-backed stub implementations here too or perhaps some of the stubs already classified by this method might become IL backed in the future. 'Managed' or 'Jitted' appear more like orthogonal attributes rather than a specific kind of stub.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I agree with Noah, we already have an API for the type of managed code.

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.

I think @jkotas is effectively proposing to turn this method into a super-set of what GetJITType() is. Its not the design I originally had in mind, but I can see it would make a different useful API and it would handle the immediate scenario needs so I'm fine to do that. Assuming we go that way I'd suggest we either remove GetJiTType() or re-implement it as a small helper over this API depending on how heavily its already used.
#127134 (comment)

Copilot AI review requested due to automatic review settings April 27, 2026 17:46
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 20 out of 20 changed files in this pull request and generated 4 comments.

if (_legacyProcess is not null)
{
uint nameLenLocal = 0;
char[] nameBufLocal = new char[bufLen > 0 ? bufLen : 1];
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 DEBUG validation block allocates nameBufLocal using a uint length expression (new char[bufLen > 0 ? bufLen : 1]). Array lengths must be int, so this will not compile as written. Convert bufLen to an int (with bounds checking) before using it as an array length (and consider handling very large bufLen defensively).

Suggested change
char[] nameBufLocal = new char[bufLen > 0 ? bufLen : 1];
int nameBufLocalLength = bufLen == 0 ? 1 : bufLen > int.MaxValue ? int.MaxValue : (int)bufLen;
char[] nameBufLocal = new char[nameBufLocalLength];

Copilot uses AI. Check for mistakes.
{
hrLocal = _legacyProcess.GetRuntimeNameByAddress(
address, flags, bufLen,
&nameLenLocal,
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.

In the DEBUG cross-check call to _legacyProcess.GetRuntimeNameByAddress, nameLenLocal is always passed as a non-null pointer. To accurately validate behavior, this should mirror the current call's nameLen parameter (i.e., pass null when nameLen is null). Otherwise debug builds can hit mismatched HRESULT/outputs for callers that omit nameLen.

Suggested change
&nameLenLocal,
nameLen is null ? null : &nameLenLocal,

Copilot uses AI. Check for mistakes.
Comment on lines +5349 to 5360
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.
wcscpy_s(symbolBuf, bufLen, wszStubManagerName);
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;
}
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.
#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.
Comment thread src/coreclr/vm/codeman.h
// 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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants