diff --git a/src/hotspot/share/prims/jvmtiExtensions.cpp b/src/hotspot/share/prims/jvmtiExtensions.cpp index 855c7bd4eba5e..8813ec4679626 100644 --- a/src/hotspot/share/prims/jvmtiExtensions.cpp +++ b/src/hotspot/share/prims/jvmtiExtensions.cpp @@ -30,6 +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; @@ -37,6 +42,66 @@ 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; + +// 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 // INCLUDE_STACKWALKER // // Extension Functions @@ -167,6 +232,85 @@ 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) +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; + } + + // 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()) { + return JVMTI_ERROR_WRONG_PHASE; + } + + HandleMark hm(java_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); + + 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, java_thread, ucontext, true /* suspended */); + return JVMTI_ERROR_NONE; + } + 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() { + // 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 @@ -190,6 +334,20 @@ void JvmtiExtensions::register_extensions() { { (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 }, + { (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 = nullptr; +#endif + static jvmtiError errors[] = { JVMTI_ERROR_MUST_POSSESS_CAPABILITY, JVMTI_ERROR_INVALID_THREAD @@ -225,9 +383,35 @@ void JvmtiExtensions::register_extensions() { errors }; +#if INCLUDE_STACKWALKER + 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", + 0, + func_params4, + 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 d44c6dd4f5188..f97dfd9b1142d 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,10 @@ 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() NOT_STACKWALKER_RETURN(); + }; #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) {