| /* |
| * Copyright (c) 2003, 2023, Oracle and/or its affiliates. All rights reserved. |
| * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
| * |
| * This code is free software; you can redistribute it and/or modify it |
| * under the terms of the GNU General Public License version 2 only, as |
| * published by the Free Software Foundation. |
| * |
| * This code is distributed in the hope that it will be useful, but WITHOUT |
| * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
| * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
| * version 2 for more details (a copy is included in the LICENSE file that |
| * accompanied this code). |
| * |
| * You should have received a copy of the GNU General Public License version |
| * 2 along with this work; if not, write to the Free Software Foundation, |
| * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. |
| * |
| * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA |
| * or visit www.oracle.com if you need additional information or have any |
| * questions. |
| * |
| */ |
| |
| #include "precompiled.hpp" |
| #include "classfile/javaClasses.inline.hpp" |
| #include "prims/jvmtiExport.hpp" |
| #include "prims/jvmtiExtensions.hpp" |
| #include "prims/jvmtiThreadState.inline.hpp" |
| #include "runtime/handles.inline.hpp" |
| #include "runtime/interfaceSupport.inline.hpp" |
| #include "runtime/jniHandles.inline.hpp" |
| |
| // the list of extension functions |
| GrowableArray<jvmtiExtensionFunctionInfo*>* JvmtiExtensions::_ext_functions; |
| |
| // the list of extension events |
| GrowableArray<jvmtiExtensionEventInfo*>* JvmtiExtensions::_ext_events; |
| |
| |
| // |
| // Extension Functions |
| // |
| static jvmtiError JNICALL IsClassUnloadingEnabled(const jvmtiEnv* env, ...) { |
| jboolean* enabled = nullptr; |
| va_list ap; |
| |
| va_start(ap, env); |
| enabled = va_arg(ap, jboolean *); |
| va_end(ap); |
| |
| if (enabled == nullptr) { |
| return JVMTI_ERROR_NULL_POINTER; |
| } |
| *enabled = (jboolean)ClassUnloading; |
| return JVMTI_ERROR_NONE; |
| } |
| |
| // Parameters: (jthread thread, jthread* vthread_ptr) |
| static jvmtiError JNICALL GetVirtualThread(const jvmtiEnv* env, ...) { |
| JvmtiEnv* jvmti_env = JvmtiEnv::JvmtiEnv_from_jvmti_env((jvmtiEnv*)env); |
| if (jvmti_env->get_capabilities()->can_support_virtual_threads == 0) { |
| return JVMTI_ERROR_MUST_POSSESS_CAPABILITY; |
| } |
| |
| JavaThread* current_thread = JavaThread::current(); |
| ResourceMark rm(current_thread); |
| jthread thread = nullptr; |
| jthread* vthread_ptr = nullptr; |
| JavaThread* java_thread = nullptr; |
| oop cthread_oop = nullptr; |
| oop thread_oop = nullptr; |
| va_list ap; |
| |
| va_start(ap, env); |
| thread = va_arg(ap, jthread); |
| vthread_ptr = va_arg(ap, jthread*); |
| va_end(ap); |
| |
| ThreadInVMfromNative tiv(current_thread); |
| JvmtiVTMSTransitionDisabler disabler; |
| ThreadsListHandle tlh(current_thread); |
| |
| jvmtiError err; |
| |
| if (thread == nullptr) { |
| java_thread = current_thread; |
| cthread_oop = java_thread->threadObj(); |
| } else { |
| err = JvmtiExport::cv_external_thread_to_JavaThread(tlh.list(), thread, &java_thread, &cthread_oop); |
| if (err != JVMTI_ERROR_NONE) { |
| return err; |
| } |
| } |
| if (vthread_ptr == nullptr) { |
| return JVMTI_ERROR_NULL_POINTER; |
| } |
| if (cthread_oop == nullptr || java_lang_VirtualThread::is_instance(cthread_oop)) { |
| return JVMTI_ERROR_INVALID_THREAD; |
| } |
| *vthread_ptr = nullptr; |
| |
| JvmtiThreadState *state = JvmtiThreadState::state_for(java_thread); |
| if (state == nullptr) { |
| return JVMTI_ERROR_THREAD_NOT_ALIVE; |
| } |
| oop vthread_oop = java_thread->jvmti_vthread(); |
| if (!java_lang_VirtualThread::is_instance(vthread_oop)) { // not a virtual thread |
| vthread_oop = nullptr; |
| } |
| *vthread_ptr = (jthread)JNIHandles::make_local(current_thread, vthread_oop); |
| return JVMTI_ERROR_NONE; |
| } |
| |
| // Parameters: (jthread vthread, jthread* thread_ptr) |
| static jvmtiError JNICALL GetCarrierThread(const jvmtiEnv* env, ...) { |
| JvmtiEnv* jvmti_env = JvmtiEnv::JvmtiEnv_from_jvmti_env((jvmtiEnv*)env); |
| if (jvmti_env->get_capabilities()->can_support_virtual_threads == 0) { |
| return JVMTI_ERROR_MUST_POSSESS_CAPABILITY; |
| } |
| |
| JavaThread* current_thread = JavaThread::current(); |
| HandleMark hm(current_thread); |
| jthread vthread = nullptr; |
| jthread* thread_ptr = nullptr; |
| va_list ap; |
| |
| va_start(ap, env); |
| vthread = va_arg(ap, jthread); |
| thread_ptr = va_arg(ap, jthread*); |
| va_end(ap); |
| |
| ThreadInVMfromNative tiv(current_thread); |
| JvmtiVTMSTransitionDisabler disabler; |
| |
| ThreadsListHandle tlh(current_thread); |
| JavaThread* java_thread; |
| oop vthread_oop = nullptr; |
| |
| if (vthread == nullptr) { |
| vthread = (jthread)JNIHandles::make_local(current_thread, JvmtiEnvBase::get_vthread_or_thread_oop(current_thread)); |
| } |
| jvmtiError err = JvmtiExport::cv_external_thread_to_JavaThread(tlh.list(), vthread, &java_thread, &vthread_oop); |
| if (err != JVMTI_ERROR_NONE) { |
| // We got an error code so we don't have a JavaThread *, but |
| // only return an error from here if we didn't get a valid |
| // thread_oop. |
| // In a vthread case the cv_external_thread_to_JavaThread is expected to correctly set |
| // the thread_oop and return JVMTI_ERROR_INVALID_THREAD which we ignore here. |
| if (vthread_oop == nullptr) { |
| return err; |
| } |
| } |
| |
| if (!java_lang_VirtualThread::is_instance(vthread_oop)) { |
| return JVMTI_ERROR_INVALID_THREAD; |
| } |
| if (thread_ptr == nullptr) { |
| return JVMTI_ERROR_NULL_POINTER; |
| } |
| VirtualThreadGetThreadClosure op(Handle(current_thread, vthread_oop), thread_ptr); |
| Handshake::execute(&op, &tlh, current_thread); |
| return op.result(); |
| } |
| |
| // 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 |
| // EXT_EVENT_CLASS_UNLOAD which is used to provide the JVMDI_EVENT_CLASS_UNLOAD |
| // event. The function and the event are registered here. |
| // |
| void JvmtiExtensions::register_extensions() { |
| _ext_functions = new (mtServiceability) GrowableArray<jvmtiExtensionFunctionInfo*>(1, mtServiceability); |
| _ext_events = new (mtServiceability) GrowableArray<jvmtiExtensionEventInfo*>(1, mtServiceability); |
| |
| // Register our extension functions. |
| static jvmtiParamInfo func_params0[] = { |
| { (char*)"IsClassUnloadingEnabled", JVMTI_KIND_OUT, JVMTI_TYPE_JBOOLEAN, JNI_FALSE } |
| }; |
| static jvmtiParamInfo func_params1[] = { |
| { (char*)"GetVirtualThread", JVMTI_KIND_IN, JVMTI_TYPE_JTHREAD, JNI_FALSE }, |
| { (char*)"GetVirtualThread", JVMTI_KIND_OUT, JVMTI_TYPE_JTHREAD, JNI_FALSE } |
| }; |
| static jvmtiParamInfo func_params2[] = { |
| { (char*)"GetCarrierThread", JVMTI_KIND_IN, JVMTI_TYPE_JTHREAD, JNI_FALSE }, |
| { (char*)"GetCarrierThread", JVMTI_KIND_OUT, JVMTI_TYPE_JTHREAD, JNI_FALSE } |
| }; |
| |
| static jvmtiError errors[] = { |
| JVMTI_ERROR_MUST_POSSESS_CAPABILITY, |
| JVMTI_ERROR_INVALID_THREAD |
| }; |
| |
| static jvmtiExtensionFunctionInfo ext_func0 = { |
| (jvmtiExtensionFunction)IsClassUnloadingEnabled, |
| (char*)"com.sun.hotspot.functions.IsClassUnloadingEnabled", |
| (char*)"Tell if class unloading is enabled (-noclassgc)", |
| sizeof(func_params0)/sizeof(func_params0[0]), |
| func_params0, |
| 0, // no non-universal errors |
| nullptr |
| }; |
| |
| static jvmtiExtensionFunctionInfo ext_func1 = { |
| (jvmtiExtensionFunction)GetVirtualThread, |
| (char*)"com.sun.hotspot.functions.GetVirtualThread", |
| (char*)"Get virtual thread executed on carrier thread", |
| sizeof(func_params1)/sizeof(func_params1[0]), |
| func_params1, |
| sizeof(errors)/sizeof(jvmtiError), // non-universal errors |
| errors |
| }; |
| |
| static jvmtiExtensionFunctionInfo ext_func2 = { |
| (jvmtiExtensionFunction)GetCarrierThread, |
| (char*)"com.sun.hotspot.functions.GetCarrierThread", |
| (char*)"Get carrier thread executing virtual thread", |
| sizeof(func_params2)/sizeof(func_params2[0]), |
| func_params2, |
| sizeof(errors)/sizeof(jvmtiError), // non-universal errors |
| errors |
| }; |
| |
| _ext_functions->append(&ext_func0); |
| _ext_functions->append(&ext_func1); |
| _ext_functions->append(&ext_func2); |
| |
| // register our extension event |
| |
| static jvmtiParamInfo class_unload_event_params[] = { |
| { (char*)"JNI Environment", JVMTI_KIND_IN_PTR, JVMTI_TYPE_JNIENV, JNI_FALSE }, |
| { (char*)"Class", JVMTI_KIND_IN_PTR, JVMTI_TYPE_CCHAR, JNI_FALSE } |
| }; |
| static jvmtiParamInfo virtual_thread_event_params[] = { |
| { (char*)"JNI Environment", JVMTI_KIND_IN_PTR, JVMTI_TYPE_JNIENV, JNI_FALSE }, |
| { (char*)"Virtual Thread", JVMTI_KIND_IN, JVMTI_TYPE_JTHREAD, JNI_FALSE } |
| }; |
| |
| static jvmtiExtensionEventInfo class_unload_ext_event = { |
| EXT_EVENT_CLASS_UNLOAD, |
| (char*)"com.sun.hotspot.events.ClassUnload", |
| (char*)"CLASS_UNLOAD event", |
| sizeof(class_unload_event_params)/sizeof(class_unload_event_params[0]), |
| class_unload_event_params |
| }; |
| static jvmtiExtensionEventInfo virtual_thread_mount_ext_event = { |
| EXT_EVENT_VIRTUAL_THREAD_MOUNT, |
| (char*)"com.sun.hotspot.events.VirtualThreadMount", |
| (char*)"VIRTUAL_THREAD_MOUNT event", |
| sizeof(virtual_thread_event_params)/sizeof(virtual_thread_event_params[0]), |
| virtual_thread_event_params |
| }; |
| static jvmtiExtensionEventInfo virtual_thread_unmount_ext_event = { |
| EXT_EVENT_VIRTUAL_THREAD_UNMOUNT, |
| (char*)"com.sun.hotspot.events.VirtualThreadUnmount", |
| (char*)"VIRTUAL_THREAD_UNMOUNT event", |
| sizeof(virtual_thread_event_params)/sizeof(virtual_thread_event_params[0]), |
| virtual_thread_event_params |
| }; |
| |
| _ext_events->append(&class_unload_ext_event); |
| _ext_events->append(&virtual_thread_mount_ext_event); |
| _ext_events->append(&virtual_thread_unmount_ext_event); |
| } |
| |
| |
| // return the list of extension functions |
| |
| jvmtiError JvmtiExtensions::get_functions(JvmtiEnv* env, |
| jint* extension_count_ptr, |
| jvmtiExtensionFunctionInfo** extensions) |
| { |
| guarantee(_ext_functions != nullptr, "registration not done"); |
| |
| ResourceTracker rt(env); |
| |
| jvmtiExtensionFunctionInfo* ext_funcs; |
| jvmtiError err = rt.allocate(_ext_functions->length() * |
| sizeof(jvmtiExtensionFunctionInfo), |
| (unsigned char**)&ext_funcs); |
| if (err != JVMTI_ERROR_NONE) { |
| return err; |
| } |
| |
| for (int i=0; i<_ext_functions->length(); i++ ) { |
| ext_funcs[i].func = _ext_functions->at(i)->func; |
| |
| char *id = _ext_functions->at(i)->id; |
| err = rt.allocate(strlen(id)+1, (unsigned char**)&(ext_funcs[i].id)); |
| if (err != JVMTI_ERROR_NONE) { |
| return err; |
| } |
| strcpy(ext_funcs[i].id, id); |
| |
| char *desc = _ext_functions->at(i)->short_description; |
| err = rt.allocate(strlen(desc)+1, |
| (unsigned char**)&(ext_funcs[i].short_description)); |
| if (err != JVMTI_ERROR_NONE) { |
| return err; |
| } |
| strcpy(ext_funcs[i].short_description, desc); |
| |
| // params |
| |
| jint param_count = _ext_functions->at(i)->param_count; |
| |
| ext_funcs[i].param_count = param_count; |
| if (param_count == 0) { |
| ext_funcs[i].params = nullptr; |
| } else { |
| err = rt.allocate(param_count*sizeof(jvmtiParamInfo), |
| (unsigned char**)&(ext_funcs[i].params)); |
| if (err != JVMTI_ERROR_NONE) { |
| return err; |
| } |
| jvmtiParamInfo* src_params = _ext_functions->at(i)->params; |
| jvmtiParamInfo* dst_params = ext_funcs[i].params; |
| |
| for (int j=0; j<param_count; j++) { |
| err = rt.allocate(strlen(src_params[j].name)+1, |
| (unsigned char**)&(dst_params[j].name)); |
| if (err != JVMTI_ERROR_NONE) { |
| return err; |
| } |
| strcpy(dst_params[j].name, src_params[j].name); |
| |
| dst_params[j].kind = src_params[j].kind; |
| dst_params[j].base_type = src_params[j].base_type; |
| dst_params[j].null_ok = src_params[j].null_ok; |
| } |
| } |
| |
| // errors |
| |
| jint error_count = _ext_functions->at(i)->error_count; |
| ext_funcs[i].error_count = error_count; |
| if (error_count == 0) { |
| ext_funcs[i].errors = nullptr; |
| } else { |
| err = rt.allocate(error_count*sizeof(jvmtiError), |
| (unsigned char**)&(ext_funcs[i].errors)); |
| if (err != JVMTI_ERROR_NONE) { |
| return err; |
| } |
| memcpy(ext_funcs[i].errors, _ext_functions->at(i)->errors, |
| error_count*sizeof(jvmtiError)); |
| } |
| } |
| |
| *extension_count_ptr = _ext_functions->length(); |
| *extensions = ext_funcs; |
| return JVMTI_ERROR_NONE; |
| } |
| |
| |
| // return the list of extension events |
| |
| jvmtiError JvmtiExtensions::get_events(JvmtiEnv* env, |
| jint* extension_count_ptr, |
| jvmtiExtensionEventInfo** extensions) |
| { |
| guarantee(_ext_events != nullptr, "registration not done"); |
| |
| ResourceTracker rt(env); |
| |
| jvmtiExtensionEventInfo* ext_events; |
| jvmtiError err = rt.allocate(_ext_events->length() * sizeof(jvmtiExtensionEventInfo), |
| (unsigned char**)&ext_events); |
| if (err != JVMTI_ERROR_NONE) { |
| return err; |
| } |
| |
| for (int i=0; i<_ext_events->length(); i++ ) { |
| ext_events[i].extension_event_index = _ext_events->at(i)->extension_event_index; |
| |
| char *id = _ext_events->at(i)->id; |
| err = rt.allocate(strlen(id)+1, (unsigned char**)&(ext_events[i].id)); |
| if (err != JVMTI_ERROR_NONE) { |
| return err; |
| } |
| strcpy(ext_events[i].id, id); |
| |
| char *desc = _ext_events->at(i)->short_description; |
| err = rt.allocate(strlen(desc)+1, |
| (unsigned char**)&(ext_events[i].short_description)); |
| if (err != JVMTI_ERROR_NONE) { |
| return err; |
| } |
| strcpy(ext_events[i].short_description, desc); |
| |
| // params |
| |
| jint param_count = _ext_events->at(i)->param_count; |
| |
| ext_events[i].param_count = param_count; |
| if (param_count == 0) { |
| ext_events[i].params = nullptr; |
| } else { |
| err = rt.allocate(param_count*sizeof(jvmtiParamInfo), |
| (unsigned char**)&(ext_events[i].params)); |
| if (err != JVMTI_ERROR_NONE) { |
| return err; |
| } |
| jvmtiParamInfo* src_params = _ext_events->at(i)->params; |
| jvmtiParamInfo* dst_params = ext_events[i].params; |
| |
| for (int j=0; j<param_count; j++) { |
| err = rt.allocate(strlen(src_params[j].name)+1, |
| (unsigned char**)&(dst_params[j].name)); |
| if (err != JVMTI_ERROR_NONE) { |
| return err; |
| } |
| strcpy(dst_params[j].name, src_params[j].name); |
| |
| dst_params[j].kind = src_params[j].kind; |
| dst_params[j].base_type = src_params[j].base_type; |
| dst_params[j].null_ok = src_params[j].null_ok; |
| } |
| } |
| } |
| |
| *extension_count_ptr = _ext_events->length(); |
| *extensions = ext_events; |
| return JVMTI_ERROR_NONE; |
| } |
| |
| // set callback for an extension event and enable/disable it. |
| |
| jvmtiError JvmtiExtensions::set_event_callback(JvmtiEnv* env, |
| jint extension_event_index, |
| jvmtiExtensionEvent callback) |
| { |
| guarantee(_ext_events != nullptr, "registration not done"); |
| |
| jvmtiExtensionEventInfo* event = nullptr; |
| |
| // if there are extension events registered then validate that the |
| // extension_event_index matches one of the registered events. |
| if (_ext_events != nullptr) { |
| for (int i=0; i<_ext_events->length(); i++ ) { |
| if (_ext_events->at(i)->extension_event_index == extension_event_index) { |
| event = _ext_events->at(i); |
| break; |
| } |
| } |
| } |
| |
| // invalid event index |
| if (event == nullptr) { |
| return JVMTI_ERROR_ILLEGAL_ARGUMENT; |
| } |
| |
| JvmtiEventController::set_extension_event_callback(env, extension_event_index, |
| callback); |
| |
| return JVMTI_ERROR_NONE; |
| } |