From 75438e687c65b6496192d601928cb0f1f52a36ce Mon Sep 17 00:00:00 2001 From: mdh1418 Date: Fri, 14 Aug 2026 01:46:53 +0000 Subject: [PATCH 1/2] Restore ELT stack snapshot context Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/coreclr/vm/profilinghelper.h | 11 +++++ src/coreclr/vm/profilinghelper.inl | 30 ++++++++++++++ src/coreclr/vm/proftoeeinterfaceimpl.cpp | 53 ++++++++++++++++++++++++ src/coreclr/vm/threads.cpp | 1 + src/coreclr/vm/threads.h | 20 +++++++++ 5 files changed, 115 insertions(+) diff --git a/src/coreclr/vm/profilinghelper.h b/src/coreclr/vm/profilinghelper.h index e33071062426a0..721f0f16f8f82a 100644 --- a/src/coreclr/vm/profilinghelper.h +++ b/src/coreclr/vm/profilinghelper.h @@ -136,4 +136,15 @@ class SetCallbackStateFlagsHolder DWORD m_dwOriginalFullState; }; +class ProfilerELTContextHolder +{ +public: + ProfilerELTContextHolder(T_CONTEXT *pContext); + ~ProfilerELTContextHolder(); + +private: + Thread * m_pThread; + T_CONTEXT *m_pOriginalContext; +}; + #endif //__PROFILING_HELPER_H__ diff --git a/src/coreclr/vm/profilinghelper.inl b/src/coreclr/vm/profilinghelper.inl index eb529e55dfa686..249d109faceba7 100644 --- a/src/coreclr/vm/profilinghelper.inl +++ b/src/coreclr/vm/profilinghelper.inl @@ -46,6 +46,36 @@ FORCEINLINE SetCallbackStateFlagsHolder::~SetCallbackStateFlagsHolder() } } +FORCEINLINE ProfilerELTContextHolder::ProfilerELTContextHolder(T_CONTEXT *pContext) +{ + m_pThread = (pContext != nullptr) ? GetThreadNULLOk() : nullptr; + if (m_pThread != nullptr) + { + m_pOriginalContext = m_pThread->GetProfilerELTContext(); + m_pThread->SetProfilerELTContext(pContext); + } + else + { + m_pOriginalContext = nullptr; + } +} + +FORCEINLINE ProfilerELTContextHolder::~ProfilerELTContextHolder() +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + MODE_ANY; + } + CONTRACTL_END; + + if (m_pThread != nullptr) + { + m_pThread->SetProfilerELTContext(m_pOriginalContext); + } +} + #ifdef ENABLE_CONTRACTS //--------------------------------------------------------------------------------------- // diff --git a/src/coreclr/vm/proftoeeinterfaceimpl.cpp b/src/coreclr/vm/proftoeeinterfaceimpl.cpp index dd65cdb956ecea..e539c91951048c 100644 --- a/src/coreclr/vm/proftoeeinterfaceimpl.cpp +++ b/src/coreclr/vm/proftoeeinterfaceimpl.cpp @@ -123,6 +123,7 @@ #include "generics.h" #include "gcinfo.h" #include "safemath.h" +#include "stacktrace.h" #include "threadsuspend.h" #include "inlinetracking.h" #include "frozenobjectheap.h" @@ -8578,6 +8579,8 @@ HRESULT ProfToEEInterfaceImpl::DoStackSnapshot(ThreadID thread, CONTEXT ctxCurrent; memset(&ctxCurrent, 0, sizeof(ctxCurrent)); + CONTEXT ctxELT; + REGDISPLAY rd; PROFILER_STACK_WALK_DATA data; @@ -8716,6 +8719,17 @@ HRESULT ProfToEEInterfaceImpl::DoStackSnapshot(ThreadID thread, #endif // !PLATFORM_SUPPORTS_SAFE_THREADSUSPEND } + if ((pctxSeed == nullptr) && (pThreadToSnapshot == pCurrentThread)) + { + T_CONTEXT *pELTContext = pCurrentThread->GetProfilerELTContext(); + if (pELTContext != nullptr) + { + CopyOSContext(&ctxELT, pELTContext); + Thread::VirtualUnwindToFirstManagedCallFrame(&ctxELT); + pctxSeed = &ctxELT; + } + } + // If target thread is in pre-emptive mode, the profiler's seed context is unnecessary // because our frame chain is good enough: it will give us at least as accurate a // starting point as the profiler could. Also, since profiler contexts cannot be @@ -10694,6 +10708,21 @@ void __stdcall ProfilerUnmanagedToManagedTransitionMD(MethodDesc *pMD, // These do a lot of work for us, setting up Frames, gathering arg info and resolving generics. //******************************************************************************************* +#ifdef PROFILING_SUPPORTED +static void CaptureProfilerELTContext(T_CONTEXT *pContext) +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + MODE_COOPERATIVE; + } + CONTRACTL_END; + + ClrCaptureContext(pContext); +} +#endif // PROFILING_SUPPORTED + HCIMPL2(EXTERN_C void, ProfileEnter, UINT_PTR clientData, void * platformSpecificHandle) { FCALL_CONTRACT; @@ -10741,6 +10770,14 @@ HCIMPL2(EXTERN_C void, ProfileEnter, UINT_PTR clientData, void * platformSpecifi SetCallbackStateFlagsHolder csf( COR_PRF_CALLBACKSTATE_INCALLBACK); + T_CONTEXT *pProfilerELTContext = nullptr; + if (CORProfilerStackSnapshotEnabled()) + { + pProfilerELTContext = static_cast(_alloca(sizeof(T_CONTEXT))); + CaptureProfilerELTContext(pProfilerELTContext); + } + ProfilerELTContextHolder contextHolder(pProfilerELTContext); + COR_PRF_ELT_INFO_INTERNAL eltInfo; eltInfo.platformSpecificHandle = platformSpecificHandle; @@ -10908,6 +10945,14 @@ HCIMPL2(EXTERN_C void, ProfileLeave, UINT_PTR clientData, void * platformSpecifi SetCallbackStateFlagsHolder csf( COR_PRF_CALLBACKSTATE_INCALLBACK); + T_CONTEXT *pProfilerELTContext = nullptr; + if (CORProfilerStackSnapshotEnabled()) + { + pProfilerELTContext = static_cast(_alloca(sizeof(T_CONTEXT))); + CaptureProfilerELTContext(pProfilerELTContext); + } + ProfilerELTContextHolder contextHolder(pProfilerELTContext); + COR_PRF_ELT_INFO_INTERNAL eltInfo; eltInfo.platformSpecificHandle = platformSpecificHandle; @@ -11033,6 +11078,14 @@ HCIMPL2(EXTERN_C void, ProfileTailcall, UINT_PTR clientData, void * platformSpec SetCallbackStateFlagsHolder csf( COR_PRF_CALLBACKSTATE_INCALLBACK); + T_CONTEXT *pProfilerELTContext = nullptr; + if (CORProfilerStackSnapshotEnabled()) + { + pProfilerELTContext = static_cast(_alloca(sizeof(T_CONTEXT))); + CaptureProfilerELTContext(pProfilerELTContext); + } + ProfilerELTContextHolder contextHolder(pProfilerELTContext); + COR_PRF_ELT_INFO_INTERNAL eltInfo; eltInfo.platformSpecificHandle = platformSpecificHandle; diff --git a/src/coreclr/vm/threads.cpp b/src/coreclr/vm/threads.cpp index 4519b127bb3250..332b147a6764a9 100644 --- a/src/coreclr/vm/threads.cpp +++ b/src/coreclr/vm/threads.cpp @@ -1283,6 +1283,7 @@ Thread::Thread() } m_pProfilerFilterContext = NULL; + m_pProfilerELTContext = nullptr; #endif // PROFILING_SUPPORTED m_CacheStackBase = 0; diff --git a/src/coreclr/vm/threads.h b/src/coreclr/vm/threads.h index 96db33d50df211..393f874422a5ee 100644 --- a/src/coreclr/vm/threads.h +++ b/src/coreclr/vm/threads.h @@ -2746,6 +2746,12 @@ class Thread //--------------------------------------------------------------- T_CONTEXT *m_pProfilerFilterContext; + //--------------------------------------------------------------- + // Native context captured before invoking an ELT profiler callback. + // DoStackSnapshot may unwind it to seed a same-thread stack walk. + //--------------------------------------------------------------- + T_CONTEXT *m_pProfilerELTContext; + //--------------------------------------------------------------- // Bitmask to remember per-thread state useful for the profiler API. See // COR_PRF_CALLBACKSTATE_* flags in clr\src\inc\ProfilePriv.h for bit values. @@ -2836,6 +2842,20 @@ class Thread m_pProfilerFilterContext = pContext; } + void SetProfilerELTContext(T_CONTEXT *pContext) + { + LIMITED_METHOD_CONTRACT; + + m_pProfilerELTContext = pContext; + } + + T_CONTEXT *GetProfilerELTContext() + { + LIMITED_METHOD_CONTRACT; + + return m_pProfilerELTContext; + } + FORCEINLINE DWORD GetProfilerEvacuationCounter(size_t slot) { LIMITED_METHOD_CONTRACT; From 0a7ac907c32f1bdabaf080a17b26033503369880 Mon Sep 17 00:00:00 2001 From: mdh1418 Date: Fri, 14 Aug 2026 01:47:01 +0000 Subject: [PATCH 2/2] Test stack snapshots in slow ELT hooks Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../eltprofiler/slowpatheltprofiler.cpp | 52 ++++++++++++++++++- .../native/eltprofiler/slowpatheltprofiler.h | 12 ++++- 2 files changed, 62 insertions(+), 2 deletions(-) diff --git a/src/tests/profiler/native/eltprofiler/slowpatheltprofiler.cpp b/src/tests/profiler/native/eltprofiler/slowpatheltprofiler.cpp index ed2f2c6fc9ce1d..a4ad9fcf472ee7 100644 --- a/src/tests/profiler/native/eltprofiler/slowpatheltprofiler.cpp +++ b/src/tests/profiler/native/eltprofiler/slowpatheltprofiler.cpp @@ -97,7 +97,8 @@ HRESULT SlowPathELTProfiler::Initialize(IUnknown* pICorProfilerInfoUnk) if (FAILED(hr = pCorProfilerInfo->SetEventMask2(COR_PRF_MONITOR_ENTERLEAVE | COR_PRF_ENABLE_FUNCTION_ARGS | COR_PRF_ENABLE_FUNCTION_RETVAL - | COR_PRF_ENABLE_FRAME_INFO, + | COR_PRF_ENABLE_FRAME_INFO + | COR_PRF_ENABLE_STACK_SNAPSHOT, 0))) { wcout << L"FAIL: IpCorProfilerInfo::SetEventMask2() failed hr=0x" << std::hex << hr << endl; @@ -164,6 +165,8 @@ HRESULT STDMETHODCALLTYPE SlowPathELTProfiler::EnterCallback(FunctionIDOrClientI return S_OK; } + TestHookRestrictions(); + COR_PRF_FRAME_INFO frameInfo; ULONG pcbArgumentInfo = 0; NewArrayHolder pArgumentInfoBytes; @@ -422,6 +425,51 @@ HRESULT STDMETHODCALLTYPE SlowPathELTProfiler::EnterCallback(FunctionIDOrClientI return hr; } +HRESULT STDMETHODCALLTYPE SlowPathELTProfiler::StackSnapshotCallback( + FunctionID functionId, + UINT_PTR instructionPointer, + COR_PRF_FRAME_INFO frameInfo, + ULONG32 contextSize, + BYTE context[], + void *clientData) +{ + bool *sawManagedFrame = reinterpret_cast(clientData); + *sawManagedFrame |= functionId != 0; + return S_OK; +} + +void SlowPathELTProfiler::TestHookRestrictions() +{ + if (_testedHookRestrictions.exchange(true)) + { + return; + } + +#ifndef WIN32 + bool sawManagedFrame = false; + HRESULT hr = pCorProfilerInfo->DoStackSnapshot( + 0, + StackSnapshotCallback, + COR_PRF_SNAPSHOT_DEFAULT, + &sawManagedFrame, + nullptr, + 0); + if (hr != S_OK || !sawManagedFrame) + { + wcout << L"DoStackSnapshot from ELT hook failed hr=0x" << std::hex << hr + << L" sawManagedFrame=" << sawManagedFrame << endl; + _failures++; + } +#endif // WIN32 + + HRESULT forceGCHr = pCorProfilerInfo->ForceGC(); + if (forceGCHr != CORPROF_E_UNSUPPORTED_CALL_SEQUENCE) + { + wcout << L"ForceGC from ELT hook returned unexpected hr=0x" << std::hex << forceGCHr << endl; + _failures++; + } +} + HRESULT STDMETHODCALLTYPE SlowPathELTProfiler::LeaveCallback(FunctionIDOrClientID functionIdOrClientID, COR_PRF_ELT_INFO eltInfo) { if (_testType != TestType::LeaveHooks) @@ -429,6 +477,8 @@ HRESULT STDMETHODCALLTYPE SlowPathELTProfiler::LeaveCallback(FunctionIDOrClientI return S_OK; } + TestHookRestrictions(); + COR_PRF_FRAME_INFO frameInfo; COR_PRF_FUNCTION_ARGUMENT_RANGE * pRetvalRange = new COR_PRF_FUNCTION_ARGUMENT_RANGE; HRESULT hr = pCorProfilerInfo->GetFunctionLeave3Info(functionIdOrClientID.functionID, eltInfo, &frameInfo, pRetvalRange); diff --git a/src/tests/profiler/native/eltprofiler/slowpatheltprofiler.h b/src/tests/profiler/native/eltprofiler/slowpatheltprofiler.h index dbf0759117664b..67ff349b38727a 100644 --- a/src/tests/profiler/native/eltprofiler/slowpatheltprofiler.h +++ b/src/tests/profiler/native/eltprofiler/slowpatheltprofiler.h @@ -127,7 +127,7 @@ class SlowPathELTProfiler : public Profiler public: static std::shared_ptr s_profiler; - SlowPathELTProfiler() : Profiler(), _failures(0), _testType(TestType::Unknown) + SlowPathELTProfiler() : Profiler(), _failures(0), _testedHookRestrictions(false), _testType(TestType::Unknown) { _sawFuncEnter[L"SimpleArgsFunc"] = false; _sawFuncEnter[L"MixedStructFunc"] = false; @@ -185,11 +185,21 @@ class SlowPathELTProfiler : public Profiler }; std::atomic _failures; + std::atomic _testedHookRestrictions; std::unordered_map _sawFuncEnter; std::unordered_map _sawFuncLeave; TestType _testType; + static HRESULT STDMETHODCALLTYPE StackSnapshotCallback( + FunctionID functionId, + UINT_PTR instructionPointer, + COR_PRF_FRAME_INFO frameInfo, + ULONG32 contextSize, + BYTE context[], + void *clientData); + + void TestHookRestrictions(); void PrintBytes(const BYTE *bytes, size_t length); bool ValidateInt(UINT_PTR ptr, int expected);