diff options
| -rw-r--r-- | test/Android.bp | 3 | ||||
| -rw-r--r-- | test/ti-agent/breakpoint_helper.cc | 204 | ||||
| -rw-r--r-- | test/ti-agent/common_helper.cc | 1039 | ||||
| -rw-r--r-- | test/ti-agent/common_helper.h | 16 | ||||
| -rw-r--r-- | test/ti-agent/common_load.cc | 13 | ||||
| -rw-r--r-- | test/ti-agent/redefinition_helper.cc | 410 | ||||
| -rw-r--r-- | test/ti-agent/trace_helper.cc | 493 |
7 files changed, 1133 insertions, 1045 deletions
diff --git a/test/Android.bp b/test/Android.bp index 0dff01b6cf..f893531c41 100644 --- a/test/Android.bp +++ b/test/Android.bp @@ -250,7 +250,10 @@ art_cc_defaults { "ti-agent/jni_binder.cc", "ti-agent/jvmti_helper.cc", "ti-agent/test_env.cc", + "ti-agent/breakpoint_helper.cc", "ti-agent/common_helper.cc", + "ti-agent/redefinition_helper.cc", + "ti-agent/trace_helper.cc", // This is the list of non-special OnLoad things and excludes BCI and anything that depends // on ART internals. "903-hello-tagging/tagging.cc", diff --git a/test/ti-agent/breakpoint_helper.cc b/test/ti-agent/breakpoint_helper.cc new file mode 100644 index 0000000000..78aab4376f --- /dev/null +++ b/test/ti-agent/breakpoint_helper.cc @@ -0,0 +1,204 @@ +/* + * 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_breakpoint { + +struct BreakpointData { + jclass test_klass; + jmethodID breakpoint_method; + bool in_callback; + bool allow_recursive; +}; + +extern "C" void breakpointCB(jvmtiEnv* jvmti, + JNIEnv* jnienv, + jthread thread, + jmethodID method, + jlocation location) { + BreakpointData* data = nullptr; + if (JvmtiErrorToException(jnienv, jvmti, + jvmti->GetEnvironmentLocalStorage(reinterpret_cast<void**>(&data)))) { + return; + } + if (data->in_callback && !data->allow_recursive) { + return; + } + data->in_callback = true; + jobject method_arg = GetJavaMethod(jvmti, jnienv, method); + jnienv->CallStaticVoidMethod(data->test_klass, + data->breakpoint_method, + thread, + method_arg, + static_cast<jlong>(location)); + jnienv->DeleteLocalRef(method_arg); + data->in_callback = false; +} + +extern "C" JNIEXPORT jobjectArray JNICALL Java_art_Breakpoint_getLineNumberTableNative( + JNIEnv* env, + jclass k ATTRIBUTE_UNUSED, + jobject target) { + jmethodID method = env->FromReflectedMethod(target); + if (env->ExceptionCheck()) { + return nullptr; + } + jint nlines; + jvmtiLineNumberEntry* lines = nullptr; + if (JvmtiErrorToException(env, jvmti_env, + jvmti_env->GetLineNumberTable(method, &nlines, &lines))) { + return nullptr; + } + jintArray lines_array = env->NewIntArray(nlines); + if (env->ExceptionCheck()) { + jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(lines)); + return nullptr; + } + jlongArray locs_array = env->NewLongArray(nlines); + if (env->ExceptionCheck()) { + jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(lines)); + return nullptr; + } + ScopedLocalRef<jclass> object_class(env, env->FindClass("java/lang/Object")); + if (env->ExceptionCheck()) { + jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(lines)); + return nullptr; + } + jobjectArray ret = env->NewObjectArray(2, object_class.get(), nullptr); + if (env->ExceptionCheck()) { + jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(lines)); + return nullptr; + } + jint* temp_lines = env->GetIntArrayElements(lines_array, /*isCopy*/nullptr); + jlong* temp_locs = env->GetLongArrayElements(locs_array, /*isCopy*/nullptr); + for (jint i = 0; i < nlines; i++) { + temp_lines[i] = lines[i].line_number; + temp_locs[i] = lines[i].start_location; + } + env->ReleaseIntArrayElements(lines_array, temp_lines, 0); + env->ReleaseLongArrayElements(locs_array, temp_locs, 0); + env->SetObjectArrayElement(ret, 0, locs_array); + env->SetObjectArrayElement(ret, 1, lines_array); + jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(lines)); + return ret; +} + +extern "C" JNIEXPORT jlong JNICALL Java_art_Breakpoint_getStartLocation(JNIEnv* env, + jclass k ATTRIBUTE_UNUSED, + jobject target) { + jmethodID method = env->FromReflectedMethod(target); + if (env->ExceptionCheck()) { + return 0; + } + jlong start = 0; + jlong end = end; + JvmtiErrorToException(env, jvmti_env, jvmti_env->GetMethodLocation(method, &start, &end)); + return start; +} + +extern "C" JNIEXPORT void JNICALL Java_art_Breakpoint_clearBreakpoint(JNIEnv* env, + jclass k ATTRIBUTE_UNUSED, + jobject target, + jlocation location) { + jmethodID method = env->FromReflectedMethod(target); + if (env->ExceptionCheck()) { + return; + } + JvmtiErrorToException(env, jvmti_env, jvmti_env->ClearBreakpoint(method, location)); +} + +extern "C" JNIEXPORT void JNICALL Java_art_Breakpoint_setBreakpoint(JNIEnv* env, + jclass k ATTRIBUTE_UNUSED, + jobject target, + jlocation location) { + jmethodID method = env->FromReflectedMethod(target); + if (env->ExceptionCheck()) { + return; + } + JvmtiErrorToException(env, jvmti_env, jvmti_env->SetBreakpoint(method, location)); +} + +extern "C" JNIEXPORT void JNICALL Java_art_Breakpoint_startBreakpointWatch( + JNIEnv* env, + jclass k ATTRIBUTE_UNUSED, + jclass method_klass, + jobject method, + jboolean allow_recursive, + jthread thr) { + BreakpointData* data = nullptr; + if (JvmtiErrorToException(env, + jvmti_env, + jvmti_env->Allocate(sizeof(BreakpointData), + reinterpret_cast<unsigned char**>(&data)))) { + return; + } + memset(data, 0, sizeof(BreakpointData)); + data->test_klass = reinterpret_cast<jclass>(env->NewGlobalRef(method_klass)); + data->breakpoint_method = env->FromReflectedMethod(method); + data->in_callback = false; + data->allow_recursive = allow_recursive; + + void* old_data = nullptr; + if (JvmtiErrorToException(env, jvmti_env, jvmti_env->GetEnvironmentLocalStorage(&old_data))) { + return; + } else if (old_data != nullptr) { + ScopedLocalRef<jclass> 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.Breakpoint = breakpointCB; + if (JvmtiErrorToException(env, jvmti_env, jvmti_env->SetEventCallbacks(&cb, sizeof(cb)))) { + return; + } + if (JvmtiErrorToException(env, + jvmti_env, + jvmti_env->SetEventNotificationMode(JVMTI_ENABLE, + JVMTI_EVENT_BREAKPOINT, + thr))) { + return; + } +} + +extern "C" JNIEXPORT void JNICALL Java_art_Breakpoint_stopBreakpointWatch( + JNIEnv* env, + jclass k ATTRIBUTE_UNUSED, + jthread thr) { + if (JvmtiErrorToException(env, jvmti_env, + jvmti_env->SetEventNotificationMode(JVMTI_DISABLE, + JVMTI_EVENT_BREAKPOINT, + thr))) { + return; + } +} + +} // namespace common_breakpoint + +} // namespace art diff --git a/test/ti-agent/common_helper.cc b/test/ti-agent/common_helper.cc index 0eb71f8371..e57a493fe4 100644 --- a/test/ti-agent/common_helper.cc +++ b/test/ti-agent/common_helper.cc @@ -16,63 +16,18 @@ #include "common_helper.h" -#include <dlfcn.h> -#include <map> -#include <stdio.h> #include <sstream> -#include <deque> -#include <vector> +#include <string> #include "android-base/stringprintf.h" #include "jni.h" #include "jvmti.h" -#include "jni_binder.h" #include "jvmti_helper.h" -#include "scoped_local_ref.h" -#include "test_env.h" namespace art { -static void SetupCommonRetransform(); -static void SetupCommonRedefine(); -static void SetupCommonTransform(); - -// Taken from art/runtime/modifiers.h -static constexpr uint32_t kAccStatic = 0x0008; // field, method, ic - -template <bool is_redefine> -static void throwCommonRedefinitionError(jvmtiEnv* jvmti, - JNIEnv* env, - jint num_targets, - jclass* target, - jvmtiError res) { - std::stringstream err; - char* error = nullptr; - jvmti->GetErrorName(res, &error); - err << "Failed to " << (is_redefine ? "redefine" : "retransform") << " class"; - if (num_targets > 1) { - err << "es"; - } - err << " <"; - for (jint i = 0; i < num_targets; i++) { - char* signature = nullptr; - char* generic = nullptr; - jvmti->GetClassSignature(target[i], &signature, &generic); - if (i != 0) { - err << ", "; - } - err << signature; - jvmti->Deallocate(reinterpret_cast<unsigned char*>(signature)); - jvmti->Deallocate(reinterpret_cast<unsigned char*>(generic)); - } - err << "> due to " << error; - std::string message = err.str(); - jvmti->Deallocate(reinterpret_cast<unsigned char*>(error)); - env->ThrowNew(env->FindClass("java/lang/Exception"), message.c_str()); -} - -static jobject GetJavaField(jvmtiEnv* jvmti, JNIEnv* env, jclass field_klass, jfieldID f) { +jobject GetJavaField(jvmtiEnv* jvmti, JNIEnv* env, jclass field_klass, jfieldID f) { jint mods = 0; if (JvmtiErrorToException(env, jvmti, jvmti->GetFieldModifiers(field_klass, f, &mods))) { return nullptr; @@ -82,7 +37,7 @@ static jobject GetJavaField(jvmtiEnv* jvmti, JNIEnv* env, jclass field_klass, jf return env->ToReflectedField(field_klass, f, is_static); } -static jobject GetJavaMethod(jvmtiEnv* jvmti, JNIEnv* env, jmethodID m) { +jobject GetJavaMethod(jvmtiEnv* jvmti, JNIEnv* env, jmethodID m) { jint mods = 0; if (JvmtiErrorToException(env, jvmti, jvmti->GetMethodModifiers(m, &mods))) { return nullptr; @@ -98,7 +53,7 @@ static jobject GetJavaMethod(jvmtiEnv* jvmti, JNIEnv* env, jmethodID m) { return res; } -static jobject GetJavaValueByType(JNIEnv* env, char type, jvalue value) { +jobject GetJavaValueByType(JNIEnv* env, char type, jvalue value) { std::string name; switch (type) { case 'V': @@ -146,10 +101,7 @@ static jobject GetJavaValueByType(JNIEnv* env, char type, jvalue value) { return res; } -static jobject GetJavaValue(jvmtiEnv* jvmtienv, - JNIEnv* env, - jmethodID m, - jvalue value) { +jobject GetJavaValue(jvmtiEnv* jvmtienv, JNIEnv* env, jmethodID m, jvalue value) { char *fname, *fsig, *fgen; if (JvmtiErrorToException(env, jvmtienv, jvmtienv->GetMethodName(m, &fname, &fsig, &fgen))) { return nullptr; @@ -162,985 +114,4 @@ static jobject GetJavaValue(jvmtiEnv* jvmtienv, return GetJavaValueByType(env, type[0], value); } -namespace common_breakpoint { - -struct BreakpointData { - jclass test_klass; - jmethodID breakpoint_method; - bool in_callback; - bool allow_recursive; -}; - -extern "C" void breakpointCB(jvmtiEnv* jvmti, - JNIEnv* jnienv, - jthread thread, - jmethodID method, - jlocation location) { - BreakpointData* data = nullptr; - if (JvmtiErrorToException(jnienv, jvmti, - jvmti->GetEnvironmentLocalStorage(reinterpret_cast<void**>(&data)))) { - return; - } - if (data->in_callback && !data->allow_recursive) { - return; - } - data->in_callback = true; - jobject method_arg = GetJavaMethod(jvmti, jnienv, method); - jnienv->CallStaticVoidMethod(data->test_klass, - data->breakpoint_method, - thread, - method_arg, - static_cast<jlong>(location)); - jnienv->DeleteLocalRef(method_arg); - data->in_callback = false; -} - -extern "C" JNIEXPORT jobjectArray JNICALL Java_art_Breakpoint_getLineNumberTableNative( - JNIEnv* env, - jclass k ATTRIBUTE_UNUSED, - jobject target) { - jmethodID method = env->FromReflectedMethod(target); - if (env->ExceptionCheck()) { - return nullptr; - } - jint nlines; - jvmtiLineNumberEntry* lines = nullptr; - if (JvmtiErrorToException(env, jvmti_env, - jvmti_env->GetLineNumberTable(method, &nlines, &lines))) { - return nullptr; - } - jintArray lines_array = env->NewIntArray(nlines); - if (env->ExceptionCheck()) { - jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(lines)); - return nullptr; - } - jlongArray locs_array = env->NewLongArray(nlines); - if (env->ExceptionCheck()) { - jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(lines)); - return nullptr; - } - ScopedLocalRef<jclass> object_class(env, env->FindClass("java/lang/Object")); - if (env->ExceptionCheck()) { - jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(lines)); - return nullptr; - } - jobjectArray ret = env->NewObjectArray(2, object_class.get(), nullptr); - if (env->ExceptionCheck()) { - jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(lines)); - return nullptr; - } - jint* temp_lines = env->GetIntArrayElements(lines_array, /*isCopy*/nullptr); - jlong* temp_locs = env->GetLongArrayElements(locs_array, /*isCopy*/nullptr); - for (jint i = 0; i < nlines; i++) { - temp_lines[i] = lines[i].line_number; - temp_locs[i] = lines[i].start_location; - } - env->ReleaseIntArrayElements(lines_array, temp_lines, 0); - env->ReleaseLongArrayElements(locs_array, temp_locs, 0); - env->SetObjectArrayElement(ret, 0, locs_array); - env->SetObjectArrayElement(ret, 1, lines_array); - jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(lines)); - return ret; -} - -extern "C" JNIEXPORT jlong JNICALL Java_art_Breakpoint_getStartLocation(JNIEnv* env, - jclass k ATTRIBUTE_UNUSED, - jobject target) { - jmethodID method = env->FromReflectedMethod(target); - if (env->ExceptionCheck()) { - return 0; - } - jlong start = 0; - jlong end = end; - JvmtiErrorToException(env, jvmti_env, jvmti_env->GetMethodLocation(method, &start, &end)); - return start; -} - -extern "C" JNIEXPORT void JNICALL Java_art_Breakpoint_clearBreakpoint(JNIEnv* env, - jclass k ATTRIBUTE_UNUSED, - jobject target, - jlocation location) { - jmethodID method = env->FromReflectedMethod(target); - if (env->ExceptionCheck()) { - return; - } - JvmtiErrorToException(env, jvmti_env, jvmti_env->ClearBreakpoint(method, location)); -} - -extern "C" JNIEXPORT void JNICALL Java_art_Breakpoint_setBreakpoint(JNIEnv* env, - jclass k ATTRIBUTE_UNUSED, - jobject target, - jlocation location) { - jmethodID method = env->FromReflectedMethod(target); - if (env->ExceptionCheck()) { - return; - } - JvmtiErrorToException(env, jvmti_env, jvmti_env->SetBreakpoint(method, location)); -} - -extern "C" JNIEXPORT void JNICALL Java_art_Breakpoint_startBreakpointWatch( - JNIEnv* env, - jclass k ATTRIBUTE_UNUSED, - jclass method_klass, - jobject method, - jboolean allow_recursive, - jthread thr) { - BreakpointData* data = nullptr; - if (JvmtiErrorToException(env, - jvmti_env, - jvmti_env->Allocate(sizeof(BreakpointData), - reinterpret_cast<unsigned char**>(&data)))) { - return; - } - memset(data, 0, sizeof(BreakpointData)); - data->test_klass = reinterpret_cast<jclass>(env->NewGlobalRef(method_klass)); - data->breakpoint_method = env->FromReflectedMethod(method); - data->in_callback = false; - data->allow_recursive = allow_recursive; - - void* old_data = nullptr; - if (JvmtiErrorToException(env, jvmti_env, jvmti_env->GetEnvironmentLocalStorage(&old_data))) { - return; - } else if (old_data != nullptr) { - ScopedLocalRef<jclass> 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.Breakpoint = breakpointCB; - if (JvmtiErrorToException(env, jvmti_env, jvmti_env->SetEventCallbacks(&cb, sizeof(cb)))) { - return; - } - if (JvmtiErrorToException(env, - jvmti_env, - jvmti_env->SetEventNotificationMode(JVMTI_ENABLE, - JVMTI_EVENT_BREAKPOINT, - thr))) { - return; - } -} - -extern "C" JNIEXPORT void JNICALL Java_art_Breakpoint_stopBreakpointWatch( - JNIEnv* env, - jclass k ATTRIBUTE_UNUSED, - jthread thr) { - if (JvmtiErrorToException(env, jvmti_env, - jvmti_env->SetEventNotificationMode(JVMTI_DISABLE, - JVMTI_EVENT_BREAKPOINT, - thr))) { - return; - } -} - -} // namespace common_breakpoint - -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<void**>(&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<jlong>(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<void**>(&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<jlong>(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<void**>(&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<jlong>(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<void**>(&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<void**>(&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<void**>(&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<unsigned char*>(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<void**>(&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<unsigned char*>(klasses)); - return; - } - for (jint j = 0; j < nfields; j++) { - jvmti_env->SetFieldAccessWatch(k, fields[j]); - } - jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(fields)); - } - jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(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<void**>(&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<unsigned char*>(klasses)); - return; - } - for (jint j = 0; j < nfields; j++) { - jvmti_env->SetFieldModificationWatch(k, fields[j]); - } - jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(fields)); - } - jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(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<jclass>(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<unsigned char**>(&data)))) { - return; - } - memset(data, 0, sizeof(TraceData)); - data->test_klass = reinterpret_cast<jclass>(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<jclass> 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 common_redefine { - -static void throwRedefinitionError(jvmtiEnv* jvmti, - JNIEnv* env, - jint num_targets, - jclass* target, - jvmtiError res) { - return throwCommonRedefinitionError<true>(jvmti, env, num_targets, target, res); -} - -static void DoMultiClassRedefine(jvmtiEnv* jvmti_env, - JNIEnv* env, - jint num_redefines, - jclass* targets, - jbyteArray* class_file_bytes, - jbyteArray* dex_file_bytes) { - std::vector<jvmtiClassDefinition> defs; - for (jint i = 0; i < num_redefines; i++) { - jbyteArray desired_array = IsJVM() ? class_file_bytes[i] : dex_file_bytes[i]; - jint len = static_cast<jint>(env->GetArrayLength(desired_array)); - const unsigned char* redef_bytes = reinterpret_cast<const unsigned char*>( - env->GetByteArrayElements(desired_array, nullptr)); - defs.push_back({targets[i], static_cast<jint>(len), redef_bytes}); - } - jvmtiError res = jvmti_env->RedefineClasses(num_redefines, defs.data()); - if (res != JVMTI_ERROR_NONE) { - throwRedefinitionError(jvmti_env, env, num_redefines, targets, res); - } -} - -static void DoClassRedefine(jvmtiEnv* jvmti_env, - JNIEnv* env, - jclass target, - jbyteArray class_file_bytes, - jbyteArray dex_file_bytes) { - return DoMultiClassRedefine(jvmti_env, env, 1, &target, &class_file_bytes, &dex_file_bytes); -} - -// Magic JNI export that classes can use for redefining classes. -// To use classes should declare this as a native function with signature (Ljava/lang/Class;[B[B)V -extern "C" JNIEXPORT void JNICALL Java_art_Redefinition_doCommonClassRedefinition( - JNIEnv* env, jclass, jclass target, jbyteArray class_file_bytes, jbyteArray dex_file_bytes) { - DoClassRedefine(jvmti_env, env, target, class_file_bytes, dex_file_bytes); -} - -// Magic JNI export that classes can use for redefining classes. -// To use classes should declare this as a native function with signature -// ([Ljava/lang/Class;[[B[[B)V -extern "C" JNIEXPORT void JNICALL Java_art_Redefinition_doCommonMultiClassRedefinition( - JNIEnv* env, - jclass, - jobjectArray targets, - jobjectArray class_file_bytes, - jobjectArray dex_file_bytes) { - std::vector<jclass> classes; - std::vector<jbyteArray> class_files; - std::vector<jbyteArray> dex_files; - jint len = env->GetArrayLength(targets); - if (len != env->GetArrayLength(class_file_bytes) || len != env->GetArrayLength(dex_file_bytes)) { - env->ThrowNew(env->FindClass("java/lang/IllegalArgumentException"), - "the three array arguments passed to this function have different lengths!"); - return; - } - for (jint i = 0; i < len; i++) { - classes.push_back(static_cast<jclass>(env->GetObjectArrayElement(targets, i))); - dex_files.push_back(static_cast<jbyteArray>(env->GetObjectArrayElement(dex_file_bytes, i))); - class_files.push_back(static_cast<jbyteArray>(env->GetObjectArrayElement(class_file_bytes, i))); - } - return DoMultiClassRedefine(jvmti_env, - env, - len, - classes.data(), - class_files.data(), - dex_files.data()); -} - -// Get all capabilities except those related to retransformation. -jint OnLoad(JavaVM* vm, - char* options ATTRIBUTE_UNUSED, - void* reserved ATTRIBUTE_UNUSED) { - if (vm->GetEnv(reinterpret_cast<void**>(&jvmti_env), JVMTI_VERSION_1_0)) { - printf("Unable to get jvmti env!\n"); - return 1; - } - SetupCommonRedefine(); - return 0; -} - -} // namespace common_redefine - -namespace common_retransform { - -struct CommonTransformationResult { - std::vector<unsigned char> class_bytes; - std::vector<unsigned char> dex_bytes; - - CommonTransformationResult(size_t class_size, size_t dex_size) - : class_bytes(class_size), dex_bytes(dex_size) {} - - CommonTransformationResult() = default; - CommonTransformationResult(CommonTransformationResult&&) = default; - CommonTransformationResult(CommonTransformationResult&) = default; -}; - -// Map from class name to transformation result. -std::map<std::string, std::deque<CommonTransformationResult>> gTransformations; -bool gPopTransformations = true; - -extern "C" JNIEXPORT void JNICALL Java_art_Redefinition_addCommonTransformationResult( - JNIEnv* env, jclass, jstring class_name, jbyteArray class_array, jbyteArray dex_array) { - const char* name_chrs = env->GetStringUTFChars(class_name, nullptr); - std::string name_str(name_chrs); - env->ReleaseStringUTFChars(class_name, name_chrs); - CommonTransformationResult trans(env->GetArrayLength(class_array), - env->GetArrayLength(dex_array)); - if (env->ExceptionOccurred()) { - return; - } - env->GetByteArrayRegion(class_array, - 0, - env->GetArrayLength(class_array), - reinterpret_cast<jbyte*>(trans.class_bytes.data())); - if (env->ExceptionOccurred()) { - return; - } - env->GetByteArrayRegion(dex_array, - 0, - env->GetArrayLength(dex_array), - reinterpret_cast<jbyte*>(trans.dex_bytes.data())); - if (env->ExceptionOccurred()) { - return; - } - if (gTransformations.find(name_str) == gTransformations.end()) { - std::deque<CommonTransformationResult> list; - gTransformations[name_str] = std::move(list); - } - gTransformations[name_str].push_back(std::move(trans)); -} - -// The hook we are using. -void JNICALL CommonClassFileLoadHookRetransformable(jvmtiEnv* jvmti_env, - JNIEnv* jni_env ATTRIBUTE_UNUSED, - jclass class_being_redefined ATTRIBUTE_UNUSED, - jobject loader ATTRIBUTE_UNUSED, - const char* name, - jobject protection_domain ATTRIBUTE_UNUSED, - jint class_data_len ATTRIBUTE_UNUSED, - const unsigned char* class_dat ATTRIBUTE_UNUSED, - jint* new_class_data_len, - unsigned char** new_class_data) { - std::string name_str(name); - if (gTransformations.find(name_str) != gTransformations.end() && - gTransformations[name_str].size() > 0) { - CommonTransformationResult& res = gTransformations[name_str][0]; - const std::vector<unsigned char>& desired_array = IsJVM() ? res.class_bytes : res.dex_bytes; - unsigned char* new_data; - CHECK_EQ(JVMTI_ERROR_NONE, jvmti_env->Allocate(desired_array.size(), &new_data)); - memcpy(new_data, desired_array.data(), desired_array.size()); - *new_class_data = new_data; - *new_class_data_len = desired_array.size(); - if (gPopTransformations) { - gTransformations[name_str].pop_front(); - } - } -} - -extern "C" JNIEXPORT void Java_art_Redefinition_setPopRetransformations(JNIEnv*, - jclass, - jboolean enable) { - gPopTransformations = enable; -} - -extern "C" JNIEXPORT void Java_art_Redefinition_popTransformationFor(JNIEnv* env, - jclass, - jstring class_name) { - const char* name_chrs = env->GetStringUTFChars(class_name, nullptr); - std::string name_str(name_chrs); - env->ReleaseStringUTFChars(class_name, name_chrs); - if (gTransformations.find(name_str) != gTransformations.end() && - gTransformations[name_str].size() > 0) { - gTransformations[name_str].pop_front(); - } else { - std::stringstream err; - err << "No transformations found for class " << name_str; - std::string message = err.str(); - env->ThrowNew(env->FindClass("java/lang/Exception"), message.c_str()); - } -} - -extern "C" JNIEXPORT void Java_art_Redefinition_enableCommonRetransformation(JNIEnv* env, - jclass, - jboolean enable) { - jvmtiError res = jvmti_env->SetEventNotificationMode(enable ? JVMTI_ENABLE : JVMTI_DISABLE, - JVMTI_EVENT_CLASS_FILE_LOAD_HOOK, - nullptr); - if (res != JVMTI_ERROR_NONE) { - JvmtiErrorToException(env, jvmti_env, res); - } -} - -static void throwRetransformationError(jvmtiEnv* jvmti, - JNIEnv* env, - jint num_targets, - jclass* targets, - jvmtiError res) { - return throwCommonRedefinitionError<false>(jvmti, env, num_targets, targets, res); -} - -static void DoClassRetransformation(jvmtiEnv* jvmti_env, JNIEnv* env, jobjectArray targets) { - std::vector<jclass> classes; - jint len = env->GetArrayLength(targets); - for (jint i = 0; i < len; i++) { - classes.push_back(static_cast<jclass>(env->GetObjectArrayElement(targets, i))); - } - jvmtiError res = jvmti_env->RetransformClasses(len, classes.data()); - if (res != JVMTI_ERROR_NONE) { - throwRetransformationError(jvmti_env, env, len, classes.data(), res); - } -} - -extern "C" JNIEXPORT void JNICALL Java_art_Redefinition_doCommonClassRetransformation( - JNIEnv* env, jclass, jobjectArray targets) { - jvmtiCapabilities caps; - jvmtiError caps_err = jvmti_env->GetCapabilities(&caps); - if (caps_err != JVMTI_ERROR_NONE) { - env->ThrowNew(env->FindClass("java/lang/Exception"), - "Unable to get current jvmtiEnv capabilities"); - return; - } - - // Allocate a new environment if we don't have the can_retransform_classes capability needed to - // call the RetransformClasses function. - jvmtiEnv* real_env = nullptr; - if (caps.can_retransform_classes != 1) { - JavaVM* vm = nullptr; - if (env->GetJavaVM(&vm) != 0 || - vm->GetEnv(reinterpret_cast<void**>(&real_env), JVMTI_VERSION_1_0) != 0) { - env->ThrowNew(env->FindClass("java/lang/Exception"), - "Unable to create temporary jvmtiEnv for RetransformClasses call."); - return; - } - SetAllCapabilities(real_env); - } else { - real_env = jvmti_env; - } - DoClassRetransformation(real_env, env, targets); - if (caps.can_retransform_classes != 1) { - real_env->DisposeEnvironment(); - } -} - -// Get all capabilities except those related to retransformation. -jint OnLoad(JavaVM* vm, - char* options ATTRIBUTE_UNUSED, - void* reserved ATTRIBUTE_UNUSED) { - if (vm->GetEnv(reinterpret_cast<void**>(&jvmti_env), JVMTI_VERSION_1_0)) { - printf("Unable to get jvmti env!\n"); - return 1; - } - SetupCommonRetransform(); - return 0; -} - -} // namespace common_retransform - -namespace common_transform { - -// Get all capabilities except those related to retransformation. -jint OnLoad(JavaVM* vm, - char* options ATTRIBUTE_UNUSED, - void* reserved ATTRIBUTE_UNUSED) { - if (vm->GetEnv(reinterpret_cast<void**>(&jvmti_env), JVMTI_VERSION_1_0)) { - printf("Unable to get jvmti env!\n"); - return 1; - } - SetupCommonTransform(); - return 0; -} - -} // namespace common_transform - -#define CONFIGURATION_COMMON_REDEFINE 0 -#define CONFIGURATION_COMMON_RETRANSFORM 1 -#define CONFIGURATION_COMMON_TRANSFORM 2 - -static void SetupCommonRedefine() { - jvmtiCapabilities caps; - jvmti_env->GetPotentialCapabilities(&caps); - caps.can_retransform_classes = 0; - caps.can_retransform_any_class = 0; - jvmti_env->AddCapabilities(&caps); -} - -static void SetupCommonRetransform() { - SetAllCapabilities(jvmti_env); - jvmtiEventCallbacks cb; - memset(&cb, 0, sizeof(cb)); - cb.ClassFileLoadHook = common_retransform::CommonClassFileLoadHookRetransformable; - jvmtiError res = jvmti_env->SetEventCallbacks(&cb, sizeof(cb)); - CHECK_EQ(res, JVMTI_ERROR_NONE); - common_retransform::gTransformations.clear(); -} - -static void SetupCommonTransform() { - // Don't set the retransform caps - jvmtiCapabilities caps; - jvmti_env->GetPotentialCapabilities(&caps); - caps.can_retransform_classes = 0; - caps.can_retransform_any_class = 0; - jvmti_env->AddCapabilities(&caps); - - // Use the same callback as the retransform test. - jvmtiEventCallbacks cb; - memset(&cb, 0, sizeof(cb)); - cb.ClassFileLoadHook = common_retransform::CommonClassFileLoadHookRetransformable; - jvmtiError res = jvmti_env->SetEventCallbacks(&cb, sizeof(cb)); - CHECK_EQ(res, JVMTI_ERROR_NONE); - common_retransform::gTransformations.clear(); -} - -extern "C" JNIEXPORT void JNICALL Java_art_Redefinition_nativeSetTestConfiguration(JNIEnv*, - jclass, - jint type) { - switch (type) { - case CONFIGURATION_COMMON_REDEFINE: { - SetupCommonRedefine(); - return; - } - case CONFIGURATION_COMMON_RETRANSFORM: { - SetupCommonRetransform(); - return; - } - case CONFIGURATION_COMMON_TRANSFORM: { - SetupCommonTransform(); - return; - } - default: { - LOG(FATAL) << "Unknown test configuration: " << type; - } - } -} } // namespace art diff --git a/test/ti-agent/common_helper.h b/test/ti-agent/common_helper.h index 610019e4d2..fafa1afcda 100644 --- a/test/ti-agent/common_helper.h +++ b/test/ti-agent/common_helper.h @@ -22,17 +22,13 @@ namespace art { -namespace common_redefine { -jint OnLoad(JavaVM* vm, char* options, void* reserved); -} // namespace common_redefine +// Taken from art/runtime/modifiers.h +static constexpr uint32_t kAccStatic = 0x0008; // field, method, ic -namespace common_retransform { -jint OnLoad(JavaVM* vm, char* options, void* reserved); -} // namespace common_retransform - -namespace common_transform { -jint OnLoad(JavaVM* vm, char* options, void* reserved); -} // namespace common_transform +jobject GetJavaField(jvmtiEnv* jvmti, JNIEnv* env, jclass field_klass, jfieldID f); +jobject GetJavaMethod(jvmtiEnv* jvmti, JNIEnv* env, jmethodID m); +jobject GetJavaValueByType(JNIEnv* env, char type, jvalue value); +jobject GetJavaValue(jvmtiEnv* jvmtienv, JNIEnv* env, jmethodID m, jvalue value); } // namespace art diff --git a/test/ti-agent/common_load.cc b/test/ti-agent/common_load.cc index fd47f59905..0679c1bc17 100644 --- a/test/ti-agent/common_load.cc +++ b/test/ti-agent/common_load.cc @@ -20,7 +20,6 @@ #include "base/logging.h" #include "base/macros.h" -#include "common_helper.h" #include "jni_binder.h" #include "jvmti_helper.h" #include "test_env.h" @@ -32,6 +31,18 @@ namespace art { +namespace common_redefine { +jint OnLoad(JavaVM* vm, char* options, void* reserved); +} // namespace common_redefine + +namespace common_retransform { +jint OnLoad(JavaVM* vm, char* options, void* reserved); +} // namespace common_retransform + +namespace common_transform { +jint OnLoad(JavaVM* vm, char* options, void* reserved); +} // namespace common_transform + namespace { using OnLoad = jint (*)(JavaVM* vm, char* options, void* reserved); diff --git a/test/ti-agent/redefinition_helper.cc b/test/ti-agent/redefinition_helper.cc new file mode 100644 index 0000000000..3b18879ca5 --- /dev/null +++ b/test/ti-agent/redefinition_helper.cc @@ -0,0 +1,410 @@ +/* + * Copyright (C) 2016 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 <deque> +#include <map> +#include <stdio.h> +#include <sstream> +#include <string> +#include <vector> + +#include "jni.h" +#include "jvmti.h" + +#include "jvmti_helper.h" +#include "test_env.h" + +namespace art { + +static void SetupCommonRedefine(); +static void SetupCommonRetransform(); +static void SetupCommonTransform(); +template <bool is_redefine> +static void throwCommonRedefinitionError(jvmtiEnv* jvmti, + JNIEnv* env, + jint num_targets, + jclass* target, + jvmtiError res) { + std::stringstream err; + char* error = nullptr; + jvmti->GetErrorName(res, &error); + err << "Failed to " << (is_redefine ? "redefine" : "retransform") << " class"; + if (num_targets > 1) { + err << "es"; + } + err << " <"; + for (jint i = 0; i < num_targets; i++) { + char* signature = nullptr; + char* generic = nullptr; + jvmti->GetClassSignature(target[i], &signature, &generic); + if (i != 0) { + err << ", "; + } + err << signature; + jvmti->Deallocate(reinterpret_cast<unsigned char*>(signature)); + jvmti->Deallocate(reinterpret_cast<unsigned char*>(generic)); + } + err << "> due to " << error; + std::string message = err.str(); + jvmti->Deallocate(reinterpret_cast<unsigned char*>(error)); + env->ThrowNew(env->FindClass("java/lang/Exception"), message.c_str()); +} + +#define CONFIGURATION_COMMON_REDEFINE 0 +#define CONFIGURATION_COMMON_RETRANSFORM 1 +#define CONFIGURATION_COMMON_TRANSFORM 2 + +extern "C" JNIEXPORT void JNICALL Java_art_Redefinition_nativeSetTestConfiguration(JNIEnv*, + jclass, + jint type) { + switch (type) { + case CONFIGURATION_COMMON_REDEFINE: { + SetupCommonRedefine(); + return; + } + case CONFIGURATION_COMMON_RETRANSFORM: { + SetupCommonRetransform(); + return; + } + case CONFIGURATION_COMMON_TRANSFORM: { + SetupCommonTransform(); + return; + } + default: { + LOG(FATAL) << "Unknown test configuration: " << type; + } + } +} + +namespace common_redefine { + +static void throwRedefinitionError(jvmtiEnv* jvmti, + JNIEnv* env, + jint num_targets, + jclass* target, + jvmtiError res) { + return throwCommonRedefinitionError<true>(jvmti, env, num_targets, target, res); +} + +static void DoMultiClassRedefine(jvmtiEnv* jvmti_env, + JNIEnv* env, + jint num_redefines, + jclass* targets, + jbyteArray* class_file_bytes, + jbyteArray* dex_file_bytes) { + std::vector<jvmtiClassDefinition> defs; + for (jint i = 0; i < num_redefines; i++) { + jbyteArray desired_array = IsJVM() ? class_file_bytes[i] : dex_file_bytes[i]; + jint len = static_cast<jint>(env->GetArrayLength(desired_array)); + const unsigned char* redef_bytes = reinterpret_cast<const unsigned char*>( + env->GetByteArrayElements(desired_array, nullptr)); + defs.push_back({targets[i], static_cast<jint>(len), redef_bytes}); + } + jvmtiError res = jvmti_env->RedefineClasses(num_redefines, defs.data()); + if (res != JVMTI_ERROR_NONE) { + throwRedefinitionError(jvmti_env, env, num_redefines, targets, res); + } +} + +static void DoClassRedefine(jvmtiEnv* jvmti_env, + JNIEnv* env, + jclass target, + jbyteArray class_file_bytes, + jbyteArray dex_file_bytes) { + return DoMultiClassRedefine(jvmti_env, env, 1, &target, &class_file_bytes, &dex_file_bytes); +} + +// Magic JNI export that classes can use for redefining classes. +// To use classes should declare this as a native function with signature (Ljava/lang/Class;[B[B)V +extern "C" JNIEXPORT void JNICALL Java_art_Redefinition_doCommonClassRedefinition( + JNIEnv* env, jclass, jclass target, jbyteArray class_file_bytes, jbyteArray dex_file_bytes) { + DoClassRedefine(jvmti_env, env, target, class_file_bytes, dex_file_bytes); +} + +// Magic JNI export that classes can use for redefining classes. +// To use classes should declare this as a native function with signature +// ([Ljava/lang/Class;[[B[[B)V +extern "C" JNIEXPORT void JNICALL Java_art_Redefinition_doCommonMultiClassRedefinition( + JNIEnv* env, + jclass, + jobjectArray targets, + jobjectArray class_file_bytes, + jobjectArray dex_file_bytes) { + std::vector<jclass> classes; + std::vector<jbyteArray> class_files; + std::vector<jbyteArray> dex_files; + jint len = env->GetArrayLength(targets); + if (len != env->GetArrayLength(class_file_bytes) || len != env->GetArrayLength(dex_file_bytes)) { + env->ThrowNew(env->FindClass("java/lang/IllegalArgumentException"), + "the three array arguments passed to this function have different lengths!"); + return; + } + for (jint i = 0; i < len; i++) { + classes.push_back(static_cast<jclass>(env->GetObjectArrayElement(targets, i))); + dex_files.push_back(static_cast<jbyteArray>(env->GetObjectArrayElement(dex_file_bytes, i))); + class_files.push_back(static_cast<jbyteArray>(env->GetObjectArrayElement(class_file_bytes, i))); + } + return DoMultiClassRedefine(jvmti_env, + env, + len, + classes.data(), + class_files.data(), + dex_files.data()); +} + +// Get all capabilities except those related to retransformation. +jint OnLoad(JavaVM* vm, + char* options ATTRIBUTE_UNUSED, + void* reserved ATTRIBUTE_UNUSED) { + if (vm->GetEnv(reinterpret_cast<void**>(&jvmti_env), JVMTI_VERSION_1_0)) { + printf("Unable to get jvmti env!\n"); + return 1; + } + SetupCommonRedefine(); + return 0; +} + +} // namespace common_redefine + +namespace common_retransform { + +struct CommonTransformationResult { + std::vector<unsigned char> class_bytes; + std::vector<unsigned char> dex_bytes; + + CommonTransformationResult(size_t class_size, size_t dex_size) + : class_bytes(class_size), dex_bytes(dex_size) {} + + CommonTransformationResult() = default; + CommonTransformationResult(CommonTransformationResult&&) = default; + CommonTransformationResult(CommonTransformationResult&) = default; +}; + +// Map from class name to transformation result. +std::map<std::string, std::deque<CommonTransformationResult>> gTransformations; +bool gPopTransformations = true; + +extern "C" JNIEXPORT void JNICALL Java_art_Redefinition_addCommonTransformationResult( + JNIEnv* env, jclass, jstring class_name, jbyteArray class_array, jbyteArray dex_array) { + const char* name_chrs = env->GetStringUTFChars(class_name, nullptr); + std::string name_str(name_chrs); + env->ReleaseStringUTFChars(class_name, name_chrs); + CommonTransformationResult trans(env->GetArrayLength(class_array), + env->GetArrayLength(dex_array)); + if (env->ExceptionOccurred()) { + return; + } + env->GetByteArrayRegion(class_array, + 0, + env->GetArrayLength(class_array), + reinterpret_cast<jbyte*>(trans.class_bytes.data())); + if (env->ExceptionOccurred()) { + return; + } + env->GetByteArrayRegion(dex_array, + 0, + env->GetArrayLength(dex_array), + reinterpret_cast<jbyte*>(trans.dex_bytes.data())); + if (env->ExceptionOccurred()) { + return; + } + if (gTransformations.find(name_str) == gTransformations.end()) { + std::deque<CommonTransformationResult> list; + gTransformations[name_str] = std::move(list); + } + gTransformations[name_str].push_back(std::move(trans)); +} + +// The hook we are using. +void JNICALL CommonClassFileLoadHookRetransformable(jvmtiEnv* jvmti_env, + JNIEnv* jni_env ATTRIBUTE_UNUSED, + jclass class_being_redefined ATTRIBUTE_UNUSED, + jobject loader ATTRIBUTE_UNUSED, + const char* name, + jobject protection_domain ATTRIBUTE_UNUSED, + jint class_data_len ATTRIBUTE_UNUSED, + const unsigned char* class_dat ATTRIBUTE_UNUSED, + jint* new_class_data_len, + unsigned char** new_class_data) { + std::string name_str(name); + if (gTransformations.find(name_str) != gTransformations.end() && + gTransformations[name_str].size() > 0) { + CommonTransformationResult& res = gTransformations[name_str][0]; + const std::vector<unsigned char>& desired_array = IsJVM() ? res.class_bytes : res.dex_bytes; + unsigned char* new_data; + CHECK_EQ(JVMTI_ERROR_NONE, jvmti_env->Allocate(desired_array.size(), &new_data)); + memcpy(new_data, desired_array.data(), desired_array.size()); + *new_class_data = new_data; + *new_class_data_len = desired_array.size(); + if (gPopTransformations) { + gTransformations[name_str].pop_front(); + } + } +} + +extern "C" JNIEXPORT void Java_art_Redefinition_setPopRetransformations(JNIEnv*, + jclass, + jboolean enable) { + gPopTransformations = enable; +} + +extern "C" JNIEXPORT void Java_art_Redefinition_popTransformationFor(JNIEnv* env, + jclass, + jstring class_name) { + const char* name_chrs = env->GetStringUTFChars(class_name, nullptr); + std::string name_str(name_chrs); + env->ReleaseStringUTFChars(class_name, name_chrs); + if (gTransformations.find(name_str) != gTransformations.end() && + gTransformations[name_str].size() > 0) { + gTransformations[name_str].pop_front(); + } else { + std::stringstream err; + err << "No transformations found for class " << name_str; + std::string message = err.str(); + env->ThrowNew(env->FindClass("java/lang/Exception"), message.c_str()); + } +} + +extern "C" JNIEXPORT void Java_art_Redefinition_enableCommonRetransformation(JNIEnv* env, + jclass, + jboolean enable) { + jvmtiError res = jvmti_env->SetEventNotificationMode(enable ? JVMTI_ENABLE : JVMTI_DISABLE, + JVMTI_EVENT_CLASS_FILE_LOAD_HOOK, + nullptr); + if (res != JVMTI_ERROR_NONE) { + JvmtiErrorToException(env, jvmti_env, res); + } +} + +static void throwRetransformationError(jvmtiEnv* jvmti, + JNIEnv* env, + jint num_targets, + jclass* targets, + jvmtiError res) { + return throwCommonRedefinitionError<false>(jvmti, env, num_targets, targets, res); +} + +static void DoClassRetransformation(jvmtiEnv* jvmti_env, JNIEnv* env, jobjectArray targets) { + std::vector<jclass> classes; + jint len = env->GetArrayLength(targets); + for (jint i = 0; i < len; i++) { + classes.push_back(static_cast<jclass>(env->GetObjectArrayElement(targets, i))); + } + jvmtiError res = jvmti_env->RetransformClasses(len, classes.data()); + if (res != JVMTI_ERROR_NONE) { + throwRetransformationError(jvmti_env, env, len, classes.data(), res); + } +} + +extern "C" JNIEXPORT void JNICALL Java_art_Redefinition_doCommonClassRetransformation( + JNIEnv* env, jclass, jobjectArray targets) { + jvmtiCapabilities caps; + jvmtiError caps_err = jvmti_env->GetCapabilities(&caps); + if (caps_err != JVMTI_ERROR_NONE) { + env->ThrowNew(env->FindClass("java/lang/Exception"), + "Unable to get current jvmtiEnv capabilities"); + return; + } + + // Allocate a new environment if we don't have the can_retransform_classes capability needed to + // call the RetransformClasses function. + jvmtiEnv* real_env = nullptr; + if (caps.can_retransform_classes != 1) { + JavaVM* vm = nullptr; + if (env->GetJavaVM(&vm) != 0 || + vm->GetEnv(reinterpret_cast<void**>(&real_env), JVMTI_VERSION_1_0) != 0) { + env->ThrowNew(env->FindClass("java/lang/Exception"), + "Unable to create temporary jvmtiEnv for RetransformClasses call."); + return; + } + SetAllCapabilities(real_env); + } else { + real_env = jvmti_env; + } + DoClassRetransformation(real_env, env, targets); + if (caps.can_retransform_classes != 1) { + real_env->DisposeEnvironment(); + } +} + +// Get all capabilities except those related to retransformation. +jint OnLoad(JavaVM* vm, + char* options ATTRIBUTE_UNUSED, + void* reserved ATTRIBUTE_UNUSED) { + if (vm->GetEnv(reinterpret_cast<void**>(&jvmti_env), JVMTI_VERSION_1_0)) { + printf("Unable to get jvmti env!\n"); + return 1; + } + SetupCommonRetransform(); + return 0; +} + +} // namespace common_retransform + +namespace common_transform { + +// Get all capabilities except those related to retransformation. +jint OnLoad(JavaVM* vm, + char* options ATTRIBUTE_UNUSED, + void* reserved ATTRIBUTE_UNUSED) { + if (vm->GetEnv(reinterpret_cast<void**>(&jvmti_env), JVMTI_VERSION_1_0)) { + printf("Unable to get jvmti env!\n"); + return 1; + } + SetupCommonTransform(); + return 0; +} + +} // namespace common_transform + +static void SetupCommonRedefine() { + jvmtiCapabilities caps; + jvmti_env->GetPotentialCapabilities(&caps); + caps.can_retransform_classes = 0; + caps.can_retransform_any_class = 0; + jvmti_env->AddCapabilities(&caps); +} + +static void SetupCommonRetransform() { + SetAllCapabilities(jvmti_env); + jvmtiEventCallbacks cb; + memset(&cb, 0, sizeof(cb)); + cb.ClassFileLoadHook = common_retransform::CommonClassFileLoadHookRetransformable; + jvmtiError res = jvmti_env->SetEventCallbacks(&cb, sizeof(cb)); + CHECK_EQ(res, JVMTI_ERROR_NONE); + common_retransform::gTransformations.clear(); +} + +static void SetupCommonTransform() { + // Don't set the retransform caps + jvmtiCapabilities caps; + jvmti_env->GetPotentialCapabilities(&caps); + caps.can_retransform_classes = 0; + caps.can_retransform_any_class = 0; + jvmti_env->AddCapabilities(&caps); + + // Use the same callback as the retransform test. + jvmtiEventCallbacks cb; + memset(&cb, 0, sizeof(cb)); + cb.ClassFileLoadHook = common_retransform::CommonClassFileLoadHookRetransformable; + jvmtiError res = jvmti_env->SetEventCallbacks(&cb, sizeof(cb)); + CHECK_EQ(res, JVMTI_ERROR_NONE); + common_retransform::gTransformations.clear(); +} + +} // namespace art diff --git a/test/ti-agent/trace_helper.cc b/test/ti-agent/trace_helper.cc new file mode 100644 index 0000000000..7a9d1e0d92 --- /dev/null +++ b/test/ti-agent/trace_helper.cc @@ -0,0 +1,493 @@ +/* + * 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<void**>(&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<jlong>(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<void**>(&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<jlong>(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<void**>(&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<jlong>(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<void**>(&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<void**>(&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<void**>(&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<unsigned char*>(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<void**>(&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<unsigned char*>(klasses)); + return; + } + for (jint j = 0; j < nfields; j++) { + jvmti_env->SetFieldAccessWatch(k, fields[j]); + } + jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(fields)); + } + jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(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<void**>(&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<unsigned char*>(klasses)); + return; + } + for (jint j = 0; j < nfields; j++) { + jvmti_env->SetFieldModificationWatch(k, fields[j]); + } + jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(fields)); + } + jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(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<jclass>(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<unsigned char**>(&data)))) { + return; + } + memset(data, 0, sizeof(TraceData)); + data->test_klass = reinterpret_cast<jclass>(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<jclass> 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 |