/*
* 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