Skip to content

[Profiler] Invalid FunctionID in managed-unmanaged transition in C++/CLI code #120151

Description

@alexanderqqq

Hi there,

Profiler receives invalid FunctionID in manaded-unmanaged transitions callbacks starting from .NET 7 when profiled C++/CLI application calls native function by pointer.

In .NET 6 profiler receives each callback twice, but FunctionID is the same and is correct.

.NET 6.0.36 x64, Windows 24H2 x64 26100.6584:

00005FE8 0 0000000000000000 EProf::UnmanagedToManagedTransition fid=00007FF89C6DA990 reason=call fname='?A0x3e62bc42.func' cname=''
00005FE8 0 0000000000000000 EProf::UnmanagedToManagedTransition fid=00007FF89C6DA990 reason=call fname='?A0x3e62bc42.func' cname=''
00005FE8 0 0000000000000000 EProf::ManagedToUnmanagedTransition fid=00007FF89C6DA990 reason=return fname='?A0x3e62bc42.func' cname=''
00005FE8 0 0000000000000000 EProf::ManagedToUnmanagedTransition fid=00007FF89C6DA990 reason=return fname='?A0x3e62bc42.func' cname=''

Starting from .NET 8 (.NET7 we got crash inside dotnet runtime even before ManagedToUnmanagedTransition call) these two additional callback are called with corrupted FunctionID. Using this ID in ICorProfilerInfo::GetFunctionInfo or any other method leads to crash (access violation)

.NET 8.0.20/9.0.9 x64, Windows 24H2 x64 26100.6584:

000023F8 0 0000000000000000 EProf::UnmanagedToManagedTransition fid=00007FF8A0F87610 reason=call fname='?A0x3e62bc42.func' cname=''
000023F8 0 0000000000000000 EProf::ManagedToUnmanagedTransition fid=00007FF8A0FA0000 reason=call // invalid FunctionID
000023F8 0 0000000000000000 EProf::UnmanagedToManagedTransition fid=00007FF8A0FA0000 reason=return // invalid FunctionID
000023F8 0 0000000000000000 EProf::ManagedToUnmanagedTransition fid=00007FF8A0F87610 reason=return fname='?A0x3e62bc42.func' cname=''

Here is the sample of C++/CLI the application

CppCliIssue.zip

Activity

  1. added
    needs-area-labelAn area label is needed to ensure this gets routed to the appropriate area owners
    on Sep 26, 2025
  2. added and removed
    needs-area-labelAn area label is needed to ensure this gets routed to the appropriate area owners
    on Sep 26, 2025
  3. dotnet-policy-service commented on Sep 26, 2025

    @dotnet-policy-service
    Contributor

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

  4. added this to the 11.0.0 milestone on Sep 30, 2025
  5. mayphi commented on Jul 9, 2026

    @mayphi

    Is there any update on this? Is this still planned for .NET 11?

  6. lateralusX commented on Jul 29, 2026

    @lateralusX
    Member

    I have verified that it's fixed in .NET 11, but repros on .NET 9/10. The fix is a side effect of #117901 and part of that changed the conditions in EmitProfilerBeginTransitionCallback. Previously it misclassified this specific scenario as having a MethodDesc * as the secret argument, but that was not true (its a UMEntryThunkData *) and trying to use that as a MethodDesc * will lead to an AV. In .NET 11 its correctly emitted as NULL, since there is no MethoDesc * for that case. I will see if we could add a regression test covering this scenario, running as part of our profiler tests.

  7. valco1994 commented on Jul 29, 2026

    @valco1994
    Contributor

    Thank you for good news, @lateralusX!
    Is there any chance that the fix will be back-ported to .NET 10 and/or 9?

  8. lateralusX commented on Jul 30, 2026

    @lateralusX
    Member

    Regression test, #131578.

  9. lateralusX commented on Jul 30, 2026

    @lateralusX
    Member

    I will look into potential backport to .NET10 (this won't meet the bar for .NET9), but we can't take the full PR, so we will need to backport a more isolated fix, but with the regression test above, it will give us a clear signal if what we backport will be enough to solve the issue.

  10. lateralusX commented on Jul 31, 2026

    @lateralusX
    Member

    The regression test in #131578 fails on checked build and reveals something interesting that is an artifact of emitting an incorrect internal transition as part of the U2M marshaling stub, reported as an incorrect M2U U2M transition:

    000023F8 0 0000000000000000 EProf::UnmanagedToManagedTransition fid=00007FF8A0F87610 reason=call fname='?A0x3e62bc42.func' cname=''
    --> INCORRECT FORWARD CALL: 000023F8 0 0000000000000000 EProf::ManagedToUnmanagedTransition fid=00007FF8A0FA0000 reason=call // invalid FunctionID
    --> INCORRECT FORWARD RETURN: 000023F8 0 0000000000000000 EProf::UnmanagedToManagedTransition fid=00007FF8A0FA0000 reason=return // invalid FunctionID
    000023F8 0 0000000000000000 EProf::ManagedToUnmanagedTransition fid=00007FF8A0F87610 reason=return fname='?A0x3e62bc42.func' cname=''
    

    This was partly fixed in #69761, but didn't include the reverse p/invoke scenario hit by the C++/CLI scenario. I'm tempted to eliminate that incorrectly reported transition altogether, inline with the intent of #69761, but it will be an observable change.

    Since the incorrect transition pair previously included an invalid MethodDesc address, that if used would crash the process, I'm leaning towards eliminating the incorrect transitions in .NET11 (instead of keeping it with a NULL function id), even if it would lead to an observable change.

    With the change the regression test, that does a M2U (forward p/invoke) then a U2M (reverse p/invoke):

    __declspec(noinline) void ManagedByPointerTarget() {}
    __declspec(noinline) static void call(void (*f)()) { f(); }
    
    public ref class TestClass
    {
    public:
        // Managed -> native 'call' -> managed 'ManagedByPointerTarget' by pointer.
        int CallManagedFunctionByPointer()
        {
            call(ManagedByPointerTarget);
            return 100;
        }
    };
    

    now ends up emitting:

    ManagedToUnmanagedTransition FunctionID=0x0 reason=0 insideTarget=0 name=<null> ← forward calli to native 'call'
    UnmanagedToManagedTransition FunctionID=0x…4F210 reason=0 insideTarget=0 name=ManagedByPointerTarget ← reverse p/invoke
    ManagedToUnmanagedTransition FunctionID=0x…4F210 reason=1 insideTarget=1 name=ManagedByPointerTarget ← reverse p/invoke RETURN
    UnmanagedToManagedTransition FunctionID=0x0 reason=1 insideTarget=0 name=<null> ← forward calli RETURN
    

    I will update the regression test PR to include this change (making it into a fix + test PR), if anyone have concerns around doing that change, please let me know.

  11. lateralusX commented on Aug 6, 2026

    @lateralusX
    Member

    .NET 10 backport PR, #131928.

  12. locked and limited conversation to collaborators on Sep 6, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions