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