/* * 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 <jni.h> #include <stack> #include <string> #include <unordered_map> #include <vector> #include "android-base/logging.h" #include "android-base/macros.h" #include "jni_binder.h" #include "jni_helper.h" #include "jvmti_helper.h" #include "jvmti.h" #include "scoped_primitive_array.h" #include "test_env.h" namespace art { extern "C" JNIEXPORT jint JNICALL Java_android_jvmti_cts_JvmtiRedefineClassesTest_redefineClass( JNIEnv* env, jclass klass ATTRIBUTE_UNUSED, jclass target, jbyteArray dex_bytes) { jvmtiClassDefinition def; def.klass = target; def.class_byte_count = static_cast<jint>(env->GetArrayLength(dex_bytes)); signed char* redef_bytes = env->GetByteArrayElements(dex_bytes, nullptr); jvmtiError res =jvmti_env->Allocate(def.class_byte_count, const_cast<unsigned char**>(&def.class_bytes)); if (res != JVMTI_ERROR_NONE) { return static_cast<jint>(res); } memcpy(const_cast<unsigned char*>(def.class_bytes), redef_bytes, def.class_byte_count); env->ReleaseByteArrayElements(dex_bytes, redef_bytes, 0); // Do the redefinition. res = jvmti_env->RedefineClasses(1, &def); return static_cast<jint>(res); } extern "C" JNIEXPORT jint JNICALL Java_android_jvmti_cts_JvmtiRedefineClassesTest_retransformClass( JNIEnv* env ATTRIBUTE_UNUSED, jclass klass ATTRIBUTE_UNUSED, jclass target) { return jvmti_env->RetransformClasses(1, &target); } class TransformationData { public: TransformationData() : redefinitions_(), should_pop_(false) {} void SetPop(bool val) { should_pop_ = val; } void ClearRedefinitions() { redefinitions_.clear(); } void PushRedefinition(std::string name, std::vector<unsigned char> data) { if (redefinitions_.find(name) == redefinitions_.end()) { std::stack<std::vector<unsigned char>> stack; redefinitions_[name] = std::move(stack); } redefinitions_[name].push(std::move(data)); } bool RetrieveRedefinition(std::string name, /*out*/std::vector<unsigned char>* data) { auto stack = redefinitions_.find(name); if (stack == redefinitions_.end() || stack->second.empty()) { return false; } else { *data = stack->second.top(); return true; } } void PopRedefinition(std::string name) { if (should_pop_) { auto stack = redefinitions_.find(name); if (stack == redefinitions_.end() || stack->second.empty()) { return; } else { stack->second.pop(); } } } private: std::unordered_map<std::string, std::stack<std::vector<unsigned char>>> redefinitions_; bool should_pop_; }; static TransformationData data; // The hook we are using. void JNICALL CommonClassFileLoadHookRetransformable(jvmtiEnv* local_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); std::vector<unsigned char> dex_data; if (data.RetrieveRedefinition(name_str, &dex_data)) { unsigned char* jvmti_dex_data; if (JVMTI_ERROR_NONE == local_jvmti_env->Allocate(dex_data.size(), &jvmti_dex_data)) { memcpy(jvmti_dex_data, dex_data.data(), dex_data.size()); *new_class_data_len = dex_data.size(); *new_class_data = jvmti_dex_data; data.PopRedefinition(name); } else { LOG(FATAL) << "Unable to allocate output buffer for " << name; } } } extern "C" JNIEXPORT void JNICALL Java_android_jvmti_cts_JvmtiRedefineClassesTest_setTransformationEvent( JNIEnv* env, jclass klass ATTRIBUTE_UNUSED, jboolean enable) { jvmtiEventCallbacks cb; memset(&cb, 0, sizeof(cb)); cb.ClassFileLoadHook = CommonClassFileLoadHookRetransformable; if (JvmtiErrorToException(env, jvmti_env, jvmti_env->SetEventCallbacks(&cb, sizeof(cb)))) { return; } JvmtiErrorToException(env, jvmti_env, jvmti_env->SetEventNotificationMode( enable == JNI_TRUE ? JVMTI_ENABLE : JVMTI_DISABLE, JVMTI_EVENT_CLASS_FILE_LOAD_HOOK, nullptr)); return; } extern "C" JNIEXPORT void JNICALL Java_android_jvmti_cts_JvmtiRedefineClassesTest_clearTransformations( JNIEnv* env ATTRIBUTE_UNUSED, jclass klass ATTRIBUTE_UNUSED) { data.ClearRedefinitions(); } extern "C" JNIEXPORT void JNICALL Java_android_jvmti_cts_JvmtiRedefineClassesTest_setPopTransformations( JNIEnv* env ATTRIBUTE_UNUSED, jclass klass ATTRIBUTE_UNUSED, jboolean enable) { data.SetPop(enable == JNI_TRUE ? true : false); } extern "C" JNIEXPORT void JNICALL Java_android_jvmti_cts_JvmtiRedefineClassesTest_pushTransformationResult( JNIEnv* env, jclass klass ATTRIBUTE_UNUSED, jstring class_name, jbyteArray dex_bytes) { const char* name_chrs = env->GetStringUTFChars(class_name, nullptr); std::string name_str(name_chrs); env->ReleaseStringUTFChars(class_name, name_chrs); std::vector<unsigned char> dex_data; dex_data.resize(env->GetArrayLength(dex_bytes)); signed char* redef_bytes = env->GetByteArrayElements(dex_bytes, nullptr); memcpy(dex_data.data(), redef_bytes, env->GetArrayLength(dex_bytes)); data.PushRedefinition(std::move(name_str), std::move(dex_data)); env->ReleaseByteArrayElements(dex_bytes, redef_bytes, 0); } static JNINativeMethod gMethods[] = { { "redefineClass", "(Ljava/lang/Class;[B)I", (void*)Java_android_jvmti_cts_JvmtiRedefineClassesTest_redefineClass }, { "retransformClass", "(Ljava/lang/Class;)I", (void*)Java_android_jvmti_cts_JvmtiRedefineClassesTest_retransformClass }, { "setTransformationEvent", "(Z)V", (void*)Java_android_jvmti_cts_JvmtiRedefineClassesTest_setTransformationEvent }, { "clearTransformations", "()V", (void*)Java_android_jvmti_cts_JvmtiRedefineClassesTest_clearTransformations }, { "setPopTransformations", "(Z)V", (void*)Java_android_jvmti_cts_JvmtiRedefineClassesTest_setPopTransformations }, { "pushTransformationResult", "(Ljava/lang/String;[B)V", (void*)Java_android_jvmti_cts_JvmtiRedefineClassesTest_pushTransformationResult }, }; void register_android_jvmti_cts_JvmtiRedefineClassesTest(jvmtiEnv* jenv, JNIEnv* env) { ScopedLocalRef<jclass> klass(env, GetClass(jenv, env, "android/jvmti/cts/JvmtiRedefineClassesTest", nullptr)); if (klass.get() == nullptr) { env->ExceptionClear(); return; } env->RegisterNatives(klass.get(), gMethods, sizeof(gMethods) / sizeof(JNINativeMethod)); if (env->ExceptionCheck()) { env->ExceptionClear(); LOG(ERROR) << "Could not register natives for JvmtiRedefineClassesTest class"; } } } // namespace art