/* * Copyright (C) 2017 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "common_helper.h" #include "jni.h" #include "jvmti.h" #include "jvmti_helper.h" #include "scoped_local_ref.h" #include "test_env.h" namespace art { namespace common_trace { struct TraceData { jclass test_klass; jmethodID enter_method; jmethodID exit_method; jmethodID field_access; jmethodID field_modify; jmethodID single_step; bool in_callback; bool access_watch_on_load; bool modify_watch_on_load; }; static void singleStepCB(jvmtiEnv* jvmti, JNIEnv* jnienv, jthread thread, jmethodID method, jlocation location) { TraceData* data = nullptr; if (JvmtiErrorToException(jnienv, jvmti, jvmti->GetEnvironmentLocalStorage(reinterpret_cast(&data)))) { return; } if (data->in_callback) { return; } CHECK(data->single_step != nullptr); data->in_callback = true; jobject method_arg = GetJavaMethod(jvmti, jnienv, method); jnienv->CallStaticVoidMethod(data->test_klass, data->single_step, thread, method_arg, static_cast(location)); jnienv->DeleteLocalRef(method_arg); data->in_callback = false; } static void fieldAccessCB(jvmtiEnv* jvmti, JNIEnv* jnienv, jthread thr ATTRIBUTE_UNUSED, jmethodID method, jlocation location, jclass field_klass, jobject object, jfieldID field) { TraceData* data = nullptr; if (JvmtiErrorToException(jnienv, jvmti, jvmti->GetEnvironmentLocalStorage(reinterpret_cast(&data)))) { return; } if (data->in_callback) { // Don't do callback for either of these to prevent an infinite loop. return; } CHECK(data->field_access != nullptr); data->in_callback = true; jobject method_arg = GetJavaMethod(jvmti, jnienv, method); jobject field_arg = GetJavaField(jvmti, jnienv, field_klass, field); jnienv->CallStaticVoidMethod(data->test_klass, data->field_access, method_arg, static_cast(location), field_klass, object, field_arg); jnienv->DeleteLocalRef(method_arg); jnienv->DeleteLocalRef(field_arg); data->in_callback = false; } static void fieldModificationCB(jvmtiEnv* jvmti, JNIEnv* jnienv, jthread thr ATTRIBUTE_UNUSED, jmethodID method, jlocation location, jclass field_klass, jobject object, jfieldID field, char type_char, jvalue new_value) { TraceData* data = nullptr; if (JvmtiErrorToException(jnienv, jvmti, jvmti->GetEnvironmentLocalStorage(reinterpret_cast(&data)))) { return; } if (data->in_callback) { // Don't do callback recursively to prevent an infinite loop. return; } CHECK(data->field_modify != nullptr); data->in_callback = true; jobject method_arg = GetJavaMethod(jvmti, jnienv, method); jobject field_arg = GetJavaField(jvmti, jnienv, field_klass, field); jobject value = GetJavaValueByType(jnienv, type_char, new_value); if (jnienv->ExceptionCheck()) { data->in_callback = false; jnienv->DeleteLocalRef(method_arg); jnienv->DeleteLocalRef(field_arg); return; } jnienv->CallStaticVoidMethod(data->test_klass, data->field_modify, method_arg, static_cast(location), field_klass, object, field_arg, value); jnienv->DeleteLocalRef(method_arg); jnienv->DeleteLocalRef(field_arg); data->in_callback = false; } static void methodExitCB(jvmtiEnv* jvmti, JNIEnv* jnienv, jthread thr ATTRIBUTE_UNUSED, jmethodID method, jboolean was_popped_by_exception, jvalue return_value) { TraceData* data = nullptr; if (JvmtiErrorToException(jnienv, jvmti, jvmti->GetEnvironmentLocalStorage(reinterpret_cast(&data)))) { return; } if (method == data->exit_method || method == data->enter_method || data->in_callback) { // Don't do callback for either of these to prevent an infinite loop. return; } CHECK(data->exit_method != nullptr); data->in_callback = true; jobject method_arg = GetJavaMethod(jvmti, jnienv, method); jobject result = was_popped_by_exception ? nullptr : GetJavaValue(jvmti, jnienv, method, return_value); if (jnienv->ExceptionCheck()) { data->in_callback = false; return; } jnienv->CallStaticVoidMethod(data->test_klass, data->exit_method, method_arg, was_popped_by_exception, result); jnienv->DeleteLocalRef(method_arg); data->in_callback = false; } static void methodEntryCB(jvmtiEnv* jvmti, JNIEnv* jnienv, jthread thr ATTRIBUTE_UNUSED, jmethodID method) { TraceData* data = nullptr; if (JvmtiErrorToException(jnienv, jvmti, jvmti->GetEnvironmentLocalStorage(reinterpret_cast(&data)))) { return; } CHECK(data->enter_method != nullptr); if (method == data->exit_method || method == data->enter_method || data->in_callback) { // Don't do callback for either of these to prevent an infinite loop. return; } data->in_callback = true; jobject method_arg = GetJavaMethod(jvmti, jnienv, method); if (jnienv->ExceptionCheck()) { return; } jnienv->CallStaticVoidMethod(data->test_klass, data->enter_method, method_arg); jnienv->DeleteLocalRef(method_arg); data->in_callback = false; } static void classPrepareCB(jvmtiEnv* jvmti, JNIEnv* jnienv, jthread thr ATTRIBUTE_UNUSED, jclass klass) { TraceData* data = nullptr; if (JvmtiErrorToException(jnienv, jvmti, jvmti->GetEnvironmentLocalStorage(reinterpret_cast(&data)))) { return; } if (data->access_watch_on_load || data->modify_watch_on_load) { jint nfields; jfieldID* fields; if (JvmtiErrorToException(jnienv, jvmti, jvmti->GetClassFields(klass, &nfields, &fields))) { return; } for (jint i = 0; i < nfields; i++) { jfieldID f = fields[i]; // Ignore errors if (data->access_watch_on_load) { jvmti->SetFieldAccessWatch(klass, f); } if (data->modify_watch_on_load) { jvmti->SetFieldModificationWatch(klass, f); } } jvmti->Deallocate(reinterpret_cast(fields)); } } extern "C" JNIEXPORT void JNICALL Java_art_Trace_watchAllFieldAccesses(JNIEnv* env) { TraceData* data = nullptr; if (JvmtiErrorToException( env, jvmti_env, jvmti_env->GetEnvironmentLocalStorage(reinterpret_cast(&data)))) { return; } data->access_watch_on_load = true; // We need the classPrepareCB to watch new fields as the classes are loaded/prepared. if (JvmtiErrorToException(env, jvmti_env, jvmti_env->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_CLASS_PREPARE, nullptr))) { return; } jint nklasses; jclass* klasses; if (JvmtiErrorToException(env, jvmti_env, jvmti_env->GetLoadedClasses(&nklasses, &klasses))) { return; } for (jint i = 0; i < nklasses; i++) { jclass k = klasses[i]; jint nfields; jfieldID* fields; jvmtiError err = jvmti_env->GetClassFields(k, &nfields, &fields); if (err == JVMTI_ERROR_CLASS_NOT_PREPARED) { continue; } else if (JvmtiErrorToException(env, jvmti_env, err)) { jvmti_env->Deallocate(reinterpret_cast(klasses)); return; } for (jint j = 0; j < nfields; j++) { jvmti_env->SetFieldAccessWatch(k, fields[j]); } jvmti_env->Deallocate(reinterpret_cast(fields)); } jvmti_env->Deallocate(reinterpret_cast(klasses)); } extern "C" JNIEXPORT void JNICALL Java_art_Trace_watchAllFieldModifications(JNIEnv* env) { TraceData* data = nullptr; if (JvmtiErrorToException( env, jvmti_env, jvmti_env->GetEnvironmentLocalStorage(reinterpret_cast(&data)))) { return; } data->modify_watch_on_load = true; // We need the classPrepareCB to watch new fields as the classes are loaded/prepared. if (JvmtiErrorToException(env, jvmti_env, jvmti_env->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_CLASS_PREPARE, nullptr))) { return; } jint nklasses; jclass* klasses; if (JvmtiErrorToException(env, jvmti_env, jvmti_env->GetLoadedClasses(&nklasses, &klasses))) { return; } for (jint i = 0; i < nklasses; i++) { jclass k = klasses[i]; jint nfields; jfieldID* fields; jvmtiError err = jvmti_env->GetClassFields(k, &nfields, &fields); if (err == JVMTI_ERROR_CLASS_NOT_PREPARED) { continue; } else if (JvmtiErrorToException(env, jvmti_env, err)) { jvmti_env->Deallocate(reinterpret_cast(klasses)); return; } for (jint j = 0; j < nfields; j++) { jvmti_env->SetFieldModificationWatch(k, fields[j]); } jvmti_env->Deallocate(reinterpret_cast(fields)); } jvmti_env->Deallocate(reinterpret_cast(klasses)); } static bool GetFieldAndClass(JNIEnv* env, jobject ref_field, jclass* out_klass, jfieldID* out_field) { *out_field = env->FromReflectedField(ref_field); if (env->ExceptionCheck()) { return false; } jclass field_klass = env->FindClass("java/lang/reflect/Field"); if (env->ExceptionCheck()) { return false; } jmethodID get_declaring_class_method = env->GetMethodID(field_klass, "getDeclaringClass", "()Ljava/lang/Class;"); if (env->ExceptionCheck()) { env->DeleteLocalRef(field_klass); return false; } *out_klass = static_cast(env->CallObjectMethod(ref_field, get_declaring_class_method)); if (env->ExceptionCheck()) { *out_klass = nullptr; env->DeleteLocalRef(field_klass); return false; } env->DeleteLocalRef(field_klass); return true; } extern "C" JNIEXPORT void JNICALL Java_art_Trace_watchFieldModification( JNIEnv* env, jclass trace ATTRIBUTE_UNUSED, jobject field_obj) { jfieldID field; jclass klass; if (!GetFieldAndClass(env, field_obj, &klass, &field)) { return; } JvmtiErrorToException(env, jvmti_env, jvmti_env->SetFieldModificationWatch(klass, field)); env->DeleteLocalRef(klass); } extern "C" JNIEXPORT void JNICALL Java_art_Trace_watchFieldAccess( JNIEnv* env, jclass trace ATTRIBUTE_UNUSED, jobject field_obj) { jfieldID field; jclass klass; if (!GetFieldAndClass(env, field_obj, &klass, &field)) { return; } JvmtiErrorToException(env, jvmti_env, jvmti_env->SetFieldAccessWatch(klass, field)); env->DeleteLocalRef(klass); } extern "C" JNIEXPORT void JNICALL Java_art_Trace_enableTracing( JNIEnv* env, jclass trace ATTRIBUTE_UNUSED, jclass klass, jobject enter, jobject exit, jobject field_access, jobject field_modify, jobject single_step, jthread thr) { TraceData* data = nullptr; if (JvmtiErrorToException(env, jvmti_env, jvmti_env->Allocate(sizeof(TraceData), reinterpret_cast(&data)))) { return; } memset(data, 0, sizeof(TraceData)); data->test_klass = reinterpret_cast(env->NewGlobalRef(klass)); data->enter_method = enter != nullptr ? env->FromReflectedMethod(enter) : nullptr; data->exit_method = exit != nullptr ? env->FromReflectedMethod(exit) : nullptr; data->field_access = field_access != nullptr ? env->FromReflectedMethod(field_access) : nullptr; data->field_modify = field_modify != nullptr ? env->FromReflectedMethod(field_modify) : nullptr; data->single_step = single_step != nullptr ? env->FromReflectedMethod(single_step) : nullptr; data->in_callback = false; void* old_data = nullptr; if (JvmtiErrorToException(env, jvmti_env, jvmti_env->GetEnvironmentLocalStorage(&old_data))) { return; } else if (old_data != nullptr) { ScopedLocalRef rt_exception(env, env->FindClass("java/lang/RuntimeException")); env->ThrowNew(rt_exception.get(), "Environment already has local storage set!"); return; } if (JvmtiErrorToException(env, jvmti_env, jvmti_env->SetEnvironmentLocalStorage(data))) { return; } jvmtiEventCallbacks cb; memset(&cb, 0, sizeof(cb)); cb.MethodEntry = methodEntryCB; cb.MethodExit = methodExitCB; cb.FieldAccess = fieldAccessCB; cb.FieldModification = fieldModificationCB; cb.ClassPrepare = classPrepareCB; cb.SingleStep = singleStepCB; if (JvmtiErrorToException(env, jvmti_env, jvmti_env->SetEventCallbacks(&cb, sizeof(cb)))) { return; } if (enter != nullptr && JvmtiErrorToException(env, jvmti_env, jvmti_env->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_METHOD_ENTRY, thr))) { return; } if (exit != nullptr && JvmtiErrorToException(env, jvmti_env, jvmti_env->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_METHOD_EXIT, thr))) { return; } if (field_access != nullptr && JvmtiErrorToException(env, jvmti_env, jvmti_env->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_FIELD_ACCESS, thr))) { return; } if (field_modify != nullptr && JvmtiErrorToException(env, jvmti_env, jvmti_env->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_FIELD_MODIFICATION, thr))) { return; } if (single_step != nullptr && JvmtiErrorToException(env, jvmti_env, jvmti_env->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_SINGLE_STEP, thr))) { return; } } extern "C" JNIEXPORT void JNICALL Java_art_Trace_disableTracing( JNIEnv* env, jclass klass ATTRIBUTE_UNUSED, jthread thr) { if (JvmtiErrorToException(env, jvmti_env, jvmti_env->SetEventNotificationMode(JVMTI_DISABLE, JVMTI_EVENT_FIELD_ACCESS, thr))) { return; } if (JvmtiErrorToException(env, jvmti_env, jvmti_env->SetEventNotificationMode(JVMTI_DISABLE, JVMTI_EVENT_FIELD_MODIFICATION, thr))) { return; } if (JvmtiErrorToException(env, jvmti_env, jvmti_env->SetEventNotificationMode(JVMTI_DISABLE, JVMTI_EVENT_METHOD_ENTRY, thr))) { return; } if (JvmtiErrorToException(env, jvmti_env, jvmti_env->SetEventNotificationMode(JVMTI_DISABLE, JVMTI_EVENT_METHOD_EXIT, thr))) { return; } if (JvmtiErrorToException(env, jvmti_env, jvmti_env->SetEventNotificationMode(JVMTI_DISABLE, JVMTI_EVENT_SINGLE_STEP, thr))) { return; } } } // namespace common_trace } // namespace art