Fri, 12 Jun 2020 02:59:56 +0100
8233197: Invert JvmtiExport::post_vm_initialized() and Jfr:on_vm_start() start-up order for correct option parsing
8246703: [TESTBUG] Add test for JDK-8233197
Reviewed-by: aph, adinn, neugens
apetushkov@9858 | 1 | /* |
jbachorik@9925 | 2 | * Copyright (c) 2016, 2019, Oracle and/or its affiliates. All rights reserved. |
apetushkov@9858 | 3 | * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
apetushkov@9858 | 4 | * |
apetushkov@9858 | 5 | * This code is free software; you can redistribute it and/or modify it |
apetushkov@9858 | 6 | * under the terms of the GNU General Public License version 2 only, as |
apetushkov@9858 | 7 | * published by the Free Software Foundation. |
apetushkov@9858 | 8 | * |
apetushkov@9858 | 9 | * This code is distributed in the hope that it will be useful, but WITHOUT |
apetushkov@9858 | 10 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
apetushkov@9858 | 11 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
apetushkov@9858 | 12 | * version 2 for more details (a copy is included in the LICENSE file that |
apetushkov@9858 | 13 | * accompanied this code). |
apetushkov@9858 | 14 | * |
apetushkov@9858 | 15 | * You should have received a copy of the GNU General Public License version |
apetushkov@9858 | 16 | * 2 along with this work; if not, write to the Free Software Foundation, |
apetushkov@9858 | 17 | * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. |
apetushkov@9858 | 18 | * |
apetushkov@9858 | 19 | * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA |
apetushkov@9858 | 20 | * or visit www.oracle.com if you need additional information or have any |
apetushkov@9858 | 21 | * questions. |
apetushkov@9858 | 22 | * |
apetushkov@9858 | 23 | */ |
apetushkov@9858 | 24 | |
apetushkov@9858 | 25 | #include "precompiled.hpp" |
apetushkov@9858 | 26 | #include "jvm.h" |
apetushkov@9858 | 27 | #include "jfr/instrumentation/jfrJvmtiAgent.hpp" |
apetushkov@9858 | 28 | #include "jfr/jni/jfrJavaSupport.hpp" |
apetushkov@9858 | 29 | #include "jfr/jni/jfrUpcalls.hpp" |
apetushkov@9858 | 30 | #include "jfr/recorder/checkpoint/types/traceid/jfrTraceId.inline.hpp" |
apetushkov@9858 | 31 | #include "jfr/recorder/service/jfrOptionSet.hpp" |
apetushkov@9858 | 32 | #include "jfr/support/jfrEventClass.hpp" |
apetushkov@9858 | 33 | #include "memory/resourceArea.hpp" |
jbachorik@9925 | 34 | #include "prims/jvmtiEnvBase.hpp" |
apetushkov@9858 | 35 | #include "prims/jvmtiExport.hpp" |
jbachorik@9925 | 36 | #include "prims/jvmtiUtil.hpp" |
apetushkov@9858 | 37 | #include "runtime/interfaceSupport.hpp" |
apetushkov@9858 | 38 | #include "runtime/thread.inline.hpp" |
apetushkov@9858 | 39 | #include "utilities/exceptions.hpp" |
apetushkov@9858 | 40 | |
apetushkov@9858 | 41 | static const size_t ERROR_MSG_BUFFER_SIZE = 256; |
apetushkov@9858 | 42 | static JfrJvmtiAgent* agent = NULL; |
apetushkov@9858 | 43 | static jvmtiEnv* jfr_jvmti_env = NULL; |
apetushkov@9858 | 44 | |
apetushkov@9858 | 45 | static void check_jvmti_error(jvmtiEnv* jvmti, jvmtiError errnum, const char* str) { |
apetushkov@9858 | 46 | if (errnum != JVMTI_ERROR_NONE) { |
apetushkov@9858 | 47 | char* errnum_str = NULL; |
apetushkov@9858 | 48 | jvmti->GetErrorName(errnum, &errnum_str); |
apetushkov@9858 | 49 | if (true) tty->print_cr("ERROR: JfrJvmtiAgent: " INT32_FORMAT " (%s): %s\n", |
apetushkov@9858 | 50 | errnum, |
apetushkov@9858 | 51 | NULL == errnum_str ? "Unknown" : errnum_str, |
apetushkov@9858 | 52 | NULL == str ? "" : str); |
apetushkov@9858 | 53 | } |
apetushkov@9858 | 54 | } |
apetushkov@9858 | 55 | |
jbachorik@9925 | 56 | static bool set_event_notification_mode(jvmtiEventMode mode, |
jbachorik@9925 | 57 | jvmtiEvent event, |
jbachorik@9925 | 58 | jthread event_thread, |
jbachorik@9925 | 59 | ...) { |
jbachorik@9925 | 60 | assert(jfr_jvmti_env != NULL, "invariant"); |
apetushkov@9858 | 61 | const jvmtiError jvmti_ret_code = jfr_jvmti_env->SetEventNotificationMode(mode, event, event_thread); |
apetushkov@9858 | 62 | check_jvmti_error(jfr_jvmti_env, jvmti_ret_code, "SetEventNotificationMode"); |
jbachorik@9925 | 63 | return jvmti_ret_code == JVMTI_ERROR_NONE; |
apetushkov@9858 | 64 | } |
apetushkov@9858 | 65 | |
jbachorik@9925 | 66 | static bool update_class_file_load_hook_event(jvmtiEventMode mode) { |
apetushkov@9858 | 67 | return set_event_notification_mode(mode, JVMTI_EVENT_CLASS_FILE_LOAD_HOOK, NULL); |
apetushkov@9858 | 68 | } |
apetushkov@9858 | 69 | |
apetushkov@9858 | 70 | static JavaThread* current_java_thread() { |
apetushkov@9858 | 71 | Thread* this_thread = Thread::current(); |
apetushkov@9858 | 72 | assert(this_thread != NULL && this_thread->is_Java_thread(), "invariant"); |
apetushkov@9858 | 73 | return static_cast<JavaThread*>(this_thread); |
apetushkov@9858 | 74 | } |
apetushkov@9858 | 75 | |
apetushkov@9858 | 76 | // jvmti event callbacks require C linkage |
apetushkov@9858 | 77 | extern "C" void JNICALL jfr_on_class_file_load_hook(jvmtiEnv *jvmti_env, |
apetushkov@9858 | 78 | JNIEnv* jni_env, |
apetushkov@9858 | 79 | jclass class_being_redefined, |
apetushkov@9858 | 80 | jobject loader, |
apetushkov@9858 | 81 | const char* name, |
apetushkov@9858 | 82 | jobject protection_domain, |
apetushkov@9858 | 83 | jint class_data_len, |
apetushkov@9858 | 84 | const unsigned char* class_data, |
apetushkov@9858 | 85 | jint* new_class_data_len, |
apetushkov@9858 | 86 | unsigned char** new_class_data) { |
apetushkov@9858 | 87 | if (class_being_redefined == NULL) { |
apetushkov@9858 | 88 | return; |
apetushkov@9858 | 89 | } |
apetushkov@9858 | 90 | JavaThread* jt = JavaThread::thread_from_jni_environment(jni_env); |
apetushkov@9858 | 91 | DEBUG_ONLY(JfrJavaSupport::check_java_thread_in_native(jt));; |
apetushkov@9858 | 92 | ThreadInVMfromNative tvmfn(jt); |
apetushkov@9858 | 93 | JfrUpcalls::on_retransform(JfrTraceId::get(class_being_redefined), |
apetushkov@9858 | 94 | class_being_redefined, |
apetushkov@9858 | 95 | class_data_len, |
apetushkov@9858 | 96 | class_data, |
apetushkov@9858 | 97 | new_class_data_len, |
apetushkov@9858 | 98 | new_class_data, |
apetushkov@9858 | 99 | jt); |
apetushkov@9858 | 100 | } |
apetushkov@9858 | 101 | |
apetushkov@9858 | 102 | // caller needs ResourceMark |
apetushkov@9858 | 103 | static jclass* create_classes_array(jint classes_count, TRAPS) { |
apetushkov@9858 | 104 | assert(classes_count > 0, "invariant"); |
apetushkov@9858 | 105 | DEBUG_ONLY(JfrJavaSupport::check_java_thread_in_native(THREAD)); |
apetushkov@9858 | 106 | ThreadInVMfromNative tvmfn((JavaThread*)THREAD); |
apetushkov@9858 | 107 | jclass* const classes = NEW_RESOURCE_ARRAY_IN_THREAD_RETURN_NULL(THREAD, jclass, classes_count); |
apetushkov@9858 | 108 | if (NULL == classes) { |
apetushkov@9858 | 109 | char error_buffer[ERROR_MSG_BUFFER_SIZE]; |
apetushkov@9858 | 110 | jio_snprintf(error_buffer, ERROR_MSG_BUFFER_SIZE, |
apetushkov@9858 | 111 | "Thread local allocation (native) of " SIZE_FORMAT " bytes failed " |
apetushkov@9858 | 112 | "in retransform classes", sizeof(jclass) * classes_count); |
apetushkov@9858 | 113 | if (true) tty->print_cr("%s", error_buffer); |
apetushkov@9858 | 114 | JfrJavaSupport::throw_out_of_memory_error(error_buffer, CHECK_NULL); |
apetushkov@9858 | 115 | } |
apetushkov@9858 | 116 | return classes; |
apetushkov@9858 | 117 | } |
apetushkov@9858 | 118 | |
jbachorik@9925 | 119 | // caller needs ResourceMark |
jbachorik@9925 | 120 | static void log_and_throw(jvmtiError error, TRAPS) { |
apetushkov@9858 | 121 | if (!HAS_PENDING_EXCEPTION) { |
apetushkov@9858 | 122 | DEBUG_ONLY(JfrJavaSupport::check_java_thread_in_native(THREAD)); |
apetushkov@9858 | 123 | ThreadInVMfromNative tvmfn((JavaThread*)THREAD); |
jbachorik@9925 | 124 | const char base_error_msg[] = "JfrJvmtiAgent::retransformClasses failed: "; |
jbachorik@9925 | 125 | size_t length = sizeof base_error_msg; // includes terminating null |
jbachorik@9925 | 126 | const char* const jvmti_error_name = JvmtiUtil::error_name(error); |
jbachorik@9925 | 127 | assert(jvmti_error_name != NULL, "invariant"); |
jbachorik@9925 | 128 | length += strlen(jvmti_error_name); |
jbachorik@9925 | 129 | char* error_msg = NEW_RESOURCE_ARRAY(char, length); |
jbachorik@9925 | 130 | jio_snprintf(error_msg, length, "%s%s", base_error_msg, jvmti_error_name); |
jbachorik@9925 | 131 | if (JVMTI_ERROR_INVALID_CLASS_FORMAT == error) { |
jbachorik@9925 | 132 | JfrJavaSupport::throw_class_format_error(error_msg, THREAD); |
jbachorik@9925 | 133 | } else { |
jbachorik@9925 | 134 | JfrJavaSupport::throw_runtime_exception(error_msg, THREAD); |
jbachorik@9925 | 135 | } |
apetushkov@9858 | 136 | } |
apetushkov@9858 | 137 | } |
apetushkov@9858 | 138 | |
apetushkov@9858 | 139 | static void check_exception_and_log(JNIEnv* env, TRAPS) { |
apetushkov@9858 | 140 | assert(env != NULL, "invariant"); |
apetushkov@9858 | 141 | if (env->ExceptionOccurred()) { |
apetushkov@9858 | 142 | // array index out of bound |
apetushkov@9858 | 143 | DEBUG_ONLY(JfrJavaSupport::check_java_thread_in_native(THREAD)); |
apetushkov@9858 | 144 | ThreadInVMfromNative tvmfn((JavaThread*)THREAD); |
apetushkov@9858 | 145 | if (true) tty->print_cr("GetObjectArrayElement threw an exception"); |
apetushkov@9858 | 146 | return; |
apetushkov@9858 | 147 | } |
apetushkov@9858 | 148 | } |
apetushkov@9858 | 149 | |
jbachorik@9925 | 150 | static bool is_valid_jvmti_phase() { |
jbachorik@9925 | 151 | return JvmtiEnvBase::get_phase() == JVMTI_PHASE_LIVE; |
jbachorik@9925 | 152 | } |
jbachorik@9925 | 153 | |
apetushkov@9858 | 154 | void JfrJvmtiAgent::retransform_classes(JNIEnv* env, jobjectArray classes_array, TRAPS) { |
apetushkov@9858 | 155 | assert(env != NULL, "invariant"); |
jbachorik@9925 | 156 | assert(classes_array != NULL, "invariant"); |
jbachorik@9925 | 157 | assert(is_valid_jvmti_phase(), "invariant"); |
apetushkov@9858 | 158 | DEBUG_ONLY(JfrJavaSupport::check_java_thread_in_native(THREAD)); |
apetushkov@9858 | 159 | const jint classes_count = env->GetArrayLength(classes_array); |
apetushkov@9858 | 160 | if (classes_count <= 0) { |
apetushkov@9858 | 161 | return; |
apetushkov@9858 | 162 | } |
apetushkov@9858 | 163 | ResourceMark rm(THREAD); |
apetushkov@9858 | 164 | jclass* const classes = create_classes_array(classes_count, CHECK); |
apetushkov@9858 | 165 | assert(classes != NULL, "invariant"); |
apetushkov@9858 | 166 | for (jint i = 0; i < classes_count; i++) { |
apetushkov@9858 | 167 | jclass clz = (jclass)env->GetObjectArrayElement(classes_array, i); |
apetushkov@9858 | 168 | check_exception_and_log(env, THREAD); |
jbachorik@9925 | 169 | classes[i] = clz; |
jbachorik@9925 | 170 | } |
jbachorik@9925 | 171 | { |
apetushkov@9858 | 172 | // inspecting the oop/klass requires a thread transition |
jbachorik@9925 | 173 | ThreadInVMfromNative transition((JavaThread*)THREAD); |
jbachorik@9925 | 174 | for (jint i = 0; i < classes_count; ++i) { |
jbachorik@9925 | 175 | jclass clz = classes[i]; |
jbachorik@9925 | 176 | if (!JdkJfrEvent::is_a(clz)) { |
apetushkov@9858 | 177 | // outside the event hierarchy |
apetushkov@9858 | 178 | JdkJfrEvent::tag_as_host(clz); |
apetushkov@9858 | 179 | } |
apetushkov@9858 | 180 | } |
apetushkov@9858 | 181 | } |
jbachorik@9925 | 182 | DEBUG_ONLY(JfrJavaSupport::check_java_thread_in_native(THREAD)); |
jbachorik@9925 | 183 | const jvmtiError result = jfr_jvmti_env->RetransformClasses(classes_count, classes); |
jbachorik@9925 | 184 | if (result != JVMTI_ERROR_NONE) { |
jbachorik@9925 | 185 | log_and_throw(result, THREAD); |
apetushkov@9858 | 186 | } |
apetushkov@9858 | 187 | } |
apetushkov@9858 | 188 | |
jbachorik@9925 | 189 | static bool register_callbacks(JavaThread* jt) { |
apetushkov@9858 | 190 | assert(jfr_jvmti_env != NULL, "invariant"); |
apetushkov@9858 | 191 | DEBUG_ONLY(JfrJavaSupport::check_java_thread_in_native(jt)); |
apetushkov@9858 | 192 | jvmtiEventCallbacks callbacks; |
apetushkov@9858 | 193 | /* Set callbacks */ |
apetushkov@9858 | 194 | memset(&callbacks, 0, sizeof(callbacks)); |
apetushkov@9858 | 195 | callbacks.ClassFileLoadHook = jfr_on_class_file_load_hook; |
apetushkov@9858 | 196 | const jvmtiError jvmti_ret_code = jfr_jvmti_env->SetEventCallbacks(&callbacks, sizeof(callbacks)); |
apetushkov@9858 | 197 | check_jvmti_error(jfr_jvmti_env, jvmti_ret_code, "SetEventCallbacks"); |
jbachorik@9925 | 198 | return jvmti_ret_code == JVMTI_ERROR_NONE; |
apetushkov@9858 | 199 | } |
apetushkov@9858 | 200 | |
jbachorik@9925 | 201 | static bool register_capabilities(JavaThread* jt) { |
apetushkov@9858 | 202 | assert(jfr_jvmti_env != NULL, "invariant"); |
apetushkov@9858 | 203 | DEBUG_ONLY(JfrJavaSupport::check_java_thread_in_native(jt)); |
apetushkov@9858 | 204 | jvmtiCapabilities capabilities; |
apetushkov@9858 | 205 | /* Add JVMTI capabilities */ |
apetushkov@9858 | 206 | (void)memset(&capabilities, 0, sizeof(capabilities)); |
apetushkov@9858 | 207 | capabilities.can_retransform_classes = 1; |
apetushkov@9858 | 208 | capabilities.can_retransform_any_class = 1; |
apetushkov@9858 | 209 | const jvmtiError jvmti_ret_code = jfr_jvmti_env->AddCapabilities(&capabilities); |
apetushkov@9858 | 210 | check_jvmti_error(jfr_jvmti_env, jvmti_ret_code, "Add Capabilities"); |
jbachorik@9925 | 211 | return jvmti_ret_code == JVMTI_ERROR_NONE; |
apetushkov@9858 | 212 | } |
apetushkov@9858 | 213 | |
apetushkov@9858 | 214 | static jint create_jvmti_env(JavaThread* jt) { |
apetushkov@9858 | 215 | assert(jfr_jvmti_env == NULL, "invariant"); |
apetushkov@9858 | 216 | DEBUG_ONLY(JfrJavaSupport::check_java_thread_in_native(jt)); |
apetushkov@9858 | 217 | extern struct JavaVM_ main_vm; |
apetushkov@9858 | 218 | JavaVM* vm = &main_vm; |
apetushkov@9858 | 219 | return vm->GetEnv((void **)&jfr_jvmti_env, JVMTI_VERSION); |
apetushkov@9858 | 220 | } |
apetushkov@9858 | 221 | |
jbachorik@9925 | 222 | static bool unregister_callbacks(JavaThread* jt) { |
jbachorik@9925 | 223 | assert(jfr_jvmti_env != NULL, "invariant"); |
apetushkov@9858 | 224 | jvmtiEventCallbacks callbacks; |
apetushkov@9858 | 225 | /* Set empty callbacks */ |
apetushkov@9858 | 226 | memset(&callbacks, 0, sizeof(callbacks)); |
apetushkov@9858 | 227 | const jvmtiError jvmti_ret_code = jfr_jvmti_env->SetEventCallbacks(&callbacks, sizeof(callbacks)); |
apetushkov@9858 | 228 | check_jvmti_error(jfr_jvmti_env, jvmti_ret_code, "SetEventCallbacks"); |
jbachorik@9925 | 229 | return jvmti_ret_code == JVMTI_ERROR_NONE; |
apetushkov@9858 | 230 | } |
apetushkov@9858 | 231 | |
apetushkov@9858 | 232 | JfrJvmtiAgent::JfrJvmtiAgent() {} |
apetushkov@9858 | 233 | |
apetushkov@9858 | 234 | JfrJvmtiAgent::~JfrJvmtiAgent() { |
apetushkov@9858 | 235 | JavaThread* jt = current_java_thread(); |
apetushkov@9858 | 236 | DEBUG_ONLY(JfrJavaSupport::check_java_thread_in_vm(jt)); |
apetushkov@9858 | 237 | if (jfr_jvmti_env != NULL) { |
jbachorik@9925 | 238 | ThreadToNativeFromVM transition(jt); |
jbachorik@9925 | 239 | update_class_file_load_hook_event(JVMTI_DISABLE); |
jbachorik@9925 | 240 | unregister_callbacks(jt); |
apetushkov@9858 | 241 | jfr_jvmti_env->DisposeEnvironment(); |
apetushkov@9858 | 242 | jfr_jvmti_env = NULL; |
apetushkov@9858 | 243 | } |
apetushkov@9858 | 244 | } |
apetushkov@9858 | 245 | |
jbachorik@9925 | 246 | static bool initialize(JavaThread* jt) { |
apetushkov@9858 | 247 | assert(jt != NULL, "invariant"); |
apetushkov@9858 | 248 | DEBUG_ONLY(JfrJavaSupport::check_java_thread_in_vm(jt)); |
apetushkov@9858 | 249 | ThreadToNativeFromVM transition(jt); |
apetushkov@9858 | 250 | if (create_jvmti_env(jt) != JNI_OK) { |
apetushkov@9858 | 251 | assert(jfr_jvmti_env == NULL, "invariant"); |
apetushkov@9858 | 252 | return false; |
apetushkov@9858 | 253 | } |
apetushkov@9858 | 254 | assert(jfr_jvmti_env != NULL, "invariant"); |
jbachorik@9925 | 255 | if (!register_capabilities(jt)) { |
apetushkov@9858 | 256 | return false; |
apetushkov@9858 | 257 | } |
jbachorik@9925 | 258 | if (!register_callbacks(jt)) { |
apetushkov@9858 | 259 | return false; |
apetushkov@9858 | 260 | } |
jbachorik@9925 | 261 | return update_class_file_load_hook_event(JVMTI_ENABLE); |
jbachorik@9925 | 262 | } |
jbachorik@9925 | 263 | |
jbachorik@9925 | 264 | static void log_and_throw_illegal_state_exception(TRAPS) { |
jbachorik@9925 | 265 | DEBUG_ONLY(JfrJavaSupport::check_java_thread_in_vm(THREAD)); |
jbachorik@9925 | 266 | const char* const illegal_state_msg = "An attempt was made to start JFR too early in the VM initialization sequence."; |
jbachorik@9925 | 267 | if (true) { |
jbachorik@9925 | 268 | tty->print_cr("%s\n", illegal_state_msg); |
jbachorik@9925 | 269 | tty->print_cr("JFR uses JVMTI RetransformClasses and requires the JVMTI state to have entered JVMTI_PHASE_LIVE.\n"); |
jbachorik@9925 | 270 | tty->print_cr("Please initialize JFR in response to event JVMTI_EVENT_VM_INIT instead of JVMTI_EVENT_VM_START.\n"); |
apetushkov@9858 | 271 | } |
jbachorik@9925 | 272 | JfrJavaSupport::throw_illegal_state_exception(illegal_state_msg, THREAD); |
apetushkov@9858 | 273 | } |
apetushkov@9858 | 274 | |
apetushkov@9858 | 275 | bool JfrJvmtiAgent::create() { |
jbachorik@9925 | 276 | assert(agent == NULL, "invariant"); |
jbachorik@9925 | 277 | JavaThread* const jt = current_java_thread(); |
jbachorik@9925 | 278 | if (!is_valid_jvmti_phase()) { |
jbachorik@9925 | 279 | log_and_throw_illegal_state_exception(jt); |
jbachorik@9925 | 280 | return false; |
jbachorik@9925 | 281 | } |
apetushkov@9858 | 282 | agent = new JfrJvmtiAgent(); |
apetushkov@9858 | 283 | if (agent == NULL) { |
apetushkov@9858 | 284 | return false; |
apetushkov@9858 | 285 | } |
jbachorik@9925 | 286 | if (!initialize(jt)) { |
apetushkov@9858 | 287 | delete agent; |
apetushkov@9858 | 288 | agent = NULL; |
apetushkov@9858 | 289 | return false; |
apetushkov@9858 | 290 | } |
apetushkov@9858 | 291 | return true; |
apetushkov@9858 | 292 | } |
apetushkov@9858 | 293 | |
apetushkov@9858 | 294 | void JfrJvmtiAgent::destroy() { |
apetushkov@9858 | 295 | if (agent != NULL) { |
apetushkov@9858 | 296 | delete agent; |
apetushkov@9858 | 297 | agent = NULL; |
apetushkov@9858 | 298 | } |
apetushkov@9858 | 299 | } |