From c77dc10ac118947717512fa19fe35dc7da071b6e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Strehovsk=C3=BD?= Date: Thu, 19 Mar 2026 14:38:29 +0900 Subject: [PATCH 1/2] Add TSF_SuspensionTrapped flag to NativeAOT for accurate sample profiler reporting Implement TSF_SuspensionTrapped in the NativeAOT runtime to distinguish managed from external code in EventPipe sample profiler events, matching CoreCLR's TS_SuspensionTrapped behavior. The flag is set in WaitForGC before entering preemptive mode and cleared after the wait loop completes. All suspension paths (DisablePreemptiveMode, InlineSuspend, Redirect, hijack stubs) funnel through WaitForGC, ensuring complete coverage. Fixes https://github.com/dotnet/runtime/issues/125217 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/coreclr/nativeaot/Runtime/eventpipe/ep-rt-aot.cpp | 8 +++++--- src/coreclr/nativeaot/Runtime/thread.cpp | 6 ++++++ src/coreclr/nativeaot/Runtime/thread.h | 5 +++++ src/coreclr/nativeaot/Runtime/thread.inl | 5 +++++ .../eventsvalidation/SampleProfilerSampleType.cs | 1 - 5 files changed, 21 insertions(+), 4 deletions(-) diff --git a/src/coreclr/nativeaot/Runtime/eventpipe/ep-rt-aot.cpp b/src/coreclr/nativeaot/Runtime/eventpipe/ep-rt-aot.cpp index 5225fe67316f35..aa77a98d94f9c7 100644 --- a/src/coreclr/nativeaot/Runtime/eventpipe/ep-rt-aot.cpp +++ b/src/coreclr/nativeaot/Runtime/eventpipe/ep-rt-aot.cpp @@ -122,9 +122,11 @@ ep_rt_aot_sample_profiler_write_sampling_event_for_threads ( // Walk the stack and write it out as an event. if (ep_rt_aot_walk_managed_stack_for_thread (target_thread, current_stack_contents) && !ep_stack_contents_is_empty (current_stack_contents)) { - // Set the payload. - // TODO: We can actually detect whether we are in managed or external code but does it matter?! - uint32_t payload_data = EP_SAMPLE_PROFILER_SAMPLE_TYPE_EXTERNAL; + // Set the payload. If the thread is trapped for suspension, it was in cooperative mode + // (managed code). Otherwise, it was in preemptive mode (external code). + uint32_t payload_data = target_thread->IsSuspensionTrapped() + ? EP_SAMPLE_PROFILER_SAMPLE_TYPE_MANAGED + : EP_SAMPLE_PROFILER_SAMPLE_TYPE_EXTERNAL; // Write the sample. ep_write_sample_profile_event ( diff --git a/src/coreclr/nativeaot/Runtime/thread.cpp b/src/coreclr/nativeaot/Runtime/thread.cpp index eb9d1f2a964e2e..3e002ce0775934 100644 --- a/src/coreclr/nativeaot/Runtime/thread.cpp +++ b/src/coreclr/nativeaot/Runtime/thread.cpp @@ -83,6 +83,10 @@ void Thread::WaitForGC(PInvokeTransitionFrame* pTransitionFrame) // restored after the wait operation; int32_t lastErrorOnEntry = PalGetLastError(); + // Mark that this thread is trapped for suspension. + // Used by the sample profiler to determine this thread was in managed code. + SetState(TSF_SuspensionTrapped); + do { // set preemptive mode @@ -102,6 +106,8 @@ void Thread::WaitForGC(PInvokeTransitionFrame* pTransitionFrame) } while (ThreadStore::IsTrapThreadsRequested()); + ClearState(TSF_SuspensionTrapped); + // Restore the saved error PalSetLastError(lastErrorOnEntry); } diff --git a/src/coreclr/nativeaot/Runtime/thread.h b/src/coreclr/nativeaot/Runtime/thread.h index 2bd93257c7dab2..c93556e59ebb67 100644 --- a/src/coreclr/nativeaot/Runtime/thread.h +++ b/src/coreclr/nativeaot/Runtime/thread.h @@ -214,6 +214,9 @@ class Thread : private RuntimeThreadLocals // On Unix this is an optimization to not queue up more signals when one is // still being processed. TSF_Interrupted = 0x00000200, // Set to indicate Thread.Interrupt() has been called on this thread + + TSF_SuspensionTrapped = 0x00000400, // Set when thread is trapped waiting for suspension to complete + // (was in managed code). }; private: @@ -306,6 +309,8 @@ class Thread : private RuntimeThreadLocals bool IsDetached(); void SetDetached(); + bool IsSuspensionTrapped(); + PTR_VOID GetThreadStressLog() const; #ifndef DACCESS_COMPILE void SetThreadStressLog(void * ptsl); diff --git a/src/coreclr/nativeaot/Runtime/thread.inl b/src/coreclr/nativeaot/Runtime/thread.inl index ea622816ce2013..4345246a462283 100644 --- a/src/coreclr/nativeaot/Runtime/thread.inl +++ b/src/coreclr/nativeaot/Runtime/thread.inl @@ -146,6 +146,11 @@ inline bool Thread::IsDoNotTriggerGcSet() return IsStateSet(TSF_DoNotTriggerGc); } +inline bool Thread::IsSuspensionTrapped() +{ + return IsStateSet(TSF_SuspensionTrapped); +} + inline bool Thread::IsCurrentThreadInCooperativeMode() { #ifndef DACCESS_COMPILE diff --git a/src/tests/tracing/eventpipe/eventsvalidation/SampleProfilerSampleType.cs b/src/tests/tracing/eventpipe/eventsvalidation/SampleProfilerSampleType.cs index 8551f85d055233..190b71f7ec74db 100644 --- a/src/tests/tracing/eventpipe/eventsvalidation/SampleProfilerSampleType.cs +++ b/src/tests/tracing/eventpipe/eventsvalidation/SampleProfilerSampleType.cs @@ -22,7 +22,6 @@ public class SampleProfilerSampleType private const uint SampleTypeExternal = 1; private const uint SampleTypeManaged = 2; - [ActiveIssue("https://github.com/dotnet/runtime/issues/125217", typeof(Utilities), nameof(Utilities.IsNativeAot))] [Fact] public static int TestEntryPoint() { From 43af148655d99aa828a3d395a4aa80d70585f27e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Strehovsk=C3=BD?= Date: Wed, 18 Mar 2026 23:59:31 -0700 Subject: [PATCH 2/2] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- src/coreclr/nativeaot/Runtime/eventpipe/ep-rt-aot.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/coreclr/nativeaot/Runtime/eventpipe/ep-rt-aot.cpp b/src/coreclr/nativeaot/Runtime/eventpipe/ep-rt-aot.cpp index aa77a98d94f9c7..5e8c8c0e258ed1 100644 --- a/src/coreclr/nativeaot/Runtime/eventpipe/ep-rt-aot.cpp +++ b/src/coreclr/nativeaot/Runtime/eventpipe/ep-rt-aot.cpp @@ -26,6 +26,7 @@ #include "threadstore.inl" #include "eventtrace_context.h" #include "eventtracebase.h" +#include "thread.inl" // Uses _rt_aot_lock_internal_t that has CrstStatic as a field // This is initialized at the beginning and EventPipe library requires the lock handle to be maintained by the runtime