diff options
author | 2019-10-15 15:46:07 -0700 | |
---|---|---|
committer | 2019-10-30 16:53:25 -0700 | |
commit | d55b844e39c4d5eb1a56de6cb95c891659f8a27f (patch) | |
tree | ed2f7809528e8b44985edc12d75fb0965806045f | |
parent | 436c6f5fae95aae332361060778599d0ef24a167 (diff) |
Add more standard structural redefinition entrypoints
Add structural redefinition extension function and event that mirror
the 'RedefineClasses' function and 'ClassFileLoadHook' event. The new
extension function is called
'com.android.art.class.structurally_redefine_classes' and the new
extension event is called
'com.android.art.class.structural_dex_file_load_hook'.
These extensions are the preferred way to use structural redefinition.
Like the standard 'RedefineClasses' multiple classes may be redefined
at a time.
The structural_dex_file_load_hook is triggered prior to the
can_retransform_classes ClassFileLoadHook. It is triggered on all
classes, even ones that cannot be structurally changed by
class-loading, class redefinition or by calling the RetransformClasses
function.
Calling 'structurally_redefine_classes' with new definitions that do
not require structural changes will fall back to non-structural
redefinition.
Test: ./test.py --host
Bug: 134162467
Change-Id: If4810930470c5c6509cf6db779910006e114b39f
33 files changed, 805 insertions, 108 deletions
diff --git a/openjdkjvmti/events-inl.h b/openjdkjvmti/events-inl.h index 22822f8b40..23f7151c99 100644 --- a/openjdkjvmti/events-inl.h +++ b/openjdkjvmti/events-inl.h @@ -90,41 +90,42 @@ class ScopedEventDispatchEnvironment final : public art::ValueObject { // Infrastructure to achieve type safety for event dispatch. -#define FORALL_EVENT_TYPES(fn) \ - fn(VMInit, ArtJvmtiEvent::kVmInit) \ - fn(VMDeath, ArtJvmtiEvent::kVmDeath) \ - fn(ThreadStart, ArtJvmtiEvent::kThreadStart) \ - fn(ThreadEnd, ArtJvmtiEvent::kThreadEnd) \ - fn(ClassFileLoadHook, ArtJvmtiEvent::kClassFileLoadHookRetransformable) \ - fn(ClassFileLoadHook, ArtJvmtiEvent::kClassFileLoadHookNonRetransformable) \ - fn(ClassLoad, ArtJvmtiEvent::kClassLoad) \ - fn(ClassPrepare, ArtJvmtiEvent::kClassPrepare) \ - fn(VMStart, ArtJvmtiEvent::kVmStart) \ - fn(Exception, ArtJvmtiEvent::kException) \ - fn(ExceptionCatch, ArtJvmtiEvent::kExceptionCatch) \ - fn(SingleStep, ArtJvmtiEvent::kSingleStep) \ - fn(FramePop, ArtJvmtiEvent::kFramePop) \ - fn(Breakpoint, ArtJvmtiEvent::kBreakpoint) \ - fn(FieldAccess, ArtJvmtiEvent::kFieldAccess) \ - fn(FieldModification, ArtJvmtiEvent::kFieldModification) \ - fn(MethodEntry, ArtJvmtiEvent::kMethodEntry) \ - fn(MethodExit, ArtJvmtiEvent::kMethodExit) \ - fn(NativeMethodBind, ArtJvmtiEvent::kNativeMethodBind) \ - fn(CompiledMethodLoad, ArtJvmtiEvent::kCompiledMethodLoad) \ - fn(CompiledMethodUnload, ArtJvmtiEvent::kCompiledMethodUnload) \ - fn(DynamicCodeGenerated, ArtJvmtiEvent::kDynamicCodeGenerated) \ - fn(DataDumpRequest, ArtJvmtiEvent::kDataDumpRequest) \ - fn(MonitorWait, ArtJvmtiEvent::kMonitorWait) \ - fn(MonitorWaited, ArtJvmtiEvent::kMonitorWaited) \ - fn(MonitorContendedEnter, ArtJvmtiEvent::kMonitorContendedEnter) \ - fn(MonitorContendedEntered, ArtJvmtiEvent::kMonitorContendedEntered) \ - fn(ResourceExhausted, ArtJvmtiEvent::kResourceExhausted) \ - fn(GarbageCollectionStart, ArtJvmtiEvent::kGarbageCollectionStart) \ - fn(GarbageCollectionFinish, ArtJvmtiEvent::kGarbageCollectionFinish) \ - fn(ObjectFree, ArtJvmtiEvent::kObjectFree) \ - fn(VMObjectAlloc, ArtJvmtiEvent::kVmObjectAlloc) \ - fn(DdmPublishChunk, ArtJvmtiEvent::kDdmPublishChunk) \ - fn(ObsoleteObjectCreated, ArtJvmtiEvent::kObsoleteObjectCreated) +#define FORALL_EVENT_TYPES(fn) \ + fn(VMInit, ArtJvmtiEvent::kVmInit) \ + fn(VMDeath, ArtJvmtiEvent::kVmDeath) \ + fn(ThreadStart, ArtJvmtiEvent::kThreadStart) \ + fn(ThreadEnd, ArtJvmtiEvent::kThreadEnd) \ + fn(ClassFileLoadHook, ArtJvmtiEvent::kClassFileLoadHookRetransformable) \ + fn(ClassFileLoadHook, ArtJvmtiEvent::kClassFileLoadHookNonRetransformable) \ + fn(ClassLoad, ArtJvmtiEvent::kClassLoad) \ + fn(ClassPrepare, ArtJvmtiEvent::kClassPrepare) \ + fn(VMStart, ArtJvmtiEvent::kVmStart) \ + fn(Exception, ArtJvmtiEvent::kException) \ + fn(ExceptionCatch, ArtJvmtiEvent::kExceptionCatch) \ + fn(SingleStep, ArtJvmtiEvent::kSingleStep) \ + fn(FramePop, ArtJvmtiEvent::kFramePop) \ + fn(Breakpoint, ArtJvmtiEvent::kBreakpoint) \ + fn(FieldAccess, ArtJvmtiEvent::kFieldAccess) \ + fn(FieldModification, ArtJvmtiEvent::kFieldModification) \ + fn(MethodEntry, ArtJvmtiEvent::kMethodEntry) \ + fn(MethodExit, ArtJvmtiEvent::kMethodExit) \ + fn(NativeMethodBind, ArtJvmtiEvent::kNativeMethodBind) \ + fn(CompiledMethodLoad, ArtJvmtiEvent::kCompiledMethodLoad) \ + fn(CompiledMethodUnload, ArtJvmtiEvent::kCompiledMethodUnload) \ + fn(DynamicCodeGenerated, ArtJvmtiEvent::kDynamicCodeGenerated) \ + fn(DataDumpRequest, ArtJvmtiEvent::kDataDumpRequest) \ + fn(MonitorWait, ArtJvmtiEvent::kMonitorWait) \ + fn(MonitorWaited, ArtJvmtiEvent::kMonitorWaited) \ + fn(MonitorContendedEnter, ArtJvmtiEvent::kMonitorContendedEnter) \ + fn(MonitorContendedEntered, ArtJvmtiEvent::kMonitorContendedEntered) \ + fn(ResourceExhausted, ArtJvmtiEvent::kResourceExhausted) \ + fn(GarbageCollectionStart, ArtJvmtiEvent::kGarbageCollectionStart) \ + fn(GarbageCollectionFinish, ArtJvmtiEvent::kGarbageCollectionFinish) \ + fn(ObjectFree, ArtJvmtiEvent::kObjectFree) \ + fn(VMObjectAlloc, ArtJvmtiEvent::kVmObjectAlloc) \ + fn(DdmPublishChunk, ArtJvmtiEvent::kDdmPublishChunk) \ + fn(ObsoleteObjectCreated, ArtJvmtiEvent::kObsoleteObjectCreated) \ + fn(StructuralDexFileLoadHook, ArtJvmtiEvent::kStructuralDexFileLoadHook) template <ArtJvmtiEvent kEvent> struct EventFnType { @@ -217,7 +218,8 @@ inline void EventHandler::DispatchClassFileLoadHookEvent(art::Thread* thread, unsigned char** new_class_data) const { art::ScopedThreadStateChange stsc(thread, art::ThreadState::kNative); static_assert(kEvent == ArtJvmtiEvent::kClassFileLoadHookRetransformable || - kEvent == ArtJvmtiEvent::kClassFileLoadHookNonRetransformable, "Unsupported event"); + kEvent == ArtJvmtiEvent::kClassFileLoadHookNonRetransformable || + kEvent == ArtJvmtiEvent::kStructuralDexFileLoadHook, "Unsupported event"); DCHECK(*new_class_data == nullptr); jint current_len = class_data_len; unsigned char* current_class_data = const_cast<unsigned char*>(class_data); @@ -588,6 +590,31 @@ inline void EventHandler::DispatchEvent<ArtJvmtiEvent::kClassFileLoadHookNonRetr new_class_data); } +template <> +inline void EventHandler::DispatchEvent<ArtJvmtiEvent::kStructuralDexFileLoadHook>( + art::Thread* thread, + JNIEnv* jnienv, + jclass class_being_redefined, + jobject loader, + const char* name, + jobject protection_domain, + jint class_data_len, + const unsigned char* class_data, + jint* new_class_data_len, + unsigned char** new_class_data) const { + return DispatchClassFileLoadHookEvent<ArtJvmtiEvent::kStructuralDexFileLoadHook>( + thread, + jnienv, + class_being_redefined, + loader, + name, + protection_domain, + class_data_len, + class_data, + new_class_data_len, + new_class_data); +} + template <ArtJvmtiEvent kEvent> inline bool EventHandler::ShouldDispatchOnThread(ArtJvmTiEnv* env, art::Thread* thread) const { bool dispatch = env->event_masks.global_event_mask.Test(kEvent); diff --git a/openjdkjvmti/events.cc b/openjdkjvmti/events.cc index 3f205eb6f4..56406fc81d 100644 --- a/openjdkjvmti/events.cc +++ b/openjdkjvmti/events.cc @@ -100,6 +100,9 @@ jvmtiError ArtJvmtiEventCallbacks::Set(jint index, jvmtiExtensionEvent cb) { case static_cast<jint>(ArtJvmtiEvent::kDdmPublishChunk): DdmPublishChunk = reinterpret_cast<ArtJvmtiEventDdmPublishChunk>(cb); return OK; + case static_cast<jint>(ArtJvmtiEvent::kStructuralDexFileLoadHook): + StructuralDexFileLoadHook = reinterpret_cast<ArtJvmtiEventStructuralDexFileLoadHook>(cb); + return OK; default: return ERR(ILLEGAL_ARGUMENT); } @@ -116,6 +119,7 @@ bool IsExtensionEvent(ArtJvmtiEvent e) { switch (e) { case ArtJvmtiEvent::kDdmPublishChunk: case ArtJvmtiEvent::kObsoleteObjectCreated: + case ArtJvmtiEvent::kStructuralDexFileLoadHook: return true; default: return false; @@ -1175,6 +1179,7 @@ static DeoptRequirement GetDeoptRequirement(ArtJvmtiEvent event, jthread thread) case ArtJvmtiEvent::kClassFileLoadHookRetransformable: case ArtJvmtiEvent::kDdmPublishChunk: case ArtJvmtiEvent::kObsoleteObjectCreated: + case ArtJvmtiEvent::kStructuralDexFileLoadHook: return DeoptRequirement::kNone; } } diff --git a/openjdkjvmti/events.h b/openjdkjvmti/events.h index d5ab4fbc98..c9d587af94 100644 --- a/openjdkjvmti/events.h +++ b/openjdkjvmti/events.h @@ -81,7 +81,8 @@ enum class ArtJvmtiEvent : jint { kClassFileLoadHookRetransformable = JVMTI_MAX_EVENT_TYPE_VAL + 1, kDdmPublishChunk = JVMTI_MAX_EVENT_TYPE_VAL + 2, kObsoleteObjectCreated = JVMTI_MAX_EVENT_TYPE_VAL + 3, - kMaxNormalEventTypeVal = kObsoleteObjectCreated, + kStructuralDexFileLoadHook = JVMTI_MAX_EVENT_TYPE_VAL + 4, + kMaxNormalEventTypeVal = kStructuralDexFileLoadHook, // All that follow are events used to implement internal JVMTI functions. They are not settable // directly by agents. @@ -107,6 +108,17 @@ using ArtJvmtiEventObsoleteObjectCreated = void (*)(jvmtiEnv *jvmti_env, jlong* obsolete_tag, jlong* new_tag); +using ArtJvmtiEventStructuralDexFileLoadHook = void (*)(jvmtiEnv *jvmti_env, + JNIEnv* jni_env, + jclass class_being_redefined, + jobject loader, + const char* name, + jobject protection_domain, + jint dex_data_len, + const unsigned char* dex_data, + jint* new_dex_data_len, + unsigned char** new_dex_data); + // It is not enough to store a Thread pointer, as these may be reused. Use the pointer and the // thread id. // Note: We could just use the tid like tracing does. @@ -119,7 +131,10 @@ struct UniqueThreadHasher { }; struct ArtJvmtiEventCallbacks : jvmtiEventCallbacks { - ArtJvmtiEventCallbacks() : DdmPublishChunk(nullptr), ObsoleteObjectCreated(nullptr) { + ArtJvmtiEventCallbacks() + : DdmPublishChunk(nullptr), + ObsoleteObjectCreated(nullptr), + StructuralDexFileLoadHook(nullptr) { memset(this, 0, sizeof(jvmtiEventCallbacks)); } @@ -131,6 +146,7 @@ struct ArtJvmtiEventCallbacks : jvmtiEventCallbacks { ArtJvmtiEventDdmPublishChunk DdmPublishChunk; ArtJvmtiEventObsoleteObjectCreated ObsoleteObjectCreated; + ArtJvmtiEventStructuralDexFileLoadHook StructuralDexFileLoadHook; }; bool IsExtensionEvent(jint e); diff --git a/openjdkjvmti/ti_class.cc b/openjdkjvmti/ti_class.cc index 988274b4d7..82ce916ccd 100644 --- a/openjdkjvmti/ti_class.cc +++ b/openjdkjvmti/ti_class.cc @@ -204,6 +204,9 @@ struct ClassCallback : public art::ClassLoadCallback { memcpy(post_non_retransform.data(), def.GetDexData().data(), post_non_retransform.size()); } + // Call all structural transformation agents. + Transformer::TransformSingleClassDirect<ArtJvmtiEvent::kStructuralDexFileLoadHook>( + event_handler, self, &def); // Call all retransformable agents. Transformer::TransformSingleClassDirect<ArtJvmtiEvent::kClassFileLoadHookRetransformable>( event_handler, self, &def); diff --git a/openjdkjvmti/ti_class_definition.h b/openjdkjvmti/ti_class_definition.h index 224e664459..cb0853bdb5 100644 --- a/openjdkjvmti/ti_class_definition.h +++ b/openjdkjvmti/ti_class_definition.h @@ -40,6 +40,7 @@ #include "base/array_ref.h" #include "base/mem_map.h" +#include "events.h" namespace openjdkjvmti { @@ -65,7 +66,8 @@ class ArtClassDefinition { current_dex_file_(), redefined_(false), from_class_ext_(false), - initialized_(false) {} + initialized_(false), + structural_transform_update_(false) {} void InitFirstLoad(const char* descriptor, art::Handle<art::mirror::ClassLoader> klass_loader, @@ -76,7 +78,7 @@ class ArtClassDefinition { ArtClassDefinition(ArtClassDefinition&& o) = default; ArtClassDefinition& operator=(ArtClassDefinition&& o) = default; - void SetNewDexData(jint new_dex_len, unsigned char* new_dex_data) { + void SetNewDexData(jint new_dex_len, unsigned char* new_dex_data, ArtJvmtiEvent event) { DCHECK(IsInitialized()); if (new_dex_data == nullptr) { return; @@ -86,10 +88,17 @@ class ArtClassDefinition { dex_data_memory_.resize(new_dex_len); memcpy(dex_data_memory_.data(), new_dex_data, new_dex_len); dex_data_ = art::ArrayRef<const unsigned char>(dex_data_memory_); + if (event == ArtJvmtiEvent::kStructuralDexFileLoadHook) { + structural_transform_update_ = true; + } } } } + bool HasStructuralChanges() const { + return structural_transform_update_; + } + art::ArrayRef<const unsigned char> GetNewOriginalDexFile() const { DCHECK(IsInitialized()); if (redefined_) { @@ -187,6 +196,9 @@ class ArtClassDefinition { bool initialized_; + // Set if we had a new dex from the given transform type. + bool structural_transform_update_; + DISALLOW_COPY_AND_ASSIGN(ArtClassDefinition); }; diff --git a/openjdkjvmti/ti_extension.cc b/openjdkjvmti/ti_extension.cc index 5dc7445681..058a188630 100644 --- a/openjdkjvmti/ti_extension.cc +++ b/openjdkjvmti/ti_extension.cc @@ -30,6 +30,7 @@ #include <vector> +#include "jvmti.h" #include "ti_extension.h" #include "art_jvmti.h" @@ -45,6 +46,7 @@ #include "ti_monitor.h" #include "ti_redefine.h" #include "ti_search.h" +#include "transform.h" #include "thread-inl.h" @@ -416,7 +418,40 @@ jvmtiError ExtensionUtil::GetExtensionFunctions(jvmtiEnv* env, return error; } - // StructurallyRedefineClass + // StructurallyRedefineClasses + error = add_extension( + reinterpret_cast<jvmtiExtensionFunction>(Redefiner::StructurallyRedefineClasses), + "com.android.art.class.structurally_redefine_classes", + "Entrypoint for structural class redefinition. Has the same signature as RedefineClasses." + " Currently this only supports adding new static fields to a class without any instance" + " fields or methods. After calling this com.android.art.structural_dex_file_load_hook" + " events will be triggered, followed by re-transformable ClassFileLoadHook events. After" + " this method completes subsequent RetransformClasses calls will use the input to this" + " function as the initial class definition.", + { + { "num_classes", JVMTI_KIND_IN, JVMTI_TYPE_JINT, false }, + { "class_definitions", JVMTI_KIND_IN_BUF, JVMTI_TYPE_CVOID, false }, + }, + { + ERR(CLASS_LOADER_UNSUPPORTED), + ERR(FAILS_VERIFICATION), + ERR(ILLEGAL_ARGUMENT), + ERR(INVALID_CLASS), + ERR(MUST_POSSESS_CAPABILITY), + ERR(MUST_POSSESS_CAPABILITY), + ERR(NULL_POINTER), + ERR(OUT_OF_MEMORY), + ERR(UNMODIFIABLE_CLASS), + ERR(UNSUPPORTED_REDEFINITION_HIERARCHY_CHANGED), + ERR(UNSUPPORTED_REDEFINITION_METHOD_ADDED), + ERR(UNSUPPORTED_REDEFINITION_METHOD_DELETED), + ERR(UNSUPPORTED_REDEFINITION_SCHEMA_CHANGED), + }); + if (error != ERR(NONE)) { + return error; + } + + // StructurallyRedefineClassDirect error = add_extension( reinterpret_cast<jvmtiExtensionFunction>(Redefiner::StructurallyRedefineClassDirect), "com.android.art.UNSAFE.class.structurally_redefine_class_direct", @@ -494,7 +529,7 @@ jvmtiError ExtensionUtil::GetExtensionEvents(jvmtiEnv* env, const char* id, const char* short_description, const std::vector<CParamInfo>& params) { - DCHECK(IsExtensionEvent(extension_event_index)); + DCHECK(IsExtensionEvent(extension_event_index)) << static_cast<jint>(extension_event_index); jvmtiExtensionEventInfo event_info; jvmtiError error; @@ -592,7 +627,35 @@ jvmtiError ExtensionUtil::GetExtensionEvents(jvmtiEnv* env, if (error != OK) { return error; } - + art::Runtime* runtime = art::Runtime::Current(); + if (runtime->GetJniIdType() == art::JniIdType::kIndices && + (runtime->GetInstrumentation()->IsForcedInterpretOnly() || runtime->IsJavaDebuggable())) { + error = add_extension( + ArtJvmtiEvent::kStructuralDexFileLoadHook, + "com.android.art.class.structural_dex_file_load_hook", + "Called during class load, after a 'RetransformClasses' call, or after a 'RedefineClasses'" + " call in order to allow the agent to modify the class. This event is called after any" + " non-can_retransform_classes ClassFileLoadHookEvents and before any" + " can_retransform_classes ClassFileLoadHookEvents. The transformations applied are" + " restricted in the same way that transformations applied via the " + " 'com.android.art.class.structurally_redefine_classes' extension function. The arguments" + " to the event are identical to the ones in the ClassFileLoadHook and have the same" + " semantics.", + { + { "jni_env", JVMTI_KIND_IN, JVMTI_TYPE_JNIENV, false }, + { "class_being_redefined", JVMTI_KIND_IN, JVMTI_TYPE_JCLASS, true }, + { "loader", JVMTI_KIND_IN, JVMTI_TYPE_JOBJECT, false }, + { "name", JVMTI_KIND_IN_PTR, JVMTI_TYPE_CCHAR, false }, + { "protection_domain", JVMTI_KIND_IN, JVMTI_TYPE_JOBJECT, true }, + { "dex_data_len", JVMTI_KIND_IN, JVMTI_TYPE_JINT, false }, + { "dex_data", JVMTI_KIND_IN_BUF, JVMTI_TYPE_CCHAR, false }, + { "new_dex_data_len", JVMTI_KIND_OUT, JVMTI_TYPE_JINT, false }, + { "new_dex_data", JVMTI_KIND_ALLOC_BUF, JVMTI_TYPE_CCHAR, true }, + }); + } else { + LOG(INFO) << "debuggable & jni-type indices are required to implement structural " + << "class redefinition extensions."; + } // Copy into output buffer. *extension_count_ptr = ext_vector.size(); diff --git a/openjdkjvmti/ti_redefine.cc b/openjdkjvmti/ti_redefine.cc index 05d7de7f10..22a3bc5f06 100644 --- a/openjdkjvmti/ti_redefine.cc +++ b/openjdkjvmti/ti_redefine.cc @@ -376,7 +376,7 @@ jvmtiError Redefiner::GetClassRedefinitionError(jclass klass, /*out*/ std::strin return ERR(INVALID_CLASS); } art::Handle<art::mirror::Class> h_klass(hs.NewHandle(obj->AsClass())); - return Redefiner::GetClassRedefinitionError(h_klass, error_msg); + return Redefiner::GetClassRedefinitionError<kType>(h_klass, error_msg); } template <RedefinitionType kType> @@ -574,9 +574,10 @@ Redefiner::ClassRedefinition::~ClassRedefinition() { } } -jvmtiError Redefiner::RedefineClasses(jvmtiEnv* jenv, - jint class_count, - const jvmtiClassDefinition* definitions) { +template<RedefinitionType kType> +jvmtiError Redefiner::RedefineClassesGeneric(jvmtiEnv* jenv, + jint class_count, + const jvmtiClassDefinition* definitions) { art::Runtime* runtime = art::Runtime::Current(); art::Thread* self = art::Thread::Current(); ArtJvmTiEnv* env = ArtJvmTiEnv::AsArtJvmTiEnv(jenv); @@ -597,7 +598,8 @@ jvmtiError Redefiner::RedefineClasses(jvmtiEnv* jenv, std::vector<ArtClassDefinition> def_vector; def_vector.reserve(class_count); for (jint i = 0; i < class_count; i++) { - jvmtiError res = Redefiner::GetClassRedefinitionError(definitions[i].klass, &error_msg); + jvmtiError res = Redefiner::GetClassRedefinitionError<RedefinitionType::kNormal>( + definitions[i].klass, &error_msg); if (res != OK) { JVMTI_LOG(WARNING, env) << "FAILURE TO REDEFINE " << error_msg; return res; @@ -611,15 +613,35 @@ jvmtiError Redefiner::RedefineClasses(jvmtiEnv* jenv, def_vector.push_back(std::move(def)); } // Call all the transformation events. - Transformer::RetransformClassesDirect(self, &def_vector); - jvmtiError res = RedefineClassesDirect( - env, runtime, self, def_vector, RedefinitionType::kNormal, &error_msg); + Transformer::RetransformClassesDirect<kType>(self, &def_vector); + if (kType == RedefinitionType::kStructural) { + Transformer::RetransformClassesDirect<RedefinitionType::kNormal>(self, &def_vector); + } + jvmtiError res = RedefineClassesDirect(env, runtime, self, def_vector, kType, &error_msg); if (res != OK) { JVMTI_LOG(WARNING, env) << "FAILURE TO REDEFINE " << error_msg; } return res; } +jvmtiError Redefiner::StructurallyRedefineClasses(jvmtiEnv* jenv, + jint class_count, + const jvmtiClassDefinition* definitions) { + ArtJvmTiEnv* art_env = ArtJvmTiEnv::AsArtJvmTiEnv(jenv); + if (art_env == nullptr) { + return ERR(INVALID_ENVIRONMENT); + } else if (art_env->capabilities.can_redefine_classes != 1) { + return ERR(MUST_POSSESS_CAPABILITY); + } + return RedefineClassesGeneric<RedefinitionType::kStructural>(jenv, class_count, definitions); +} + +jvmtiError Redefiner::RedefineClasses(jvmtiEnv* jenv, + jint class_count, + const jvmtiClassDefinition* definitions) { + return RedefineClassesGeneric<RedefinitionType::kNormal>(jenv, class_count, definitions); +} + jvmtiError Redefiner::StructurallyRedefineClassDirect(jvmtiEnv* env, jclass klass, const unsigned char* data, @@ -1154,13 +1176,10 @@ bool Redefiner::ClassRedefinition::CheckRedefinable() { art::Handle<art::mirror::Class> h_klass(hs.NewHandle(GetMirrorClass())); jvmtiError res; - switch (driver_->type_) { - case RedefinitionType::kNormal: - res = Redefiner::GetClassRedefinitionError<RedefinitionType::kNormal>(h_klass, &err); - break; - case RedefinitionType::kStructural: + if (driver_->type_ == RedefinitionType::kStructural && this->IsStructuralRedefinition()) { res = Redefiner::GetClassRedefinitionError<RedefinitionType::kStructural>(h_klass, &err); - break; + } else { + res = Redefiner::GetClassRedefinitionError<RedefinitionType::kNormal>(h_klass, &err); } if (res != OK) { RecordFailure(res, err); @@ -1171,7 +1190,7 @@ bool Redefiner::ClassRedefinition::CheckRedefinable() { } bool Redefiner::ClassRedefinition::CheckRedefinitionIsValid() { - return CheckRedefinable() && CheckClass() && CheckFields() && CheckMethods(); + return CheckClass() && CheckFields() && CheckMethods() && CheckRedefinable(); } class RedefinitionDataIter; diff --git a/openjdkjvmti/ti_redefine.h b/openjdkjvmti/ti_redefine.h index b028dee8a2..58a688c1a0 100644 --- a/openjdkjvmti/ti_redefine.h +++ b/openjdkjvmti/ti_redefine.h @@ -87,6 +87,9 @@ class Redefiner { static jvmtiError RedefineClasses(jvmtiEnv* env, jint class_count, const jvmtiClassDefinition* definitions); + static jvmtiError StructurallyRedefineClasses(jvmtiEnv* env, + jint class_count, + const jvmtiClassDefinition* definitions); static jvmtiError IsModifiableClass(jvmtiEnv* env, jclass klass, jboolean* is_redefinable); static jvmtiError IsStructurallyModifiableClass(jvmtiEnv* env, @@ -287,6 +290,11 @@ class Redefiner { REQUIRES_SHARED(art::Locks::mutator_lock_); template<RedefinitionType kType = RedefinitionType::kNormal> + static jvmtiError RedefineClassesGeneric(jvmtiEnv* env, + jint class_count, + const jvmtiClassDefinition* definitions); + + template<RedefinitionType kType = RedefinitionType::kNormal> static jvmtiError IsModifiableClassGeneric(jvmtiEnv* env, jclass klass, jboolean* is_redefinable); template<RedefinitionType kType = RedefinitionType::kNormal> diff --git a/openjdkjvmti/transform.cc b/openjdkjvmti/transform.cc index aa37793e49..613368525e 100644 --- a/openjdkjvmti/transform.cc +++ b/openjdkjvmti/transform.cc @@ -255,13 +255,17 @@ void Transformer::TransformSingleClassDirect<ArtJvmtiEvent::kClassFileLoadHookNo template void Transformer::TransformSingleClassDirect<ArtJvmtiEvent::kClassFileLoadHookRetransformable>( EventHandler* event_handler, art::Thread* self, /*in-out*/ArtClassDefinition* def); +template +void Transformer::TransformSingleClassDirect<ArtJvmtiEvent::kStructuralDexFileLoadHook>( + EventHandler* event_handler, art::Thread* self, /*in-out*/ArtClassDefinition* def); template<ArtJvmtiEvent kEvent> void Transformer::TransformSingleClassDirect(EventHandler* event_handler, art::Thread* self, /*in-out*/ArtClassDefinition* def) { static_assert(kEvent == ArtJvmtiEvent::kClassFileLoadHookNonRetransformable || - kEvent == ArtJvmtiEvent::kClassFileLoadHookRetransformable, + kEvent == ArtJvmtiEvent::kClassFileLoadHookRetransformable || + kEvent == ArtJvmtiEvent::kStructuralDexFileLoadHook, "bad event type"); // We don't want to do transitions between calling the event and setting the new data so change to // native state early. This also avoids any problems that the FaultHandler might have in @@ -282,25 +286,30 @@ void Transformer::TransformSingleClassDirect(EventHandler* event_handler, dex_data.data(), /*out*/&new_len, /*out*/&new_data); - def->SetNewDexData(new_len, new_data); + def->SetNewDexData(new_len, new_data, kEvent); } +template <RedefinitionType kType> void Transformer::RetransformClassesDirect( - art::Thread* self, - /*in-out*/std::vector<ArtClassDefinition>* definitions) { + art::Thread* self, + /*in-out*/ std::vector<ArtClassDefinition>* definitions) { + constexpr ArtJvmtiEvent kEvent = kType == RedefinitionType::kNormal + ? ArtJvmtiEvent::kClassFileLoadHookRetransformable + : ArtJvmtiEvent::kStructuralDexFileLoadHook; for (ArtClassDefinition& def : *definitions) { - TransformSingleClassDirect<ArtJvmtiEvent::kClassFileLoadHookRetransformable>( - gEventHandler, self, &def); + TransformSingleClassDirect<kEvent>(gEventHandler, self, &def); } } +template void Transformer::RetransformClassesDirect<RedefinitionType::kNormal>( + art::Thread* self, /*in-out*/std::vector<ArtClassDefinition>* definitions); +template void Transformer::RetransformClassesDirect<RedefinitionType::kStructural>( + art::Thread* self, /*in-out*/std::vector<ArtClassDefinition>* definitions); + jvmtiError Transformer::RetransformClasses(jvmtiEnv* env, jint class_count, const jclass* classes) { - if (env == nullptr) { - JVMTI_LOG(WARNING, env) << "FAILURE TO RETRANSFORM env was null!"; - return ERR(INVALID_ENVIRONMENT); - } else if (class_count < 0) { + if (class_count < 0) { JVMTI_LOG(WARNING, env) << "FAILURE TO RETRANSFORM class_count was less then 0"; return ERR(ILLEGAL_ARGUMENT); } else if (class_count == 0) { @@ -317,7 +326,7 @@ jvmtiError Transformer::RetransformClasses(jvmtiEnv* env, std::vector<ArtClassDefinition> definitions; jvmtiError res = OK; for (jint i = 0; i < class_count; i++) { - res = Redefiner::GetClassRedefinitionError(classes[i], &error_msg); + res = Redefiner::GetClassRedefinitionError<RedefinitionType::kNormal>(classes[i], &error_msg); if (res != OK) { JVMTI_LOG(WARNING, env) << "FAILURE TO RETRANSFORM " << error_msg; return res; @@ -330,13 +339,16 @@ jvmtiError Transformer::RetransformClasses(jvmtiEnv* env, } definitions.push_back(std::move(def)); } - RetransformClassesDirect(self, &definitions); - res = Redefiner::RedefineClassesDirect(ArtJvmTiEnv::AsArtJvmTiEnv(env), - runtime, - self, - definitions, - RedefinitionType::kNormal, - &error_msg); + RetransformClassesDirect<RedefinitionType::kStructural>(self, &definitions); + RetransformClassesDirect<RedefinitionType::kNormal>(self, &definitions); + RedefinitionType redef_type = + std::any_of(definitions.cbegin(), + definitions.cend(), + [](const auto& it) { return it.HasStructuralChanges(); }) + ? RedefinitionType::kStructural + : RedefinitionType::kNormal; + res = Redefiner::RedefineClassesDirect( + ArtJvmTiEnv::AsArtJvmTiEnv(env), runtime, self, definitions, redef_type, &error_msg); if (res != OK) { JVMTI_LOG(WARNING, env) << "FAILURE TO RETRANSFORM " << error_msg; } diff --git a/openjdkjvmti/transform.h b/openjdkjvmti/transform.h index 40c7267ad1..a58b50ea10 100644 --- a/openjdkjvmti/transform.h +++ b/openjdkjvmti/transform.h @@ -39,6 +39,7 @@ #include "art_jvmti.h" #include "ti_class_definition.h" +#include "ti_redefine.h" namespace openjdkjvmti { @@ -56,6 +57,7 @@ class Transformer { art::Thread* self, /*in-out*/ArtClassDefinition* def); + template<RedefinitionType kType> static void RetransformClassesDirect( art::Thread* self, /*in-out*/std::vector<ArtClassDefinition>* definitions); diff --git a/test/1988-multi-structural-redefine/expected.txt b/test/1988-multi-structural-redefine/expected.txt new file mode 100644 index 0000000000..00aea886c4 --- /dev/null +++ b/test/1988-multi-structural-redefine/expected.txt @@ -0,0 +1,5 @@ +hello - Transform 1 +hello - Transform 2 +Redefining both class art.Test1988$Transform1 and class art.Test1988$Transform2 to use each other. +Transform1 says hi and Transform2 says bye! +Transform2 says hi and Transform1 says bye! diff --git a/test/1988-multi-structural-redefine/info.txt b/test/1988-multi-structural-redefine/info.txt new file mode 100644 index 0000000000..875a5f6ec1 --- /dev/null +++ b/test/1988-multi-structural-redefine/info.txt @@ -0,0 +1 @@ +Tests basic functions in the jvmti plugin. diff --git a/test/1988-multi-structural-redefine/run b/test/1988-multi-structural-redefine/run new file mode 100755 index 0000000000..a36de16ea6 --- /dev/null +++ b/test/1988-multi-structural-redefine/run @@ -0,0 +1,18 @@ +#!/bin/bash +# +# Copyright 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. + +# Ask for stack traces to be dumped to a file rather than to stdout. +./default-run "$@" --jvmti --android-runtime-option -Xopaque-jni-ids:true diff --git a/test/1988-multi-structural-redefine/src/Main.java b/test/1988-multi-structural-redefine/src/Main.java new file mode 100644 index 0000000000..7e95671cab --- /dev/null +++ b/test/1988-multi-structural-redefine/src/Main.java @@ -0,0 +1,21 @@ +/* + * 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. + */ + +public class Main { + public static void main(String[] args) throws Exception { + art.Test1988.run(); + } +} diff --git a/test/1988-multi-structural-redefine/src/art/Redefinition.java b/test/1988-multi-structural-redefine/src/art/Redefinition.java new file mode 120000 index 0000000000..81eaf31bbb --- /dev/null +++ b/test/1988-multi-structural-redefine/src/art/Redefinition.java @@ -0,0 +1 @@ +../../../jvmti-common/Redefinition.java
\ No newline at end of file diff --git a/test/1988-multi-structural-redefine/src/art/Test1988.java b/test/1988-multi-structural-redefine/src/art/Test1988.java new file mode 100644 index 0000000000..6dab4daa20 --- /dev/null +++ b/test/1988-multi-structural-redefine/src/art/Test1988.java @@ -0,0 +1,126 @@ +/* + * 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. + */ + +package art; + +import java.util.Base64; + +public class Test1988 { + static class Transform1 { + public static void sayHi() { + System.out.println("hello - Transform 1"); + } + } + static class Transform2 { + public static void sayHi() { + System.out.println("hello - Transform 2"); + } + } + + /** Base64 encoded dex file for + * + * static class Trasnform1 { + * public static void sayHi() { + * System.out.println("Transform1 says hi and " + Transform2.getBye()); + * } + * public static String getBye() { + * return "Transform1 says bye!"; + * } + * } + */ + public static final byte[] T1_BYTES = Base64.getDecoder().decode( + "ZGV4CjAzNQAU4pPI4BKgrMtz7s1Ogc8in1PQhazaRWBcBQAAcAAAAHhWNBIAAAAAAAAAAJgEAAAd" + + "AAAAcAAAAAsAAADkAAAABAAAABABAAABAAAAQAEAAAkAAABIAQAAAQAAAJABAACsAwAAsAEAAD4C" + + "AABGAgAASQIAAE0CAABoAgAAgwIAAJMCAAC3AgAA1wIAAO4CAAACAwAAFgMAADEDAABFAwAAVAMA" + + "AGADAAB2AwAAjwMAAJIDAACWAwAAowMAAKsDAACzAwAAuQMAAL4DAADHAwAAzgMAANgDAADfAwAA" + + "AwAAAAQAAAAFAAAABgAAAAcAAAAIAAAACQAAAAoAAAALAAAADAAAABEAAAABAAAABwAAAAAAAAAC" + + "AAAACAAAADgCAAARAAAACgAAAAAAAAASAAAACgAAADgCAAAJAAUAFwAAAAAAAgAAAAAAAAAAABUA" + + "AAAAAAIAGQAAAAEAAAAVAAAABQADABgAAAAGAAIAAAAAAAgAAgAAAAAACAABABQAAAAIAAAAGgAA" + + "AAAAAAAAAAAABgAAAAAAAAANAAAAiAQAAGUEAAAAAAAAAQAAAAAAAAAqAgAAAwAAABoADwARAAAA" + + "AQABAAEAAAAmAgAABAAAAHAQBQAAAA4ABAAAAAIAAAAuAgAAGwAAAGIAAABxAAMAAAAMASICCABw" + + "EAYAAgAaAxAAbiAHADIAbiAHABIAbhAIAAIADAFuIAQAEAAOAAYADgALAA4ACAAOARoPAAAAAAEA" + + "AAAHAAY8aW5pdD4AAUwAAkxMABlMYXJ0L1Rlc3QxOTg4JFRyYW5zZm9ybTE7ABlMYXJ0L1Rlc3Qx" + + "OTg4JFRyYW5zZm9ybTI7AA5MYXJ0L1Rlc3QxOTg4OwAiTGRhbHZpay9hbm5vdGF0aW9uL0VuY2xv" + + "c2luZ0NsYXNzOwAeTGRhbHZpay9hbm5vdGF0aW9uL0lubmVyQ2xhc3M7ABVMamF2YS9pby9Qcmlu" + + "dFN0cmVhbTsAEkxqYXZhL2xhbmcvT2JqZWN0OwASTGphdmEvbGFuZy9TdHJpbmc7ABlMamF2YS9s" + + "YW5nL1N0cmluZ0J1aWxkZXI7ABJMamF2YS9sYW5nL1N5c3RlbTsADVRlc3QxOTg4LmphdmEAClRy" + + "YW5zZm9ybTEAFFRyYW5zZm9ybTEgc2F5cyBieWUhABdUcmFuc2Zvcm0xIHNheXMgaGkgYW5kIAAB" + + "VgACVkwAC2FjY2Vzc0ZsYWdzAAZhcHBlbmQABmdldEJ5ZQAEbmFtZQADb3V0AAdwcmludGxuAAVz" + + "YXlIaQAIdG9TdHJpbmcABXZhbHVlAHV+fkQ4eyJjb21waWxhdGlvbi1tb2RlIjoiZGVidWciLCJt" + + "aW4tYXBpIjoxLCJzaGEtMSI6IjY4NjQ4NTU3NTM0MDJiYmFjODk2Nzc2YjAzN2RlYmJjOTM4YzQ5" + + "NTMiLCJ2ZXJzaW9uIjoiMS43LjYtZGV2In0AAgMBGxgCAgQCEwQIFhcOAAADAACAgATIAwEJsAMB" + + "CeADAAAAAAACAAAAVgQAAFwEAAB8BAAAAAAAAAAAAAAAAAAAEAAAAAAAAAABAAAAAAAAAAEAAAAd" + + "AAAAcAAAAAIAAAALAAAA5AAAAAMAAAAEAAAAEAEAAAQAAAABAAAAQAEAAAUAAAAJAAAASAEAAAYA" + + "AAABAAAAkAEAAAEgAAADAAAAsAEAAAMgAAADAAAAJgIAAAEQAAABAAAAOAIAAAIgAAAdAAAAPgIA" + + "AAQgAAACAAAAVgQAAAAgAAABAAAAZQQAAAMQAAACAAAAeAQAAAYgAAABAAAAiAQAAAAQAAABAAAA" + + "mAQAAA=="); + + + /** Base64 encoded dex file for + * + * static class Trasnform2 { + * public static void sayHi() { + * System.out.println("Transform2 says hi and " + Transform1.getBye()); + * } + * public static String getBye() { + * return "Transform2 says bye!"; + * } + * } + */ + public static final byte[] T2_BYTES = Base64.getDecoder().decode( + "ZGV4CjAzNQD94cwR+R7Yw7VMom5CwuQd5mZlsV2xrVFcBQAAcAAAAHhWNBIAAAAAAAAAAJgEAAAd" + + "AAAAcAAAAAsAAADkAAAABAAAABABAAABAAAAQAEAAAkAAABIAQAAAQAAAJABAACsAwAAsAEAAD4C" + + "AABGAgAASQIAAE0CAABoAgAAgwIAAJMCAAC3AgAA1wIAAO4CAAACAwAAFgMAADEDAABFAwAAVAMA" + + "AGADAAB2AwAAjwMAAJIDAACWAwAAowMAAKsDAACzAwAAuQMAAL4DAADHAwAAzgMAANgDAADfAwAA" + + "AwAAAAQAAAAFAAAABgAAAAcAAAAIAAAACQAAAAoAAAALAAAADAAAABEAAAABAAAABwAAAAAAAAAC" + + "AAAACAAAADgCAAARAAAACgAAAAAAAAASAAAACgAAADgCAAAJAAUAFwAAAAAAAAAVAAAAAQACAAAA" + + "AAABAAAAFQAAAAEAAgAZAAAABQADABgAAAAGAAIAAAAAAAgAAgAAAAAACAABABQAAAAIAAAAGgAA" + + "AAEAAAAAAAAABgAAAAAAAAANAAAAiAQAAGUEAAAAAAAAAQAAAAAAAAAqAgAAAwAAABoADwARAAAA" + + "AQABAAEAAAAmAgAABAAAAHAQBQAAAA4ABAAAAAIAAAAuAgAAGwAAAGIAAABxAAAAAAAMASICCABw" + + "EAYAAgAaAxAAbiAHADIAbiAHABIAbhAIAAIADAFuIAQAEAAOAA4ADgATAA4AEAAOARoPAAAAAAEA" + + "AAAHAAY8aW5pdD4AAUwAAkxMABlMYXJ0L1Rlc3QxOTg4JFRyYW5zZm9ybTE7ABlMYXJ0L1Rlc3Qx" + + "OTg4JFRyYW5zZm9ybTI7AA5MYXJ0L1Rlc3QxOTg4OwAiTGRhbHZpay9hbm5vdGF0aW9uL0VuY2xv" + + "c2luZ0NsYXNzOwAeTGRhbHZpay9hbm5vdGF0aW9uL0lubmVyQ2xhc3M7ABVMamF2YS9pby9Qcmlu" + + "dFN0cmVhbTsAEkxqYXZhL2xhbmcvT2JqZWN0OwASTGphdmEvbGFuZy9TdHJpbmc7ABlMamF2YS9s" + + "YW5nL1N0cmluZ0J1aWxkZXI7ABJMamF2YS9sYW5nL1N5c3RlbTsADVRlc3QxOTg4LmphdmEAClRy" + + "YW5zZm9ybTIAFFRyYW5zZm9ybTIgc2F5cyBieWUhABdUcmFuc2Zvcm0yIHNheXMgaGkgYW5kIAAB" + + "VgACVkwAC2FjY2Vzc0ZsYWdzAAZhcHBlbmQABmdldEJ5ZQAEbmFtZQADb3V0AAdwcmludGxuAAVz" + + "YXlIaQAIdG9TdHJpbmcABXZhbHVlAHV+fkQ4eyJjb21waWxhdGlvbi1tb2RlIjoiZGVidWciLCJt" + + "aW4tYXBpIjoxLCJzaGEtMSI6IjY4NjQ4NTU3NTM0MDJiYmFjODk2Nzc2YjAzN2RlYmJjOTM4YzQ5" + + "NTMiLCJ2ZXJzaW9uIjoiMS43LjYtZGV2In0AAgMBGxgCAgQCEwQIFhcOAAADAAGAgATIAwEJsAMB" + + "CeADAAAAAAACAAAAVgQAAFwEAAB8BAAAAAAAAAAAAAAAAAAAEAAAAAAAAAABAAAAAAAAAAEAAAAd" + + "AAAAcAAAAAIAAAALAAAA5AAAAAMAAAAEAAAAEAEAAAQAAAABAAAAQAEAAAUAAAAJAAAASAEAAAYA" + + "AAABAAAAkAEAAAEgAAADAAAAsAEAAAMgAAADAAAAJgIAAAEQAAABAAAAOAIAAAIgAAAdAAAAPgIA" + + "AAQgAAACAAAAVgQAAAAgAAABAAAAZQQAAAMQAAACAAAAeAQAAAYgAAABAAAAiAQAAAAQAAABAAAA" + + "mAQAAA=="); + + + public static void run() { + doTest(); + } + + public static void doTest() { + Transform1.sayHi(); + Transform2.sayHi(); + System.out.println( + "Redefining both " + Transform1.class + " and " + Transform2.class + " to use each other."); + Redefinition.doMultiStructuralClassRedefinition( + new Redefinition.DexOnlyClassDefinition(Transform1.class, T1_BYTES), + new Redefinition.DexOnlyClassDefinition(Transform2.class, T2_BYTES)); + Transform1.sayHi(); + Transform2.sayHi(); + } +} diff --git a/test/1991-hello-structural-retransform/expected.txt b/test/1991-hello-structural-retransform/expected.txt new file mode 100644 index 0000000000..7478dda33a --- /dev/null +++ b/test/1991-hello-structural-retransform/expected.txt @@ -0,0 +1,2 @@ +hello +I say hello and you say goodbye! diff --git a/test/1991-hello-structural-retransform/info.txt b/test/1991-hello-structural-retransform/info.txt new file mode 100644 index 0000000000..875a5f6ec1 --- /dev/null +++ b/test/1991-hello-structural-retransform/info.txt @@ -0,0 +1 @@ +Tests basic functions in the jvmti plugin. diff --git a/test/1991-hello-structural-retransform/run b/test/1991-hello-structural-retransform/run new file mode 100755 index 0000000000..03e41a58e7 --- /dev/null +++ b/test/1991-hello-structural-retransform/run @@ -0,0 +1,17 @@ +#!/bin/bash +# +# Copyright 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. + +./default-run "$@" --jvmti --runtime-option -Xopaque-jni-ids:true diff --git a/test/1991-hello-structural-retransform/src/Main.java b/test/1991-hello-structural-retransform/src/Main.java new file mode 100644 index 0000000000..531ca4afaf --- /dev/null +++ b/test/1991-hello-structural-retransform/src/Main.java @@ -0,0 +1,21 @@ +/* + * 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. + */ + +public class Main { + public static void main(String[] args) throws Exception { + art.Test1991.run(); + } +} diff --git a/test/1991-hello-structural-retransform/src/art/Redefinition.java b/test/1991-hello-structural-retransform/src/art/Redefinition.java new file mode 120000 index 0000000000..81eaf31bbb --- /dev/null +++ b/test/1991-hello-structural-retransform/src/art/Redefinition.java @@ -0,0 +1 @@ +../../../jvmti-common/Redefinition.java
\ No newline at end of file diff --git a/test/1991-hello-structural-retransform/src/art/Test1991.java b/test/1991-hello-structural-retransform/src/art/Test1991.java new file mode 100644 index 0000000000..6060c202cd --- /dev/null +++ b/test/1991-hello-structural-retransform/src/art/Test1991.java @@ -0,0 +1,79 @@ +/* + * 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. + */ + +package art; + +import java.util.Base64; +public class Test1991 { + + static class Transform { + public static void sayHi() { + System.out.println("hello"); + } + } + + + /** + * base64 encoded class/dex file for + * static class Transform { + * public static void sayHi() { + * System.out.println("I say hello and " + sayGoodbye()); + * } + * public static String sayGoodbye() { + * return "you say goodbye!"; + * } + * } + */ + private static final byte[] DEX_BYTES = Base64.getDecoder().decode( + "ZGV4CjAzNQCi0OGZvVpTRbHGfNbo3bfcu60kPpJayMgoBQAAcAAAAHhWNBIAAAAAAAAAAGQEAAAc" + + "AAAAcAAAAAoAAADgAAAABAAAAAgBAAABAAAAOAEAAAgAAABAAQAAAQAAAIABAACIAwAAoAEAAC4C" + + "AAA2AgAASAIAAEsCAABPAgAAaQIAAHkCAACdAgAAvQIAANQCAADoAgAA/AIAABcDAAArAwAAOgMA" + + "AEUDAABIAwAATAMAAFkDAABhAwAAZwMAAGwDAAB1AwAAgQMAAIgDAACSAwAAmQMAAKsDAAAEAAAA" + + "BQAAAAYAAAAHAAAACAAAAAkAAAAKAAAACwAAAAwAAAAPAAAAAgAAAAYAAAAAAAAAAwAAAAcAAAAo" + + "AgAADwAAAAkAAAAAAAAAEAAAAAkAAAAoAgAACAAEABQAAAAAAAIAAAAAAAAAAAAWAAAAAAACABcA" + + "AAAEAAMAFQAAAAUAAgAAAAAABwACAAAAAAAHAAEAEgAAAAcAAAAYAAAAAAAAAAAAAAAFAAAAAAAA" + + "AA0AAABUBAAAMgQAAAAAAAABAAAAAAAAABoCAAADAAAAGgAaABEAAAABAAEAAQAAABYCAAAEAAAA" + + "cBAEAAAADgAEAAAAAgAAAB4CAAAbAAAAYgAAAHEAAQAAAAwBIgIHAHAQBQACABoDAQBuIAYAMgBu" + + "IAYAEgBuEAcAAgAMAW4gAwAQAA4ABgAOAAsADgAIAA4BGg8AAAAAAQAAAAYABjxpbml0PgAQSSBz" + + "YXkgaGVsbG8gYW5kIAABTAACTEwAGExhcnQvVGVzdDE5OTEkVHJhbnNmb3JtOwAOTGFydC9UZXN0" + + "MTk5MTsAIkxkYWx2aWsvYW5ub3RhdGlvbi9FbmNsb3NpbmdDbGFzczsAHkxkYWx2aWsvYW5ub3Rh" + + "dGlvbi9Jbm5lckNsYXNzOwAVTGphdmEvaW8vUHJpbnRTdHJlYW07ABJMamF2YS9sYW5nL09iamVj" + + "dDsAEkxqYXZhL2xhbmcvU3RyaW5nOwAZTGphdmEvbGFuZy9TdHJpbmdCdWlsZGVyOwASTGphdmEv" + + "bGFuZy9TeXN0ZW07AA1UZXN0MTk5MS5qYXZhAAlUcmFuc2Zvcm0AAVYAAlZMAAthY2Nlc3NGbGFn" + + "cwAGYXBwZW5kAARuYW1lAANvdXQAB3ByaW50bG4ACnNheUdvb2RieWUABXNheUhpAAh0b1N0cmlu" + + "ZwAFdmFsdWUAEHlvdSBzYXkgZ29vZGJ5ZSEAdn5+RDh7ImNvbXBpbGF0aW9uLW1vZGUiOiJkZWJ1" + + "ZyIsIm1pbi1hcGkiOjEsInNoYS0xIjoiNjBkYTRkNjdiMzgxYzQyNDY3NzU3YzQ5ZmI2ZTU1NzU2" + + "ZDg4YTJmMyIsInZlcnNpb24iOiIxLjcuMTItZGV2In0AAgIBGRgBAgMCEQQIExcOAAADAACAgAS4" + + "AwEJoAMBCdADAAAAAAIAAAAjBAAAKQQAAEgEAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAEAAAAAAAAA" + + "AQAAABwAAABwAAAAAgAAAAoAAADgAAAAAwAAAAQAAAAIAQAABAAAAAEAAAA4AQAABQAAAAgAAABA" + + "AQAABgAAAAEAAACAAQAAASAAAAMAAACgAQAAAyAAAAMAAAAWAgAAARAAAAEAAAAoAgAAAiAAABwA" + + "AAAuAgAABCAAAAIAAAAjBAAAACAAAAEAAAAyBAAAAxAAAAIAAABEBAAABiAAAAEAAABUBAAAABAA" + + "AAEAAABkBAAA"); + + + public static void run() { + Redefinition.setTestConfiguration(Redefinition.Config.STRUCTURAL_TRANSFORM); + doTest(); + } + + public static void doTest() { + Transform.sayHi(); + Redefinition.addCommonTransformationResult("art/Test1991$Transform", new byte[0], DEX_BYTES); + Redefinition.enableCommonRetransformation(true); + Redefinition.doCommonClassRetransformation(Transform.class); + Transform.sayHi(); + } +} diff --git a/test/1993-fallback-non-structural/expected.txt b/test/1993-fallback-non-structural/expected.txt new file mode 100644 index 0000000000..7e2cdf4ee5 --- /dev/null +++ b/test/1993-fallback-non-structural/expected.txt @@ -0,0 +1,3 @@ +Can structurally Redefine: false +hello +Goodbye diff --git a/test/1993-fallback-non-structural/info.txt b/test/1993-fallback-non-structural/info.txt new file mode 100644 index 0000000000..3b558e1cd1 --- /dev/null +++ b/test/1993-fallback-non-structural/info.txt @@ -0,0 +1,4 @@ +Tests basic functions in the jvmti plugin. + +Tests that using the structural redefinition functions will fall back to non-structural +redefinition when possible. diff --git a/test/1993-fallback-non-structural/run b/test/1993-fallback-non-structural/run new file mode 100755 index 0000000000..03e41a58e7 --- /dev/null +++ b/test/1993-fallback-non-structural/run @@ -0,0 +1,17 @@ +#!/bin/bash +# +# Copyright 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. + +./default-run "$@" --jvmti --runtime-option -Xopaque-jni-ids:true diff --git a/test/1993-fallback-non-structural/src/Main.java b/test/1993-fallback-non-structural/src/Main.java new file mode 100644 index 0000000000..61e060c773 --- /dev/null +++ b/test/1993-fallback-non-structural/src/Main.java @@ -0,0 +1,21 @@ +/* + * 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. + */ + +public class Main { + public static void main(String[] args) throws Exception { + art.Test1993.run(); + } +} diff --git a/test/1993-fallback-non-structural/src/art/Redefinition.java b/test/1993-fallback-non-structural/src/art/Redefinition.java new file mode 120000 index 0000000000..81eaf31bbb --- /dev/null +++ b/test/1993-fallback-non-structural/src/art/Redefinition.java @@ -0,0 +1 @@ +../../../jvmti-common/Redefinition.java
\ No newline at end of file diff --git a/test/1993-fallback-non-structural/src/art/Test1993.java b/test/1993-fallback-non-structural/src/art/Test1993.java new file mode 100644 index 0000000000..f4420993f7 --- /dev/null +++ b/test/1993-fallback-non-structural/src/art/Test1993.java @@ -0,0 +1,77 @@ +/* + * 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. + */ + +package art; + +import java.util.Base64; +public class Test1993 { + + static class Transform { + public void sayHi() { + // Use lower 'h' to make sure the string will have a different string id + // than the transformation (the transformation code is the same except + // the actual printed String, which was making the test inacurately passing + // in JIT mode when loading the string from the dex cache, as the string ids + // of the two different strings were the same). + // We know the string ids will be different because lexicographically: + // "Goodbye" < "LTransform;" < "hello". + System.out.println("hello"); + } + } + + /** + * base64 encoded class/dex file for + * class Transform { + * public void sayHi() { + * System.out.println("Goodbye"); + * } + * } + */ + private static final byte[] DEX_BYTES = Base64.getDecoder().decode( + "ZGV4CjAzNQDxrdbiBcsn0r58mdtcdyDxVUxwbWfShNQwBAAAcAAAAHhWNBIAAAAAAAAAAGwDAAAV" + + "AAAAcAAAAAkAAADEAAAAAgAAAOgAAAABAAAAAAEAAAQAAAAIAQAAAQAAACgBAADoAgAASAEAAJIB" + + "AACaAQAAowEAAL0BAADNAQAA8QEAABECAAAoAgAAPAIAAFACAABkAgAAcwIAAH4CAACBAgAAhQIA" + + "AJICAACYAgAAnQIAAKYCAACtAgAAtAIAAAIAAAADAAAABAAAAAUAAAAGAAAABwAAAAgAAAAJAAAA" + + "DAAAAAwAAAAIAAAAAAAAAA0AAAAIAAAAjAEAAAcABAAQAAAAAAAAAAAAAAAAAAAAEgAAAAQAAQAR" + + "AAAABQAAAAAAAAAAAAAAAAAAAAUAAAAAAAAACgAAAFwDAAA7AwAAAAAAAAEAAQABAAAAgAEAAAQA" + + "AABwEAMAAAAOAAMAAQACAAAAhAEAAAgAAABiAAAAGgEBAG4gAgAQAA4AAwAOAAUADngAAAAAAQAA" + + "AAYABjxpbml0PgAHR29vZGJ5ZQAYTGFydC9UZXN0MTk5MyRUcmFuc2Zvcm07AA5MYXJ0L1Rlc3Qx" + + "OTkzOwAiTGRhbHZpay9hbm5vdGF0aW9uL0VuY2xvc2luZ0NsYXNzOwAeTGRhbHZpay9hbm5vdGF0" + + "aW9uL0lubmVyQ2xhc3M7ABVMamF2YS9pby9QcmludFN0cmVhbTsAEkxqYXZhL2xhbmcvT2JqZWN0" + + "OwASTGphdmEvbGFuZy9TdHJpbmc7ABJMamF2YS9sYW5nL1N5c3RlbTsADVRlc3QxOTkzLmphdmEA" + + "CVRyYW5zZm9ybQABVgACVkwAC2FjY2Vzc0ZsYWdzAARuYW1lAANvdXQAB3ByaW50bG4ABXNheUhp" + + "AAV2YWx1ZQB2fn5EOHsiY29tcGlsYXRpb24tbW9kZSI6ImRlYnVnIiwibWluLWFwaSI6MSwic2hh" + + "LTEiOiJjZDkwMDIzOTMwZDk3M2Y1NzcxMWYxZDRmZGFhZDdhM2U0NzE0NjM3IiwidmVyc2lvbiI6" + + "IjEuNy4xNC1kZXYifQACAgETGAECAwIOBAgPFwsAAAEBAICABMgCAQHgAgAAAAAAAAACAAAALAMA" + + "ADIDAABQAwAAAAAAAAAAAAAAAAAAEAAAAAAAAAABAAAAAAAAAAEAAAAVAAAAcAAAAAIAAAAJAAAA" + + "xAAAAAMAAAACAAAA6AAAAAQAAAABAAAAAAEAAAUAAAAEAAAACAEAAAYAAAABAAAAKAEAAAEgAAAC" + + "AAAASAEAAAMgAAACAAAAgAEAAAEQAAABAAAAjAEAAAIgAAAVAAAAkgEAAAQgAAACAAAALAMAAAAg" + + "AAABAAAAOwMAAAMQAAACAAAATAMAAAYgAAABAAAAXAMAAAAQAAABAAAAbAMAAA=="); + + public static void run() { + Redefinition.setTestConfiguration(Redefinition.Config.COMMON_REDEFINE); + doTest(new Transform()); + } + + public static void doTest(Transform t) { + // TODO Remove this once the class is structurally modifiable. + System.out.println("Can structurally Redefine: " + + Redefinition.isStructurallyModifiable(Transform.class)); + t.sayHi(); + Redefinition.doCommonStructuralClassRedefinition(Transform.class, DEX_BYTES); + t.sayHi(); + } +} diff --git a/test/jvmti-common/Redefinition.java b/test/jvmti-common/Redefinition.java index 2ebce17686..3402fa12b5 100644 --- a/test/jvmti-common/Redefinition.java +++ b/test/jvmti-common/Redefinition.java @@ -19,7 +19,7 @@ package art; import java.util.ArrayList; // Common Redefinition functions. Placed here for use by CTS public class Redefinition { - public static final class CommonClassDefinition { + public static class CommonClassDefinition { public final Class<?> target; public final byte[] class_file_bytes; public final byte[] dex_file_bytes; @@ -31,12 +31,19 @@ public class Redefinition { } } + public static class DexOnlyClassDefinition extends CommonClassDefinition { + public DexOnlyClassDefinition(Class<?> target, byte[] dex_file_bytes) { + super(target, new byte[0], dex_file_bytes); + } + } + // A set of possible test configurations. Test should set this if they need to. // This must be kept in sync with the defines in ti-agent/common_helper.cc public static enum Config { COMMON_REDEFINE(0), COMMON_RETRANSFORM(1), - COMMON_TRANSFORM(2); + COMMON_TRANSFORM(2), + STRUCTURAL_TRANSFORM(3); private final int val; private Config(int val) { @@ -90,5 +97,18 @@ public class Redefinition { byte[] dex_bytes); public static native void doCommonStructuralClassRedefinition(Class<?> target, byte[] dex_file); + public static void doMultiStructuralClassRedefinition(CommonClassDefinition... defs) { + ArrayList<Class<?>> classes = new ArrayList<>(); + ArrayList<byte[]> dex_files = new ArrayList<>(); + + for (CommonClassDefinition d : defs) { + classes.add(d.target); + dex_files.add(d.dex_file_bytes); + } + doCommonMultiStructuralClassRedefinition(classes.toArray(new Class<?>[0]), + dex_files.toArray(new byte[0][])); + } + public static native void doCommonMultiStructuralClassRedefinition(Class<?>[] targets, + byte[][] dexfiles); public static native boolean isStructurallyModifiable(Class<?> target); } diff --git a/test/knownfailures.json b/test/knownfailures.json index 2e34943401..049ff0dcbb 100644 --- a/test/knownfailures.json +++ b/test/knownfailures.json @@ -1140,9 +1140,12 @@ "1985-structural-redefine-stack-scope", "1986-structural-redefine-multi-thread-stack-scope", "1987-structural-redefine-recursive-stack-scope", + "1988-multi-structural-redefine", "1989-transform-bad-monitor", "1990-structural-bad-verify", - "1992-retransform-no-such-field" + "1991-hello-structural-retransform", + "1992-retransform-no-such-field", + "1993-fallback-non-structural" ], "variant": "jvm", "description": ["Doesn't run on RI."] diff --git a/test/ti-agent/jvmti_helper.cc b/test/ti-agent/jvmti_helper.cc index ae1a8d34f3..22bc64ac01 100644 --- a/test/ti-agent/jvmti_helper.cc +++ b/test/ti-agent/jvmti_helper.cc @@ -238,6 +238,26 @@ void DeallocParams(jvmtiEnv* env, jvmtiParamInfo* params, jint n_params) { } } +jint GetExtensionEventId(jvmtiEnv* jvmti, const std::string_view& name) { + jint n_ext = 0; + jint res = -1; + bool found_res = false; + jvmtiExtensionEventInfo* infos = nullptr; + CHECK_EQ(jvmti->GetExtensionEvents(&n_ext, &infos), JVMTI_ERROR_NONE); + for (jint i = 0; i < n_ext; i++) { + const jvmtiExtensionEventInfo& info = infos[i]; + if (name == info.id) { + res = info.extension_event_index; + found_res = true; + } + DeallocParams(jvmti, info.params, info.param_count); + Dealloc(jvmti, info.short_description, info.id, info.params); + } + Dealloc(jvmti, infos); + CHECK(found_res); + return res; +} + void* GetExtensionFunctionVoid(JNIEnv* env, jvmtiEnv* jvmti, const std::string_view& name) { jint n_ext = 0; void* res = nullptr; diff --git a/test/ti-agent/jvmti_helper.h b/test/ti-agent/jvmti_helper.h index a3b95353c7..74d594ff04 100644 --- a/test/ti-agent/jvmti_helper.h +++ b/test/ti-agent/jvmti_helper.h @@ -94,6 +94,8 @@ template<typename T> T GetExtensionFunction(JNIEnv* env, jvmtiEnv* jvmti, const return reinterpret_cast<T>(GetExtensionFunctionVoid(env, jvmti, name)); } +jint GetExtensionEventId(jvmtiEnv* jvmti, const std::string_view& name); + } // namespace art #endif // ART_TEST_TI_AGENT_JVMTI_HELPER_H_ diff --git a/test/ti-agent/redefinition_helper.cc b/test/ti-agent/redefinition_helper.cc index 9d9f13fa39..0baa9fe547 100644 --- a/test/ti-agent/redefinition_helper.cc +++ b/test/ti-agent/redefinition_helper.cc @@ -31,8 +31,13 @@ namespace art { +enum class RedefineType { + kNormal, + kStructural, +}; + static void SetupCommonRedefine(); -static void SetupCommonRetransform(); +static void SetupCommonRetransform(RedefineType type); static void SetupCommonTransform(); template <bool is_redefine> static void throwCommonRedefinitionError(jvmtiEnv* jvmti, @@ -68,6 +73,7 @@ static void throwCommonRedefinitionError(jvmtiEnv* jvmti, #define CONFIGURATION_COMMON_REDEFINE 0 #define CONFIGURATION_COMMON_RETRANSFORM 1 #define CONFIGURATION_COMMON_TRANSFORM 2 +#define CONFIGURATION_STRUCTURAL_TRANSFORM 3 extern "C" JNIEXPORT void JNICALL Java_art_Redefinition_nativeSetTestConfiguration(JNIEnv*, jclass, @@ -78,21 +84,54 @@ extern "C" JNIEXPORT void JNICALL Java_art_Redefinition_nativeSetTestConfigurati return; } case CONFIGURATION_COMMON_RETRANSFORM: { - SetupCommonRetransform(); + SetupCommonRetransform(RedefineType::kNormal); return; } case CONFIGURATION_COMMON_TRANSFORM: { SetupCommonTransform(); return; } + case CONFIGURATION_STRUCTURAL_TRANSFORM: { + SetupCommonRetransform(RedefineType::kStructural); + return; + } default: { LOG(FATAL) << "Unknown test configuration: " << type; } } } +template<RedefineType kType> +static bool SupportsAndIsJVM() { + if constexpr (kType == RedefineType::kStructural) { + return false; + } else { + return IsJVM(); + } +} + + namespace common_redefine { +template <RedefineType kType> +static jvmtiError CallRedefineEntrypoint(JNIEnv* env, + jvmtiEnv* jvmti, + jint num_defs, + const jvmtiClassDefinition* defs) { + decltype(jvmti->functions->RedefineClasses) entrypoint = nullptr; + if constexpr (kType == RedefineType::kNormal) { + entrypoint = jvmti->functions->RedefineClasses; + } else { + entrypoint = GetExtensionFunction<decltype(entrypoint)>( + env, jvmti_env, "com.android.art.class.structurally_redefine_classes"); + } + if (entrypoint == nullptr) { + LOG(INFO) << "Could not find entrypoint!"; + return JVMTI_ERROR_NOT_AVAILABLE; + } + return entrypoint(jvmti, num_defs, defs); +} + static void throwRedefinitionError(jvmtiEnv* jvmti, JNIEnv* env, jint num_targets, @@ -101,6 +140,7 @@ static void throwRedefinitionError(jvmtiEnv* jvmti, return throwCommonRedefinitionError<true>(jvmti, env, num_targets, target, res); } +template<RedefineType kType> static void DoMultiClassRedefine(jvmtiEnv* jvmti_env, JNIEnv* env, jint num_redefines, @@ -109,24 +149,25 @@ static void DoMultiClassRedefine(jvmtiEnv* jvmti_env, 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]; + jbyteArray desired_array = SupportsAndIsJVM<kType>() ? 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()); + jvmtiError res = CallRedefineEntrypoint<kType>(env, jvmti_env, num_redefines, defs.data()); if (res != JVMTI_ERROR_NONE) { throwRedefinitionError(jvmti_env, env, num_redefines, targets, res); } } +template<RedefineType kType> 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); + return DoMultiClassRedefine<kType>(jvmti_env, env, 1, &target, &class_file_bytes, &dex_file_bytes); } extern "C" JNIEXPORT jboolean JNICALL @@ -145,25 +186,44 @@ Java_art_Redefinition_isStructurallyModifiable(JNIEnv* env, jclass, jclass targe extern "C" JNIEXPORT void JNICALL Java_art_Redefinition_doCommonStructuralClassRedefinition( JNIEnv* env, jclass, jclass target, jbyteArray dex_file_bytes) { - using ArtStructurallyRedefineClassDirect = - jvmtiError (*)(jvmtiEnv * env, jclass k, jbyte* data, jint len); - ArtStructurallyRedefineClassDirect redef = - GetExtensionFunction<ArtStructurallyRedefineClassDirect>( - env, jvmti_env, "com.android.art.UNSAFE.class.structurally_redefine_class_direct"); - if (redef == nullptr || env->ExceptionCheck()) { - return; - } - jint len = env->GetArrayLength(dex_file_bytes); - std::vector<jbyte> v(len, 0); - env->GetByteArrayRegion(dex_file_bytes, 0, len, v.data()); - JvmtiErrorToException(env, jvmti_env, redef(jvmti_env, target, v.data(), len)); + DoClassRedefine<RedefineType::kStructural>(jvmti_env, env, target, nullptr, 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); + DoClassRedefine<RedefineType::kNormal>(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_doCommonMultiStructuralClassRedefinition( + JNIEnv* env, + jclass, + jobjectArray targets, + 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(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(nullptr); + } + return DoMultiClassRedefine<RedefineType::kStructural>(jvmti_env, + env, + len, + classes.data(), + class_files.data(), + dex_files.data()); } // Magic JNI export that classes can use for redefining classes. @@ -189,12 +249,12 @@ extern "C" JNIEXPORT void JNICALL Java_art_Redefinition_doCommonMultiClassRedefi 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()); + return DoMultiClassRedefine<RedefineType::kNormal>(jvmti_env, + env, + len, + classes.data(), + class_files.data(), + dex_files.data()); } // Get all capabilities except those related to retransformation. @@ -380,7 +440,7 @@ jint OnLoad(JavaVM* vm, printf("Unable to get jvmti env!\n"); return 1; } - SetupCommonRetransform(); + SetupCommonRetransform(RedefineType::kNormal); return 0; } @@ -409,11 +469,20 @@ static void SetupCommonRedefine() { jvmti_env->AddCapabilities(&caps); } -static void SetupCommonRetransform() { +static void SetupCommonRetransform(RedefineType type) { SetStandardCapabilities(jvmti_env); - current_callbacks.ClassFileLoadHook = common_retransform::CommonClassFileLoadHookRetransformable; - jvmtiError res = jvmti_env->SetEventCallbacks(¤t_callbacks, sizeof(current_callbacks)); - CHECK_EQ(res, JVMTI_ERROR_NONE); + if (type == RedefineType::kNormal) { + current_callbacks.ClassFileLoadHook = + common_retransform::CommonClassFileLoadHookRetransformable; + jvmtiError res = jvmti_env->SetEventCallbacks(¤t_callbacks, sizeof(current_callbacks)); + CHECK_EQ(res, JVMTI_ERROR_NONE); + } else { + jvmtiError res = jvmti_env->SetExtensionEventCallback( + GetExtensionEventId(jvmti_env, "com.android.art.class.structural_dex_file_load_hook"), + reinterpret_cast<jvmtiExtensionEvent>( + common_retransform::CommonClassFileLoadHookRetransformable)); + CHECK_EQ(res, JVMTI_ERROR_NONE); + } common_retransform::gTransformations.clear(); } |