From 292f799b94c28a9865add6d4e3e3778e60bf91de Mon Sep 17 00:00:00 2001 From: Roman Kennke Date: Fri, 20 Feb 2026 10:30:35 +0000 Subject: [PATCH 1/5] 8373578: JVMTI replacement for AsyncGetCallTrace --- src/hotspot/share/prims/jvmtiExtensions.cpp | 162 ++++++++++++++++++++ src/hotspot/share/prims/jvmtiExtensions.hpp | 20 +++ src/hotspot/share/runtime/init.cpp | 4 + 3 files changed, 186 insertions(+) diff --git a/src/hotspot/share/prims/jvmtiExtensions.cpp b/src/hotspot/share/prims/jvmtiExtensions.cpp index 855c7bd4eba5e..4cb8b96c79b94 100644 --- a/src/hotspot/share/prims/jvmtiExtensions.cpp +++ b/src/hotspot/share/prims/jvmtiExtensions.cpp @@ -30,6 +30,7 @@ #include "runtime/interfaceSupport.inline.hpp" #include "runtime/jniHandles.inline.hpp" #include "runtime/mountUnmountDisabler.hpp" +#include "runtime/stackWalker.hpp" // the list of extension functions GrowableArray* JvmtiExtensions::_ext_functions; @@ -37,6 +38,67 @@ GrowableArray* JvmtiExtensions::_ext_functions; // the list of extension events GrowableArray* JvmtiExtensions::_ext_events; +// async stack trace capability +bool JvmtiExtensions::_can_request_stack_trace = false; + +#ifdef LINUX +// JVMTI callback wrapper for StackWalker +class JvmtiStackWalkerCallback : public StackWalkerCallback { + jvmtiBeginStackTraceCallback _begin_callback; + jvmtiEndStackTraceCallback _end_callback; + jvmtiStackFrameCallback _frame_callback; + const void* _user_data; + JavaThread* _jt; + + static jvmtiFrameType convert_type(StackWalkerFrameType type) { + switch (type) { + case StackWalkerFrameType::FRAME_INTERPRETER: + case StackWalkerFrameType::FRAME_JIT: + case StackWalkerFrameType::FRAME_INLINE: + return JVMTI_JAVA_FRAME; + case StackWalkerFrameType::FRAME_NATIVE: + return JVMTI_NATIVE_FRAME; + } + ShouldNotReachHere(); + return JVMTI_JAVA_FRAME; + } + +public: + JvmtiStackWalkerCallback(jvmtiBeginStackTraceCallback begin_callback, + jvmtiEndStackTraceCallback end_callback, + jvmtiStackFrameCallback frame_callback, + const void* user_data) : + _begin_callback(begin_callback), + _end_callback(end_callback), + _frame_callback(frame_callback), + _user_data(user_data), + _jt(nullptr) {} + + void begin_stacktrace(JavaThread* jt, bool continuation, bool biased) override { + _jt = jt; + ThreadToNativeFromVM ttnfv(jt); + _begin_callback(false, biased, _user_data); + } + + void end_stacktrace(bool truncated) override { + ThreadToNativeFromVM ttnfv(_jt); + _end_callback(_user_data); + } + + void stack_frame(const Method* method, int bci, int line_no, StackWalkerFrameType type) override { + jvmtiFrameType frame_type = convert_type(type); + jlocation loc = bci; + jmethodID method_id = const_cast(method)->jmethod_id(); + ThreadToNativeFromVM ttnfv(_jt); + _frame_callback(frame_type, method_id, loc, _user_data); + } + + void failure() override { + // Nothing to report on failure + } +}; +#endif // LINUX + // // Extension Functions @@ -167,6 +229,72 @@ static jvmtiError JNICALL GetCarrierThread(const jvmtiEnv* env, ...) { return JVMTI_ERROR_NONE; } +// JvmtiEnv::RequestStackTrace(jthread thread, void* ucontext, , const void* user_data) { + +// Parameters: (thread, ucontext, begin_stack_trace_callback, end_stack_trace_callback, stack_frame_callback, user_data) +static jvmtiError JNICALL RequestStackTrace(const jvmtiEnv* env, ...) { + JvmtiEnv* jvmti_env = JvmtiEnv::JvmtiEnv_from_jvmti_env((jvmtiEnv*)env); + if (!JvmtiExtensions::can_request_stack_trace()) { + return JVMTI_ERROR_MUST_POSSESS_CAPABILITY; + } + + JavaThread* current_thread = JavaThread::current(); + HandleMark hm(current_thread); + jthread thread = nullptr; + void* ucontext; + jvmtiBeginStackTraceCallback begin_stack_trace_callback; + jvmtiEndStackTraceCallback end_stack_trace_callback; + jvmtiStackFrameCallback stack_frame_callback; + const void* user_data; + + va_list ap; + + va_start(ap, env); + thread = va_arg(ap, jthread); + ucontext = va_arg(ap, void*); + begin_stack_trace_callback = va_arg(ap, jvmtiBeginStackTraceCallback); + end_stack_trace_callback = va_arg(ap, jvmtiEndStackTraceCallback); + stack_frame_callback = va_arg(ap, jvmtiStackFrameCallback); + user_data = va_arg(ap, const void*); + va_end(ap); + +#ifdef LINUX + if (thread == nullptr) { + // Use StackWalker API directly. + // Note: StackWalker::initialize() is called in init_globals2() after + // InitializeRequestStackTrace sets can_request_stack_trace(). + StackWalkRequest request; + request.set_max_frames(512); + request.construct_callback( + begin_stack_trace_callback, end_stack_trace_callback, + stack_frame_callback, user_data); + StackWalker::request_stack_trace(request, current_thread, ucontext, true /* suspended */); + return JVMTI_ERROR_NONE; + } +#endif + return JVMTI_ERROR_UNSUPPORTED_OPERATION; +} + +// No parameters. +static jvmtiError JNICALL InitializeRequestStackTrace(const jvmtiEnv* env, ...) { + // Set the flag so post_initialize() will initialize the StackWalker. + // Note: This is typically called during Agent_OnLoad before the VM is fully + // initialized, so we cannot initialize StackWalker here directly. + JvmtiExtensions::set_can_request_stack_trace(true); + return JVMTI_ERROR_NONE; +} + +// Called from init_globals2() after VM initialization is complete. +void JvmtiExtensions::post_initialize() { +#ifdef LINUX + // Initialize the StackWalker if JVMTI requested async stack traces. + // This must happen after VM initialization is complete (BarrierSet created). + if (JvmtiExtensions::can_request_stack_trace()) { + StackWalker::initialize(); + } +#endif +} + // register extension functions and events. In this implementation we // have a single extension function (to prove the API) that tests if class // unloading is enabled or disabled. We also have a single extension event @@ -189,6 +317,18 @@ void JvmtiExtensions::register_extensions() { { (char*)"GetCarrierThread", JVMTI_KIND_IN, JVMTI_TYPE_JTHREAD, JNI_FALSE }, { (char*)"GetCarrierThread", JVMTI_KIND_OUT, JVMTI_TYPE_JTHREAD, JNI_FALSE } }; + // RequestStackTrace + static jvmtiParamInfo func_params3[] = { + { (char*)"thread", JVMTI_KIND_IN, JVMTI_TYPE_JTHREAD, JNI_TRUE }, + { (char*)"ucontext", JVMTI_KIND_OUT_BUF, JVMTI_TYPE_CVOID, JNI_TRUE }, + { (char*)"begin_stack_trace_callback", JVMTI_KIND_OUT_BUF, JVMTI_TYPE_CVOID, JNI_FALSE }, + { (char*)"end_stack_trace_callback", JVMTI_KIND_OUT_BUF, JVMTI_TYPE_CVOID, JNI_FALSE }, + { (char*)"stack_frame_callback", JVMTI_KIND_OUT_BUF, JVMTI_TYPE_CVOID, JNI_FALSE }, + { (char*)"user_data", JVMTI_KIND_IN_BUF, JVMTI_TYPE_CVOID, JNI_TRUE } + }; + // InitializeRequestStackTrace + static jvmtiParamInfo func_params4[] = { + }; static jvmtiError errors[] = { JVMTI_ERROR_MUST_POSSESS_CAPABILITY, @@ -225,9 +365,31 @@ void JvmtiExtensions::register_extensions() { errors }; + static jvmtiExtensionFunctionInfo ext_func3 = { + (jvmtiExtensionFunction)RequestStackTrace, + (char*)"com.sun.hotspot.functions.RequestStackTrace", + (char*)"Request a stacktrace to be emitted via callbacks", + sizeof(func_params3)/sizeof(func_params3[0]), + func_params3, + sizeof(errors)/sizeof(jvmtiError), // non-universal errors + errors + }; + + static jvmtiExtensionFunctionInfo ext_func4 = { + (jvmtiExtensionFunction)InitializeRequestStackTrace, + (char*)"com.sun.hotspot.functions.InitializeRequestStackTrace", + (char*)"Initializes the VM to enable requesting a stacktrace via RequestStackTrace", + sizeof(func_params4)/sizeof(func_params4[0]), + func_params4, + sizeof(errors)/sizeof(jvmtiError), // non-universal errors + errors + }; + _ext_functions->append(&ext_func0); _ext_functions->append(&ext_func1); _ext_functions->append(&ext_func2); + _ext_functions->append(&ext_func3); + _ext_functions->append(&ext_func4); // register our extension event diff --git a/src/hotspot/share/prims/jvmtiExtensions.hpp b/src/hotspot/share/prims/jvmtiExtensions.hpp index d44c6dd4f5188..0dd33d3cfb350 100644 --- a/src/hotspot/share/prims/jvmtiExtensions.hpp +++ b/src/hotspot/share/prims/jvmtiExtensions.hpp @@ -29,6 +29,20 @@ #include "jvmtifiles/jvmtiEnv.hpp" #include "memory/allStatic.hpp" +enum jvmtiFrameType { + JVMTI_JAVA_FRAME, + JVMTI_NATIVE_FRAME +}; + +typedef void (JNICALL *jvmtiBeginStackTraceCallback) + (jboolean failed, jboolean biased, const void* user_data); + +typedef void (JNICALL *jvmtiEndStackTraceCallback) + (const void* user_data); + +typedef jvmtiIterationControl (JNICALL *jvmtiStackFrameCallback) + (jvmtiFrameType frame_type, jmethodID method, jlocation location, const void* user_data); + // JvmtiExtensions // // Maintains the list of extension functions and events in this JVMTI @@ -40,8 +54,11 @@ class JvmtiExtensions : public AllStatic { private: static GrowableArray* _ext_functions; static GrowableArray* _ext_events; + static bool _can_request_stack_trace; public: + static bool can_request_stack_trace() { return _can_request_stack_trace; } + static void set_can_request_stack_trace(bool value) { _can_request_stack_trace = value; } // register extensions function static void register_extensions(); @@ -56,6 +73,9 @@ class JvmtiExtensions : public AllStatic { // sets the callback function for an extension event and enables the event static jvmtiError set_event_callback(JvmtiEnv* env, jint extension_event_index, jvmtiExtensionEvent callback); + + // called after VM initialization to perform deferred initialization + static void post_initialize(); }; #endif // SHARE_PRIMS_JVMTIEXTENSIONS_HPP diff --git a/src/hotspot/share/runtime/init.cpp b/src/hotspot/share/runtime/init.cpp index 4f470043e8980..b56fac4d8e3e4 100644 --- a/src/hotspot/share/runtime/init.cpp +++ b/src/hotspot/share/runtime/init.cpp @@ -205,6 +205,10 @@ jint init_globals2() { final_stubs_init(); // final StubRoutines stubs MethodHandles::generate_adapters(); +#if INCLUDE_JVMTI + JvmtiExtensions::post_initialize(); +#endif + // All the flags that get adjusted by VM_Version_init and os::init_2 // have been set so dump the flags now. if (PrintFlagsFinal || PrintFlagsRanges) { From f8bb9c46e8fe54f9893eefc00b14b99d4f03ef1a Mon Sep 17 00:00:00 2001 From: Roman Kennke Date: Fri, 20 Feb 2026 15:54:00 +0000 Subject: [PATCH 2/5] Fix release build --- src/hotspot/share/runtime/stackWalker.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/hotspot/share/runtime/stackWalker.cpp b/src/hotspot/share/runtime/stackWalker.cpp index 0890ea4085fe8..daebe6f62c3be 100644 --- a/src/hotspot/share/runtime/stackWalker.cpp +++ b/src/hotspot/share/runtime/stackWalker.cpp @@ -491,9 +491,11 @@ static bool in_stack(intptr_t* ptr, const JavaThread* jt) { return jt->is_in_full_stack_checked(reinterpret_cast
(ptr)); } +#ifdef ASSERT static bool sp_in_stack(const StackWalkRequest& request, const JavaThread* jt) { return in_stack(static_cast(request.sample_sp()), jt); } +#endif // ASSERT static bool fp_in_stack(const StackWalkRequest& request, const JavaThread* jt) { return in_stack(static_cast(request.sample_bcp()), jt); From 2cd961ea2237f813c31008d0498e2b6a02fc6974 Mon Sep 17 00:00:00 2001 From: Roman Kennke Date: Fri, 6 Mar 2026 13:34:00 +0000 Subject: [PATCH 3/5] Some safety checks in JVMTI RequestStackTrace --- src/hotspot/share/prims/jvmtiExtensions.cpp | 22 ++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/src/hotspot/share/prims/jvmtiExtensions.cpp b/src/hotspot/share/prims/jvmtiExtensions.cpp index 4cb8b96c79b94..75d60758bc13a 100644 --- a/src/hotspot/share/prims/jvmtiExtensions.cpp +++ b/src/hotspot/share/prims/jvmtiExtensions.cpp @@ -238,8 +238,24 @@ static jvmtiError JNICALL RequestStackTrace(const jvmtiEnv* env, ...) { return JVMTI_ERROR_MUST_POSSESS_CAPABILITY; } - JavaThread* current_thread = JavaThread::current(); - HandleMark hm(current_thread); + // Use current_or_null_safe() because this is called from a signal handler + // and the signal may fire on a thread that is detaching from the VM. + Thread* current = Thread::current_or_null_safe(); + if (current == nullptr || !current->is_Java_thread()) { + return JVMTI_ERROR_WRONG_PHASE; + } + + JavaThread* java_thread = JavaThread::cast(current); + + // Filter out threads that are exiting or excluded, matching the JFR CPU + // time sampler's get_java_thread_if_valid() checks. + if (java_thread->is_exiting() || + java_thread->is_hidden_from_external_view() || + java_thread->jfr_thread_local()->is_excluded()) { + return JVMTI_ERROR_WRONG_PHASE; + } + + HandleMark hm(java_thread); jthread thread = nullptr; void* ucontext; jvmtiBeginStackTraceCallback begin_stack_trace_callback; @@ -268,7 +284,7 @@ static jvmtiError JNICALL RequestStackTrace(const jvmtiEnv* env, ...) { request.construct_callback( begin_stack_trace_callback, end_stack_trace_callback, stack_frame_callback, user_data); - StackWalker::request_stack_trace(request, current_thread, ucontext, true /* suspended */); + StackWalker::request_stack_trace(request, java_thread, ucontext, true /* suspended */); return JVMTI_ERROR_NONE; } #endif From a81e24985f496ad83542522be97f4b3bf667913c Mon Sep 17 00:00:00 2001 From: Roman Kennke Date: Tue, 10 Mar 2026 09:00:48 +0000 Subject: [PATCH 4/5] Fix Zero build --- src/hotspot/share/prims/jvmtiExtensions.cpp | 25 +++++++++++++-------- src/hotspot/share/prims/jvmtiExtensions.hpp | 3 ++- 2 files changed, 18 insertions(+), 10 deletions(-) diff --git a/src/hotspot/share/prims/jvmtiExtensions.cpp b/src/hotspot/share/prims/jvmtiExtensions.cpp index 75d60758bc13a..fbe4ee9842622 100644 --- a/src/hotspot/share/prims/jvmtiExtensions.cpp +++ b/src/hotspot/share/prims/jvmtiExtensions.cpp @@ -30,7 +30,11 @@ #include "runtime/interfaceSupport.inline.hpp" #include "runtime/jniHandles.inline.hpp" #include "runtime/mountUnmountDisabler.hpp" +#include "utilities/macros.hpp" + +#if INCLUDE_STACKWALKER #include "runtime/stackWalker.hpp" +#endif // the list of extension functions GrowableArray* JvmtiExtensions::_ext_functions; @@ -38,10 +42,10 @@ GrowableArray* JvmtiExtensions::_ext_functions; // the list of extension events GrowableArray* JvmtiExtensions::_ext_events; +#if INCLUDE_STACKWALKER // async stack trace capability bool JvmtiExtensions::_can_request_stack_trace = false; -#ifdef LINUX // JVMTI callback wrapper for StackWalker class JvmtiStackWalkerCallback : public StackWalkerCallback { jvmtiBeginStackTraceCallback _begin_callback; @@ -97,8 +101,7 @@ class JvmtiStackWalkerCallback : public StackWalkerCallback { // Nothing to report on failure } }; -#endif // LINUX - +#endif // INCLUDE_STACKWALKER // // Extension Functions @@ -229,6 +232,7 @@ static jvmtiError JNICALL GetCarrierThread(const jvmtiEnv* env, ...) { return JVMTI_ERROR_NONE; } +#if INCLUDE_STACKWALKER // JvmtiEnv::RequestStackTrace(jthread thread, void* ucontext, , const void* user_data) { // Parameters: (thread, ucontext, begin_stack_trace_callback, end_stack_trace_callback, stack_frame_callback, user_data) @@ -250,8 +254,7 @@ static jvmtiError JNICALL RequestStackTrace(const jvmtiEnv* env, ...) { // Filter out threads that are exiting or excluded, matching the JFR CPU // time sampler's get_java_thread_if_valid() checks. if (java_thread->is_exiting() || - java_thread->is_hidden_from_external_view() || - java_thread->jfr_thread_local()->is_excluded()) { + java_thread->is_hidden_from_external_view()) { return JVMTI_ERROR_WRONG_PHASE; } @@ -274,7 +277,6 @@ static jvmtiError JNICALL RequestStackTrace(const jvmtiEnv* env, ...) { user_data = va_arg(ap, const void*); va_end(ap); -#ifdef LINUX if (thread == nullptr) { // Use StackWalker API directly. // Note: StackWalker::initialize() is called in init_globals2() after @@ -287,7 +289,6 @@ static jvmtiError JNICALL RequestStackTrace(const jvmtiEnv* env, ...) { StackWalker::request_stack_trace(request, java_thread, ucontext, true /* suspended */); return JVMTI_ERROR_NONE; } -#endif return JVMTI_ERROR_UNSUPPORTED_OPERATION; } @@ -302,14 +303,13 @@ static jvmtiError JNICALL InitializeRequestStackTrace(const jvmtiEnv* env, ...) // Called from init_globals2() after VM initialization is complete. void JvmtiExtensions::post_initialize() { -#ifdef LINUX // Initialize the StackWalker if JVMTI requested async stack traces. // This must happen after VM initialization is complete (BarrierSet created). if (JvmtiExtensions::can_request_stack_trace()) { StackWalker::initialize(); } -#endif } +#endif // register extension functions and events. In this implementation we // have a single extension function (to prove the API) that tests if class @@ -333,6 +333,8 @@ void JvmtiExtensions::register_extensions() { { (char*)"GetCarrierThread", JVMTI_KIND_IN, JVMTI_TYPE_JTHREAD, JNI_FALSE }, { (char*)"GetCarrierThread", JVMTI_KIND_OUT, JVMTI_TYPE_JTHREAD, JNI_FALSE } }; + +#if INCLUDE_STACKWALKER // RequestStackTrace static jvmtiParamInfo func_params3[] = { { (char*)"thread", JVMTI_KIND_IN, JVMTI_TYPE_JTHREAD, JNI_TRUE }, @@ -345,6 +347,7 @@ void JvmtiExtensions::register_extensions() { // InitializeRequestStackTrace static jvmtiParamInfo func_params4[] = { }; +#endif static jvmtiError errors[] = { JVMTI_ERROR_MUST_POSSESS_CAPABILITY, @@ -381,6 +384,7 @@ void JvmtiExtensions::register_extensions() { errors }; +#if INCLUDE_STACKWALKER static jvmtiExtensionFunctionInfo ext_func3 = { (jvmtiExtensionFunction)RequestStackTrace, (char*)"com.sun.hotspot.functions.RequestStackTrace", @@ -400,12 +404,15 @@ void JvmtiExtensions::register_extensions() { sizeof(errors)/sizeof(jvmtiError), // non-universal errors errors }; +#endif // INCLUDE_STACKWALKER _ext_functions->append(&ext_func0); _ext_functions->append(&ext_func1); _ext_functions->append(&ext_func2); +#if INCLUDE_STACKWALKER _ext_functions->append(&ext_func3); _ext_functions->append(&ext_func4); +#endif // register our extension event diff --git a/src/hotspot/share/prims/jvmtiExtensions.hpp b/src/hotspot/share/prims/jvmtiExtensions.hpp index 0dd33d3cfb350..f97dfd9b1142d 100644 --- a/src/hotspot/share/prims/jvmtiExtensions.hpp +++ b/src/hotspot/share/prims/jvmtiExtensions.hpp @@ -75,7 +75,8 @@ class JvmtiExtensions : public AllStatic { jvmtiExtensionEvent callback); // called after VM initialization to perform deferred initialization - static void post_initialize(); + static void post_initialize() NOT_STACKWALKER_RETURN(); + }; #endif // SHARE_PRIMS_JVMTIEXTENSIONS_HPP From 06e262b894fa50268d45a3901e9b3ec46937837c Mon Sep 17 00:00:00 2001 From: Roman Kennke Date: Tue, 10 Mar 2026 12:03:09 +0000 Subject: [PATCH 5/5] Fix Windows build --- src/hotspot/share/prims/jvmtiExtensions.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/hotspot/share/prims/jvmtiExtensions.cpp b/src/hotspot/share/prims/jvmtiExtensions.cpp index fbe4ee9842622..8813ec4679626 100644 --- a/src/hotspot/share/prims/jvmtiExtensions.cpp +++ b/src/hotspot/share/prims/jvmtiExtensions.cpp @@ -345,8 +345,7 @@ void JvmtiExtensions::register_extensions() { { (char*)"user_data", JVMTI_KIND_IN_BUF, JVMTI_TYPE_CVOID, JNI_TRUE } }; // InitializeRequestStackTrace - static jvmtiParamInfo func_params4[] = { - }; + static jvmtiParamInfo* func_params4 = nullptr; #endif static jvmtiError errors[] = { @@ -399,7 +398,7 @@ void JvmtiExtensions::register_extensions() { (jvmtiExtensionFunction)InitializeRequestStackTrace, (char*)"com.sun.hotspot.functions.InitializeRequestStackTrace", (char*)"Initializes the VM to enable requesting a stacktrace via RequestStackTrace", - sizeof(func_params4)/sizeof(func_params4[0]), + 0, func_params4, sizeof(errors)/sizeof(jvmtiError), // non-universal errors errors