You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
213 lines
7.8 KiB
213 lines
7.8 KiB
/*
|
|
* 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
|
|
|