diff options
24 files changed, 564 insertions, 41 deletions
diff --git a/runtime/class_linker.cc b/runtime/class_linker.cc index abd35f1712..02b26c6568 100644 --- a/runtime/class_linker.cc +++ b/runtime/class_linker.cc @@ -2650,6 +2650,10 @@ mirror::Class* ClassLinker::DefineClass(Thread* self, dex_class_def, &new_dex_file, &new_class_def); + // Check to see if an exception happened during runtime callbacks. Return if so. + if (self->IsExceptionPending()) { + return nullptr; + } ObjPtr<mirror::DexCache> dex_cache = RegisterDexFile(*new_dex_file, class_loader.Get()); if (dex_cache == nullptr) { self->AssertPendingOOMException(); diff --git a/runtime/mirror/array-inl.h b/runtime/mirror/array-inl.h index a5db0c0a8a..f56226bd98 100644 --- a/runtime/mirror/array-inl.h +++ b/runtime/mirror/array-inl.h @@ -207,6 +207,19 @@ inline void PrimitiveArray<T>::VisitRoots(RootVisitor* visitor) { } template<typename T> +inline PrimitiveArray<T>* PrimitiveArray<T>::AllocateAndFill(Thread* self, + const T* data, + size_t length) { + StackHandleScope<1> hs(self); + Handle<PrimitiveArray<T>> arr(hs.NewHandle(PrimitiveArray<T>::Alloc(self, length))); + if (!arr.IsNull()) { + // Copy it in. Just skip if it's null + memcpy(arr->GetData(), data, sizeof(T) * length); + } + return arr.Get(); +} + +template<typename T> inline PrimitiveArray<T>* PrimitiveArray<T>::Alloc(Thread* self, size_t length) { Array* raw_array = Array::Alloc<true>(self, GetArrayClass(), diff --git a/runtime/mirror/array.h b/runtime/mirror/array.h index 19d300e1f4..16cf30f1e2 100644 --- a/runtime/mirror/array.h +++ b/runtime/mirror/array.h @@ -119,6 +119,10 @@ class MANAGED PrimitiveArray : public Array { static PrimitiveArray<T>* Alloc(Thread* self, size_t length) REQUIRES_SHARED(Locks::mutator_lock_) REQUIRES(!Roles::uninterruptible_); + static PrimitiveArray<T>* AllocateAndFill(Thread* self, const T* data, size_t length) + REQUIRES_SHARED(Locks::mutator_lock_) REQUIRES(!Roles::uninterruptible_); + + const T* GetData() const ALWAYS_INLINE REQUIRES_SHARED(Locks::mutator_lock_) { return reinterpret_cast<const T*>(GetRawData(sizeof(T), 0)); } diff --git a/runtime/openjdkjvmti/art_jvmti.h b/runtime/openjdkjvmti/art_jvmti.h index 256c3a6cec..91353e2788 100644 --- a/runtime/openjdkjvmti/art_jvmti.h +++ b/runtime/openjdkjvmti/art_jvmti.h @@ -114,6 +114,21 @@ static inline JvmtiUniquePtr MakeJvmtiUniquePtr(jvmtiEnv* env, T* mem) { } ALWAYS_INLINE +static inline jvmtiError CopyDataIntoJvmtiBuffer(ArtJvmTiEnv* env, + const unsigned char* source, + jint len, + /*out*/unsigned char** dest) { + jvmtiError res = env->Allocate(len, dest); + if (res != OK) { + return res; + } + memcpy(reinterpret_cast<void*>(*dest), + reinterpret_cast<const void*>(source), + len); + return OK; +} + +ALWAYS_INLINE static inline jvmtiError CopyString(jvmtiEnv* env, const char* src, unsigned char** copy) { size_t len = strlen(src) + 1; unsigned char* buf; diff --git a/runtime/openjdkjvmti/events-inl.h b/runtime/openjdkjvmti/events-inl.h index 655a53adb3..4f5eb0c33f 100644 --- a/runtime/openjdkjvmti/events-inl.h +++ b/runtime/openjdkjvmti/events-inl.h @@ -131,8 +131,8 @@ inline void EventHandler::DispatchClassFileLoadHookEvent(art::Thread* thread, ArtJvmTiEnv* last_env = nullptr; for (ArtJvmTiEnv* env : envs) { if (ShouldDispatch<kEvent>(env, thread)) { - jint new_len; - unsigned char* new_data; + jint new_len = 0; + unsigned char* new_data = nullptr; auto callback = impl::GetCallback<kEvent>(env); callback(env, jnienv, diff --git a/runtime/openjdkjvmti/ti_class.cc b/runtime/openjdkjvmti/ti_class.cc index 6dbab8611b..b6de592142 100644 --- a/runtime/openjdkjvmti/ti_class.cc +++ b/runtime/openjdkjvmti/ti_class.cc @@ -31,6 +31,8 @@ #include "ti_class.h" +#include "android-base/stringprintf.h" + #include <mutex> #include <unordered_set> @@ -38,20 +40,208 @@ #include "base/macros.h" #include "class_table-inl.h" #include "class_linker.h" +#include "common_throws.h" #include "events-inl.h" #include "handle.h" #include "jni_env_ext-inl.h" #include "jni_internal.h" +#include "mirror/array-inl.h" +#include "mirror/class-inl.h" +#include "mirror/class_ext.h" #include "runtime.h" #include "runtime_callbacks.h" #include "ScopedLocalRef.h" #include "scoped_thread_state_change-inl.h" #include "thread-inl.h" #include "thread_list.h" +#include "ti_redefine.h" +#include "utils.h" namespace openjdkjvmti { +using android::base::StringPrintf; + +static std::unique_ptr<const art::DexFile> MakeSingleDexFile(art::Thread* self, + const char* descriptor, + const std::string& orig_location, + jint final_len, + const unsigned char* final_dex_data) + REQUIRES_SHARED(art::Locks::mutator_lock_) { + // Make the mmap + std::string error_msg; + std::unique_ptr<art::MemMap> map(Redefiner::MoveDataToMemMap(orig_location, + final_len, + final_dex_data, + &error_msg)); + if (map.get() == nullptr) { + LOG(WARNING) << "Unable to allocate mmap for redefined dex file! Error was: " << error_msg; + self->ThrowOutOfMemoryError(StringPrintf( + "Unable to allocate dex file for transformation of %s", descriptor).c_str()); + return nullptr; + } + + // Make a dex-file + if (map->Size() < sizeof(art::DexFile::Header)) { + LOG(WARNING) << "Could not read dex file header because dex_data was too short"; + art::ThrowClassFormatError(nullptr, + "Unable to read transformed dex file of %s", + descriptor); + return nullptr; + } + uint32_t checksum = reinterpret_cast<const art::DexFile::Header*>(map->Begin())->checksum_; + std::unique_ptr<const art::DexFile> dex_file(art::DexFile::Open(map->GetName(), + checksum, + std::move(map), + /*verify*/true, + /*verify_checksum*/true, + &error_msg)); + if (dex_file.get() == nullptr) { + LOG(WARNING) << "Unable to load modified dex file for " << descriptor << ": " << error_msg; + art::ThrowClassFormatError(nullptr, + "Unable to read transformed dex file of %s because %s", + descriptor, + error_msg.c_str()); + return nullptr; + } + if (dex_file->NumClassDefs() != 1) { + LOG(WARNING) << "Dex file contains more than 1 class_def. Ignoring."; + // TODO Throw some other sort of error here maybe? + art::ThrowClassFormatError( + nullptr, + "Unable to use transformed dex file of %s because it contained too many classes", + descriptor); + return nullptr; + } + return dex_file; +} + struct ClassCallback : public art::ClassLoadCallback { + void ClassPreDefine(const char* descriptor, + art::Handle<art::mirror::Class> klass, + art::Handle<art::mirror::ClassLoader> class_loader, + const art::DexFile& initial_dex_file, + const art::DexFile::ClassDef& initial_class_def ATTRIBUTE_UNUSED, + /*out*/art::DexFile const** final_dex_file, + /*out*/art::DexFile::ClassDef const** final_class_def) + OVERRIDE REQUIRES_SHARED(art::Locks::mutator_lock_) { + bool is_enabled = + event_handler->IsEventEnabledAnywhere(ArtJvmtiEvent::kClassFileLoadHookRetransformable) || + event_handler->IsEventEnabledAnywhere(ArtJvmtiEvent::kClassFileLoadHookNonRetransformable); + if (!is_enabled) { + return; + } + if (descriptor[0] != 'L') { + // It is a primitive or array. Just return + return; + } + std::string name(art::PrettyDescriptor(descriptor)); + + art::Thread* self = art::Thread::Current(); + art::JNIEnvExt* env = self->GetJniEnv(); + ScopedLocalRef<jobject> loader( + env, class_loader.IsNull() ? nullptr : env->AddLocalReference<jobject>(class_loader.Get())); + // Go back to native. + art::ScopedThreadSuspension sts(self, art::ThreadState::kNative); + // Call all Non-retransformable agents. + jint post_no_redefine_len = 0; + unsigned char* post_no_redefine_dex_data = nullptr; + std::unique_ptr<const unsigned char> post_no_redefine_unique_ptr(nullptr); + event_handler->DispatchEvent<ArtJvmtiEvent::kClassFileLoadHookNonRetransformable>( + self, + static_cast<JNIEnv*>(env), + static_cast<jclass>(nullptr), // The class doesn't really exist yet so send null. + loader.get(), + name.c_str(), + static_cast<jobject>(nullptr), // Android doesn't seem to have protection domains + static_cast<jint>(initial_dex_file.Size()), + static_cast<const unsigned char*>(initial_dex_file.Begin()), + static_cast<jint*>(&post_no_redefine_len), + static_cast<unsigned char**>(&post_no_redefine_dex_data)); + if (post_no_redefine_dex_data == nullptr) { + DCHECK_EQ(post_no_redefine_len, 0); + post_no_redefine_dex_data = const_cast<unsigned char*>(initial_dex_file.Begin()); + post_no_redefine_len = initial_dex_file.Size(); + } else { + post_no_redefine_unique_ptr = std::unique_ptr<const unsigned char>(post_no_redefine_dex_data); + DCHECK_GT(post_no_redefine_len, 0); + } + // Call all retransformable agents. + jint final_len = 0; + unsigned char* final_dex_data = nullptr; + std::unique_ptr<const unsigned char> final_dex_unique_ptr(nullptr); + event_handler->DispatchEvent<ArtJvmtiEvent::kClassFileLoadHookRetransformable>( + self, + static_cast<JNIEnv*>(env), + static_cast<jclass>(nullptr), // The class doesn't really exist yet so send null. + loader.get(), + name.c_str(), + static_cast<jobject>(nullptr), // Android doesn't seem to have protection domains + static_cast<jint>(post_no_redefine_len), + static_cast<const unsigned char*>(post_no_redefine_dex_data), + static_cast<jint*>(&final_len), + static_cast<unsigned char**>(&final_dex_data)); + if (final_dex_data == nullptr) { + DCHECK_EQ(final_len, 0); + final_dex_data = post_no_redefine_dex_data; + final_len = post_no_redefine_len; + } else { + final_dex_unique_ptr = std::unique_ptr<const unsigned char>(final_dex_data); + DCHECK_GT(final_len, 0); + } + + if (final_dex_data != initial_dex_file.Begin()) { + LOG(WARNING) << "Changing class " << descriptor; + art::ScopedObjectAccess soa(self); + art::StackHandleScope<2> hs(self); + // Save the results of all the non-retransformable agents. + // First allocate the ClassExt + art::Handle<art::mirror::ClassExt> ext(hs.NewHandle(klass->EnsureExtDataPresent(self))); + // Make sure we have a ClassExt. This is fine even though we are a temporary since it will + // get copied. + if (ext.IsNull()) { + // We will just return failure if we fail to allocate + LOG(WARNING) << "Could not allocate ext-data for class '" << descriptor << "'. " + << "Aborting transformation since we will be unable to store it."; + self->AssertPendingOOMException(); + return; + } + + // Allocate the byte array to store the dex file bytes in. + art::Handle<art::mirror::ByteArray> arr(hs.NewHandle( + art::mirror::ByteArray::AllocateAndFill( + self, + reinterpret_cast<const signed char*>(post_no_redefine_dex_data), + post_no_redefine_len))); + if (arr.IsNull()) { + LOG(WARNING) << "Unable to allocate byte array for initial dex-file bytes. Aborting " + << "transformation"; + self->AssertPendingOOMException(); + return; + } + + std::unique_ptr<const art::DexFile> dex_file(MakeSingleDexFile(self, + descriptor, + initial_dex_file.GetLocation(), + final_len, + final_dex_data)); + if (dex_file.get() == nullptr) { + return; + } + + // TODO Check Redefined dex file for invariants. + LOG(WARNING) << "Dex file created by class-definition time transformation of " + << descriptor << " is not checked for all retransformation invariants."; + // TODO Put it in classpath + LOG(WARNING) << "Dex file created for class-definition time transformation of " + << descriptor << " was not added to any classpaths!"; + // Actually set the ClassExt's original bytes once we have actually succeeded. + ext->SetOriginalDexFileBytes(arr.Get()); + // Set the return values + *final_class_def = &dex_file->GetClassDef(0); + *final_dex_file = dex_file.release(); + } + } + void ClassLoad(art::Handle<art::mirror::Class> klass) REQUIRES_SHARED(art::Locks::mutator_lock_) { if (event_handler->IsEventEnabledAnywhere(ArtJvmtiEvent::kClassLoad)) { art::Thread* thread = art::Thread::Current(); diff --git a/runtime/openjdkjvmti/ti_redefine.cc b/runtime/openjdkjvmti/ti_redefine.cc index 34efc502e1..d2ddc21cd4 100644 --- a/runtime/openjdkjvmti/ti_redefine.cc +++ b/runtime/openjdkjvmti/ti_redefine.cc @@ -520,28 +520,13 @@ void Redefiner::RecordFailure(jvmtiError result, result_ = result; } -// Allocates a ByteArray big enough to store the given number of bytes and copies them from the -// bytes pointer. -static art::mirror::ByteArray* AllocateAndFillBytes(art::Thread* self, - const uint8_t* bytes, - int32_t num_bytes) - REQUIRES_SHARED(art::Locks::mutator_lock_) { - art::StackHandleScope<1> hs(self); - art::Handle<art::mirror::ByteArray> arr(hs.NewHandle( - art::mirror::ByteArray::Alloc(self, num_bytes))); - if (!arr.IsNull()) { - // Copy it in. Just skip if it's null - memcpy(arr->GetData(), bytes, num_bytes); - } - return arr.Get(); -} - art::mirror::ByteArray* Redefiner::ClassRedefinition::AllocateOrGetOriginalDexFileBytes() { // If we have been specifically given a new set of bytes use that if (original_dex_file_.size() != 0) { - return AllocateAndFillBytes(driver_->self_, - &original_dex_file_.At(0), - original_dex_file_.size()); + return art::mirror::ByteArray::AllocateAndFill( + driver_->self_, + reinterpret_cast<const signed char*>(&original_dex_file_.At(0)), + original_dex_file_.size()); } // See if we already have one set. @@ -561,7 +546,10 @@ art::mirror::ByteArray* Redefiner::ClassRedefinition::AllocateOrGetOriginalDexFi LOG(WARNING) << "Current dex file has more than one class in it. Calling RetransformClasses " << "on this class might fail if no transformations are applied to it!"; } - return AllocateAndFillBytes(driver_->self_, current_dex_file.Begin(), current_dex_file.Size()); + return art::mirror::ByteArray::AllocateAndFill( + driver_->self_, + reinterpret_cast<const signed char*>(current_dex_file.Begin()), + current_dex_file.Size()); } struct CallbackCtx { diff --git a/runtime/openjdkjvmti/ti_redefine.h b/runtime/openjdkjvmti/ti_redefine.h index 29a7e1f3ac..fdc13eee32 100644 --- a/runtime/openjdkjvmti/ti_redefine.h +++ b/runtime/openjdkjvmti/ti_redefine.h @@ -96,6 +96,11 @@ class Redefiner { static jvmtiError IsModifiableClass(jvmtiEnv* env, jclass klass, jboolean* is_redefinable); + static std::unique_ptr<art::MemMap> MoveDataToMemMap(const std::string& original_location, + jint data_len, + const unsigned char* dex_data, + std::string* error_msg); + private: class ClassRedefinition { public: @@ -234,11 +239,6 @@ class Redefiner { /*out*/std::string* error_msg) REQUIRES_SHARED(art::Locks::mutator_lock_); - static std::unique_ptr<art::MemMap> MoveDataToMemMap(const std::string& original_location, - jint data_len, - const unsigned char* dex_data, - std::string* error_msg); - // TODO Put on all the lock qualifiers. jvmtiError Run() REQUIRES_SHARED(art::Locks::mutator_lock_); diff --git a/runtime/openjdkjvmti/transform.cc b/runtime/openjdkjvmti/transform.cc index 745c0f5dbb..3c4cfea19a 100644 --- a/runtime/openjdkjvmti/transform.cc +++ b/runtime/openjdkjvmti/transform.cc @@ -137,20 +137,6 @@ jvmtiError GetClassLocation(ArtJvmTiEnv* env, jclass klass, /*out*/std::string* return OK; } -static jvmtiError CopyDataIntoJvmtiBuffer(ArtJvmTiEnv* env, - const unsigned char* source, - jint len, - /*out*/unsigned char** dest) { - jvmtiError res = env->Allocate(len, dest); - if (res != OK) { - return res; - } - memcpy(reinterpret_cast<void*>(*dest), - reinterpret_cast<const void*>(source), - len); - return OK; -} - jvmtiError Transformer::GetDexDataForRetransformation(ArtJvmTiEnv* env, art::Handle<art::mirror::Class> klass, /*out*/jint* dex_data_len, diff --git a/test/934-load-transform/build b/test/934-load-transform/build new file mode 100755 index 0000000000..898e2e54a2 --- /dev/null +++ b/test/934-load-transform/build @@ -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-build "$@" --experimental agents diff --git a/test/934-load-transform/expected.txt b/test/934-load-transform/expected.txt new file mode 100644 index 0000000000..2b60207f03 --- /dev/null +++ b/test/934-load-transform/expected.txt @@ -0,0 +1 @@ +Goodbye diff --git a/test/934-load-transform/info.txt b/test/934-load-transform/info.txt new file mode 100644 index 0000000000..875a5f6ec1 --- /dev/null +++ b/test/934-load-transform/info.txt @@ -0,0 +1 @@ +Tests basic functions in the jvmti plugin. diff --git a/test/934-load-transform/run b/test/934-load-transform/run new file mode 100755 index 0000000000..4379349cb2 --- /dev/null +++ b/test/934-load-transform/run @@ -0,0 +1,19 @@ +#!/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 "$@" --experimental agents \ + --experimental runtime-plugins \ + --jvmti diff --git a/test/934-load-transform/src/Main.java b/test/934-load-transform/src/Main.java new file mode 100644 index 0000000000..0b7f26890f --- /dev/null +++ b/test/934-load-transform/src/Main.java @@ -0,0 +1,66 @@ +/* + * 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. + */ + +import java.util.Base64; +public class Main { + + /** + * base64 encoded class/dex file for + * class Transform { + * public void sayHi() { + * System.out.println("Goodbye"); + * } + * } + */ + private static final byte[] CLASS_BYTES = Base64.getDecoder().decode( + "yv66vgAAADQAHAoABgAOCQAPABAIABEKABIAEwcAFAcAFQEABjxpbml0PgEAAygpVgEABENvZGUB" + + "AA9MaW5lTnVtYmVyVGFibGUBAAVzYXlIaQEAClNvdXJjZUZpbGUBAA5UcmFuc2Zvcm0uamF2YQwA" + + "BwAIBwAWDAAXABgBAAdHb29kYnllBwAZDAAaABsBAAlUcmFuc2Zvcm0BABBqYXZhL2xhbmcvT2Jq" + + "ZWN0AQAQamF2YS9sYW5nL1N5c3RlbQEAA291dAEAFUxqYXZhL2lvL1ByaW50U3RyZWFtOwEAE2ph" + + "dmEvaW8vUHJpbnRTdHJlYW0BAAdwcmludGxuAQAVKExqYXZhL2xhbmcvU3RyaW5nOylWACAABQAG" + + "AAAAAAACAAAABwAIAAEACQAAAB0AAQABAAAABSq3AAGxAAAAAQAKAAAABgABAAAAEQABAAsACAAB" + + "AAkAAAAlAAIAAQAAAAmyAAISA7YABLEAAAABAAoAAAAKAAIAAAATAAgAFAABAAwAAAACAA0="); + private static final byte[] DEX_BYTES = Base64.getDecoder().decode( + "ZGV4CjAzNQCLXSBQ5FiS3f16krSYZFF8xYZtFVp0GRXMAgAAcAAAAHhWNBIAAAAAAAAAACwCAAAO" + + "AAAAcAAAAAYAAACoAAAAAgAAAMAAAAABAAAA2AAAAAQAAADgAAAAAQAAAAABAACsAQAAIAEAAGIB" + + "AABqAQAAcwEAAIABAACXAQAAqwEAAL8BAADTAQAA4wEAAOYBAADqAQAA/gEAAAMCAAAMAgAAAgAA" + + "AAMAAAAEAAAABQAAAAYAAAAIAAAACAAAAAUAAAAAAAAACQAAAAUAAABcAQAABAABAAsAAAAAAAAA" + + "AAAAAAAAAAANAAAAAQABAAwAAAACAAAAAAAAAAAAAAAAAAAAAgAAAAAAAAAHAAAAAAAAAB4CAAAA" + + "AAAAAQABAAEAAAATAgAABAAAAHAQAwAAAA4AAwABAAIAAAAYAgAACQAAAGIAAAAbAQEAAABuIAIA" + + "EAAOAAAAAQAAAAMABjxpbml0PgAHR29vZGJ5ZQALTFRyYW5zZm9ybTsAFUxqYXZhL2lvL1ByaW50" + + "U3RyZWFtOwASTGphdmEvbGFuZy9PYmplY3Q7ABJMamF2YS9sYW5nL1N0cmluZzsAEkxqYXZhL2xh" + + "bmcvU3lzdGVtOwAOVHJhbnNmb3JtLmphdmEAAVYAAlZMABJlbWl0dGVyOiBqYWNrLTMuMzYAA291" + + "dAAHcHJpbnRsbgAFc2F5SGkAEQAHDgATAAcOhQAAAAEBAICABKACAQG4Ag0AAAAAAAAAAQAAAAAA" + + "AAABAAAADgAAAHAAAAACAAAABgAAAKgAAAADAAAAAgAAAMAAAAAEAAAAAQAAANgAAAAFAAAABAAA" + + "AOAAAAAGAAAAAQAAAAABAAABIAAAAgAAACABAAABEAAAAQAAAFwBAAACIAAADgAAAGIBAAADIAAA" + + "AgAAABMCAAAAIAAAAQAAAB4CAAAAEAAAAQAAACwCAAA="); + + public static void main(String[] args) { + doTest(); + } + + public static void doTest() { + addCommonTransformationResult("Transform", CLASS_BYTES, DEX_BYTES); + enableCommonRetransformation(true); + new Transform().sayHi(); + } + + // Transforms the class + private static native void enableCommonRetransformation(boolean enable); + private static native void addCommonTransformationResult(String target_name, + byte[] class_bytes, + byte[] dex_bytes); +} diff --git a/test/934-load-transform/src/Transform.java b/test/934-load-transform/src/Transform.java new file mode 100644 index 0000000000..f624c3ac99 --- /dev/null +++ b/test/934-load-transform/src/Transform.java @@ -0,0 +1,21 @@ +/* + * 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. + */ + +class Transform { + public void sayHi() { + throw new Error("Should not be called!"); + } +} diff --git a/test/935-non-retransformable/build b/test/935-non-retransformable/build new file mode 100755 index 0000000000..898e2e54a2 --- /dev/null +++ b/test/935-non-retransformable/build @@ -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-build "$@" --experimental agents diff --git a/test/935-non-retransformable/expected.txt b/test/935-non-retransformable/expected.txt new file mode 100644 index 0000000000..ccd50a66a0 --- /dev/null +++ b/test/935-non-retransformable/expected.txt @@ -0,0 +1,6 @@ +Hello +Hello +Goodbye +Hello +Hello +Goodbye diff --git a/test/935-non-retransformable/info.txt b/test/935-non-retransformable/info.txt new file mode 100644 index 0000000000..875a5f6ec1 --- /dev/null +++ b/test/935-non-retransformable/info.txt @@ -0,0 +1 @@ +Tests basic functions in the jvmti plugin. diff --git a/test/935-non-retransformable/run b/test/935-non-retransformable/run new file mode 100755 index 0000000000..4379349cb2 --- /dev/null +++ b/test/935-non-retransformable/run @@ -0,0 +1,19 @@ +#!/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 "$@" --experimental agents \ + --experimental runtime-plugins \ + --jvmti diff --git a/test/935-non-retransformable/src/Main.java b/test/935-non-retransformable/src/Main.java new file mode 100644 index 0000000000..d9cc329ed9 --- /dev/null +++ b/test/935-non-retransformable/src/Main.java @@ -0,0 +1,95 @@ +/* + * 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. + */ + +import java.lang.reflect.Method; +import java.util.Base64; + +public class Main { + + /** + * base64 encoded class/dex file for + * class Transform { + * public void sayHi() { + * System.out.println("Hello"); + * } + * public void sayGoodbye() { + * System.out.println("Goodbye"); + * } + * } + */ + private static final byte[] CLASS_BYTES = Base64.getDecoder().decode( + "yv66vgAAADQAHwoABwAQCQARABIIABMKABQAFQgAFgcAFwcAGAEABjxpbml0PgEAAygpVgEABENv" + + "ZGUBAA9MaW5lTnVtYmVyVGFibGUBAAVzYXlIaQEACnNheUdvb2RieWUBAApTb3VyY2VGaWxlAQAO" + + "VHJhbnNmb3JtLmphdmEMAAgACQcAGQwAGgAbAQAFSGVsbG8HABwMAB0AHgEAB0dvb2RieWUBAAlU" + + "cmFuc2Zvcm0BABBqYXZhL2xhbmcvT2JqZWN0AQAQamF2YS9sYW5nL1N5c3RlbQEAA291dAEAFUxq" + + "YXZhL2lvL1ByaW50U3RyZWFtOwEAE2phdmEvaW8vUHJpbnRTdHJlYW0BAAdwcmludGxuAQAVKExq" + + "YXZhL2xhbmcvU3RyaW5nOylWACAABgAHAAAAAAADAAAACAAJAAEACgAAAB0AAQABAAAABSq3AAGx" + + "AAAAAQALAAAABgABAAAAAQABAAwACQABAAoAAAAlAAIAAQAAAAmyAAISA7YABLEAAAABAAsAAAAK" + + "AAIAAAADAAgABAABAA0ACQABAAoAAAAlAAIAAQAAAAmyAAISBbYABLEAAAABAAsAAAAKAAIAAAAG" + + "AAgABwABAA4AAAACAA8="); + private static final byte[] DEX_BYTES = Base64.getDecoder().decode( + "ZGV4CjAzNQDpaN+7jX/ZLl9Jr0HAEV7nqL1YDuakKakgAwAAcAAAAHhWNBIAAAAAAAAAAIACAAAQ" + + "AAAAcAAAAAYAAACwAAAAAgAAAMgAAAABAAAA4AAAAAUAAADoAAAAAQAAABABAADwAQAAMAEAAJYB" + + "AACeAQAApwEAAK4BAAC7AQAA0gEAAOYBAAD6AQAADgIAAB4CAAAhAgAAJQIAADkCAAA+AgAARwIA" + + "AFMCAAADAAAABAAAAAUAAAAGAAAABwAAAAkAAAAJAAAABQAAAAAAAAAKAAAABQAAAJABAAAEAAEA" + + "DAAAAAAAAAAAAAAAAAAAAA4AAAAAAAAADwAAAAEAAQANAAAAAgAAAAAAAAAAAAAAAAAAAAIAAAAA" + + "AAAACAAAAAAAAABrAgAAAAAAAAEAAQABAAAAWgIAAAQAAABwEAQAAAAOAAMAAQACAAAAXwIAAAkA" + + "AABiAAAAGwEBAAAAbiADABAADgAAAAMAAQACAAAAZQIAAAkAAABiAAAAGwECAAAAbiADABAADgAA" + + "AAEAAAADAAY8aW5pdD4AB0dvb2RieWUABUhlbGxvAAtMVHJhbnNmb3JtOwAVTGphdmEvaW8vUHJp" + + "bnRTdHJlYW07ABJMamF2YS9sYW5nL09iamVjdDsAEkxqYXZhL2xhbmcvU3RyaW5nOwASTGphdmEv" + + "bGFuZy9TeXN0ZW07AA5UcmFuc2Zvcm0uamF2YQABVgACVkwAEmVtaXR0ZXI6IGphY2stNC4yMgAD" + + "b3V0AAdwcmludGxuAApzYXlHb29kYnllAAVzYXlIaQABAAcOAAYABw6HAAMABw6HAAAAAQIAgIAE" + + "sAIBAcgCAQHsAgAAAA0AAAAAAAAAAQAAAAAAAAABAAAAEAAAAHAAAAACAAAABgAAALAAAAADAAAA" + + "AgAAAMgAAAAEAAAAAQAAAOAAAAAFAAAABQAAAOgAAAAGAAAAAQAAABABAAABIAAAAwAAADABAAAB" + + "EAAAAQAAAJABAAACIAAAEAAAAJYBAAADIAAAAwAAAFoCAAAAIAAAAQAAAGsCAAAAEAAAAQAAAIAC" + + "AAA="); + + public static void main(String[] args) { + doTest(); + } + + public static void doTest() { + addCommonTransformationResult("Transform", CLASS_BYTES, DEX_BYTES); + enableCommonRetransformation(true); + // Actually load the class. + Transform t = new Transform(); + try { + // Call functions with reflection. Since the sayGoodbye function does not exist in the + // LTransform; when we compile this for the first time we need to use reflection. + Method hi = Transform.class.getMethod("sayHi"); + Method bye = Transform.class.getMethod("sayGoodbye"); + hi.invoke(t); + t.sayHi(); + bye.invoke(t); + // Make sure we don't get called for transformation again. + addCommonTransformationResult("Transform", new byte[0], new byte[0]); + doCommonClassRetransformation(Transform.class); + hi.invoke(t); + t.sayHi(); + bye.invoke(t); + } catch (Exception e) { + System.out.println("Unexpected error occured! " + e.toString()); + e.printStackTrace(); + } + } + + // Transforms the class + private static native void doCommonClassRetransformation(Class<?>... klasses); + private static native void enableCommonRetransformation(boolean enable); + private static native void addCommonTransformationResult(String target_name, + byte[] class_bytes, + byte[] dex_bytes); +} diff --git a/test/935-non-retransformable/src/Transform.java b/test/935-non-retransformable/src/Transform.java new file mode 100644 index 0000000000..f624c3ac99 --- /dev/null +++ b/test/935-non-retransformable/src/Transform.java @@ -0,0 +1,21 @@ +/* + * 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. + */ + +class Transform { + public void sayHi() { + throw new Error("Should not be called!"); + } +} diff --git a/test/ti-agent/common_helper.cc b/test/ti-agent/common_helper.cc index 80e1797369..330f7e12eb 100644 --- a/test/ti-agent/common_helper.cc +++ b/test/ti-agent/common_helper.cc @@ -329,6 +329,38 @@ jint OnLoad(JavaVM* vm, } // namespace common_retransform +namespace common_transform { + +using art::common_retransform::CommonClassFileLoadHookRetransformable; + +// 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; + } + // 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 = CommonClassFileLoadHookRetransformable; + if (jvmti_env->SetEventCallbacks(&cb, sizeof(cb)) != JVMTI_ERROR_NONE) { + printf("Unable to set class file load hook cb!\n"); + return 1; + } + return 0; +} + +} // namespace common_transform + static void BindMethod(jvmtiEnv* jenv, JNIEnv* env, jclass klass, diff --git a/test/ti-agent/common_helper.h b/test/ti-agent/common_helper.h index c60553dc5a..031850147e 100644 --- a/test/ti-agent/common_helper.h +++ b/test/ti-agent/common_helper.h @@ -27,10 +27,15 @@ 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 + extern bool RuntimeIsJVM; diff --git a/test/ti-agent/common_load.cc b/test/ti-agent/common_load.cc index 8ed8e67e42..f5074519b8 100644 --- a/test/ti-agent/common_load.cc +++ b/test/ti-agent/common_load.cc @@ -110,6 +110,8 @@ static AgentLib agents[] = { { "926-multi-obsolescence", common_redefine::OnLoad, nullptr }, { "930-hello-retransform", common_retransform::OnLoad, nullptr }, { "932-transform-saves", common_retransform::OnLoad, nullptr }, + { "934-load-transform", common_retransform::OnLoad, nullptr }, + { "935-non-retransformable", common_transform::OnLoad, nullptr }, }; static AgentLib* FindAgent(char* name) { |