diff options
21 files changed, 951 insertions, 17 deletions
diff --git a/openjdkjvmti/ti_extension.cc b/openjdkjvmti/ti_extension.cc index f12cb0a380..08667c34f8 100644 --- a/openjdkjvmti/ti_extension.cc +++ b/openjdkjvmti/ti_extension.cc @@ -42,6 +42,7 @@ #include "ti_heap.h" #include "ti_logging.h" #include "ti_monitor.h" +#include "ti_search.h" #include "thread-inl.h" @@ -327,6 +328,54 @@ jvmtiError ExtensionUtil::GetExtensionFunctions(jvmtiEnv* env, return error; } + // AddToDexClassLoader + error = add_extension( + reinterpret_cast<jvmtiExtensionFunction>(SearchUtil::AddToDexClassLoader), + "com.android.art.classloader.add_to_dex_class_loader", + "Adds a dexfile to a given dalvik.system.BaseDexClassLoader in a manner similar to" + " AddToSystemClassLoader.", + { + { "classloader", JVMTI_KIND_IN, JVMTI_TYPE_JOBJECT, false }, + { "segment", JVMTI_KIND_IN_PTR, JVMTI_TYPE_CCHAR, false }, + }, + { + ERR(NULL_POINTER), + ERR(CLASS_LOADER_UNSUPPORTED), + ERR(ILLEGAL_ARGUMENT), + ERR(WRONG_PHASE), + }); + if (error != ERR(NONE)) { + return error; + } + + // AddToDexClassLoaderInMemory requires memfd_create which non-linux doesn't have. The code will + // still all link but since it will only ever return ERR(INTERNAL) we might as well not even + // advertise the extension. + // TODO Support non-linux in some way. +#ifdef __linux__ + // AddToDexClassLoaderInMemory + error = add_extension( + reinterpret_cast<jvmtiExtensionFunction>(SearchUtil::AddToDexClassLoaderInMemory), + "com.android.art.classloader.add_to_dex_class_loader_in_memory", + "Adds a dexfile buffer to a given dalvik.system.BaseDexClassLoader in a manner similar to" + " AddToSystemClassLoader. This may only be done during the LIVE phase. The buffer is copied" + " and the caller is responsible for deallocating it after this call.", + { + { "classloader", JVMTI_KIND_IN, JVMTI_TYPE_JOBJECT, false }, + { "dex_bytes", JVMTI_KIND_IN_BUF, JVMTI_TYPE_CCHAR, false }, + { "dex_bytes_len", JVMTI_KIND_IN, JVMTI_TYPE_JINT, false }, + }, + { + ERR(NULL_POINTER), + ERR(CLASS_LOADER_UNSUPPORTED), + ERR(ILLEGAL_ARGUMENT), + ERR(WRONG_PHASE), + }); + if (error != ERR(NONE)) { + return error; + } +#endif + // Copy into output buffer. *extension_count_ptr = ext_vector.size(); diff --git a/openjdkjvmti/ti_search.cc b/openjdkjvmti/ti_search.cc index 2187825746..1eadf1155f 100644 --- a/openjdkjvmti/ti_search.cc +++ b/openjdkjvmti/ti_search.cc @@ -29,6 +29,9 @@ * questions. */ +#include <sstream> +#include <unistd.h> + #include "ti_search.h" #include "jni.h" @@ -37,6 +40,9 @@ #include "art_jvmti.h" #include "base/enums.h" #include "base/macros.h" +#include "base/memfd.h" +#include "base/os.h" +#include "base/unix_file/fd_file.h" #include "class_linker.h" #include "dex/art_dex_file_loader.h" #include "dex/dex_file.h" @@ -249,37 +255,92 @@ jvmtiError SearchUtil::AddToBootstrapClassLoaderSearch(jvmtiEnv* env, return ERR(NONE); } -jvmtiError SearchUtil::AddToSystemClassLoaderSearch(jvmtiEnv* jvmti_env ATTRIBUTE_UNUSED, - const char* segment) { - if (segment == nullptr) { +jvmtiError SearchUtil::AddToDexClassLoaderInMemory(jvmtiEnv* jvmti_env, + jobject classloader, + const char* dex_bytes, + jint dex_bytes_length) { + if (jvmti_env == nullptr) { + return ERR(INVALID_ENVIRONMENT); + } else if (art::Thread::Current() == nullptr) { + return ERR(UNATTACHED_THREAD); + } else if (classloader == nullptr) { return ERR(NULL_POINTER); + } else if (dex_bytes == nullptr) { + return ERR(NULL_POINTER); + } else if (dex_bytes_length <= 0) { + return ERR(ILLEGAL_ARGUMENT); } jvmtiPhase phase = PhaseUtil::GetPhaseUnchecked(); - if (phase == jvmtiPhase::JVMTI_PHASE_ONLOAD) { - // We could try and see whether it is a valid path. We could also try to allocate Java - // objects to avoid later OOME. - gSystemOnloadSegments.push_back(segment); - return ERR(NONE); - } else if (phase != jvmtiPhase::JVMTI_PHASE_LIVE) { + // TODO We really should try to support doing this during the ON_LOAD phase. + if (phase != jvmtiPhase::JVMTI_PHASE_LIVE) { + JVMTI_LOG(INFO, jvmti_env) << "Cannot add buffers to classpath during ON_LOAD phase to " + << "prevent file-descriptor leaking."; return ERR(WRONG_PHASE); } - jobject sys_class_loader = art::Runtime::Current()->GetSystemClassLoader(); - if (sys_class_loader == nullptr) { - // This is unexpected. + // We have java APIs for adding files to the classpath, we might as well use them. It simplifies a + // lot of code as well. + + // Create a memfd + art::File file(art::memfd_create("JVMTI InMemory Added dex file", 0), /*check-usage*/true); + if (file.Fd() < 0) { + char* reason = strerror(errno); + JVMTI_LOG(ERROR, jvmti_env) << "Unable to create memfd due to " << reason; + return ERR(INTERNAL); + } + // Fill it with the buffer. + if (!file.WriteFully(dex_bytes, dex_bytes_length) || file.Flush() != 0) { + JVMTI_LOG(ERROR, jvmti_env) << "Failed to write to memfd!"; return ERR(INTERNAL); } + // Get the filename in procfs. + std::ostringstream oss; + oss << "/proc/self/fd/" << file.Fd(); + std::string seg(oss.str()); + // Use common code. + + jvmtiError result = AddToDexClassLoader(jvmti_env, classloader, seg.c_str()); + // We have either loaded the dex file and have a new MemMap pointing to the same pages or loading + // has failed and the memory isn't needed anymore. Either way we can close the memfd we created + // and return. + if (file.Close() != 0) { + JVMTI_LOG(WARNING, jvmti_env) << "Failed to close memfd!"; + } + return result; +} + +jvmtiError SearchUtil::AddToDexClassLoader(jvmtiEnv* jvmti_env, + jobject classloader, + const char* segment) { + if (jvmti_env == nullptr) { + return ERR(INVALID_ENVIRONMENT); + } else if (art::Thread::Current() == nullptr) { + return ERR(UNATTACHED_THREAD); + } else if (classloader == nullptr) { + return ERR(NULL_POINTER); + } else if (segment == nullptr) { + return ERR(NULL_POINTER); + } + + jvmtiPhase phase = PhaseUtil::GetPhaseUnchecked(); + + // TODO We really should try to support doing this during the ON_LOAD phase. + if (phase != jvmtiPhase::JVMTI_PHASE_LIVE) { + JVMTI_LOG(INFO, jvmti_env) << "Cannot add to classpath of arbitrary classloaders during " + << "ON_LOAD phase."; + return ERR(WRONG_PHASE); + } // We'll use BaseDexClassLoader.addDexPath, as it takes care of array resizing etc. As a downside, // exceptions are swallowed. art::Thread* self = art::Thread::Current(); JNIEnv* env = self->GetJniEnv(); - if (!env->IsInstanceOf(sys_class_loader, - art::WellKnownClasses::dalvik_system_BaseDexClassLoader)) { - return ERR(INTERNAL); + if (!env->IsInstanceOf(classloader, art::WellKnownClasses::dalvik_system_BaseDexClassLoader)) { + JVMTI_LOG(ERROR, jvmti_env) << "Unable to add " << segment << " to non BaseDexClassLoader!"; + return ERR(CLASS_LOADER_UNSUPPORTED); } jmethodID add_dex_path_id = env->GetMethodID( @@ -294,13 +355,48 @@ jvmtiError SearchUtil::AddToSystemClassLoaderSearch(jvmtiEnv* jvmti_env ATTRIBUT if (dex_path.get() == nullptr) { return ERR(INTERNAL); } - env->CallVoidMethod(sys_class_loader, add_dex_path_id, dex_path.get()); + env->CallVoidMethod(classloader, add_dex_path_id, dex_path.get()); if (env->ExceptionCheck()) { + { + art::ScopedObjectAccess soa(self); + JVMTI_LOG(ERROR, jvmti_env) << "Failed to add " << segment << " to classloader. Error was " + << self->GetException()->Dump(); + } env->ExceptionClear(); return ERR(ILLEGAL_ARGUMENT); } - return ERR(NONE); + return OK; +} + +jvmtiError SearchUtil::AddToSystemClassLoaderSearch(jvmtiEnv* jvmti_env, const char* segment) { + if (segment == nullptr) { + return ERR(NULL_POINTER); + } + + jvmtiPhase phase = PhaseUtil::GetPhaseUnchecked(); + + if (phase == jvmtiPhase::JVMTI_PHASE_ONLOAD) { + // We could try and see whether it is a valid path. We could also try to allocate Java + // objects to avoid later OOME. + gSystemOnloadSegments.push_back(segment); + return ERR(NONE); + } else if (phase != jvmtiPhase::JVMTI_PHASE_LIVE) { + return ERR(WRONG_PHASE); + } + + jobject loader = art::Runtime::Current()->GetSystemClassLoader(); + if (loader == nullptr) { + return ERR(INTERNAL); + } + + art::Thread* self = art::Thread::Current(); + JNIEnv* env = self->GetJniEnv(); + if (!env->IsInstanceOf(loader, art::WellKnownClasses::dalvik_system_BaseDexClassLoader)) { + return ERR(INTERNAL); + } + + return AddToDexClassLoader(jvmti_env, loader, segment); } } // namespace openjdkjvmti diff --git a/openjdkjvmti/ti_search.h b/openjdkjvmti/ti_search.h index 81a28ccbe5..b8d08bf615 100644 --- a/openjdkjvmti/ti_search.h +++ b/openjdkjvmti/ti_search.h @@ -46,6 +46,12 @@ class SearchUtil { static jvmtiError AddToBootstrapClassLoaderSearch(jvmtiEnv* env, const char* segment); static jvmtiError AddToSystemClassLoaderSearch(jvmtiEnv* env, const char* segment); + + static jvmtiError AddToDexClassLoader(jvmtiEnv* env, jobject classloader, const char* segment); + static jvmtiError AddToDexClassLoaderInMemory(jvmtiEnv* env, + jobject classloader, + const char* dex_bytes, + jint dex_bytes_length); }; } // namespace openjdkjvmti diff --git a/test/1963-add-to-dex-classloader-in-memory/add_to_loader.cc b/test/1963-add-to-dex-classloader-in-memory/add_to_loader.cc new file mode 100644 index 0000000000..1c3f36d8ca --- /dev/null +++ b/test/1963-add-to-dex-classloader-in-memory/add_to_loader.cc @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <atomic> + +#include "jvmti.h" + +// Test infrastructure +#include "jvmti_helper.h" +#include "scoped_local_ref.h" +#include "test_env.h" + +namespace art { +namespace Test1963AddToDexClassLoaderInMemory { + +using AddToDexClassLoaderInMemory = jvmtiError (*)(jvmtiEnv* env, + jobject loader, + const unsigned char* dex_file, + jint dex_file_length); + +template <typename T> static void Dealloc(T* t) { + jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(t)); +} + +template <typename T, typename... Rest> static void Dealloc(T* t, Rest... rs) { + Dealloc(t); + Dealloc(rs...); +} +static void DeallocParams(jvmtiParamInfo* params, jint n_params) { + for (jint i = 0; i < n_params; i++) { + Dealloc(params[i].name); + } +} + +AddToDexClassLoaderInMemory GetAddFunction(JNIEnv* env) { + // Get the extensions. + jint n_ext = 0; + jvmtiExtensionFunctionInfo* infos = nullptr; + if (JvmtiErrorToException(env, jvmti_env, jvmti_env->GetExtensionFunctions(&n_ext, &infos))) { + return nullptr; + } + AddToDexClassLoaderInMemory result = nullptr; + for (jint i = 0; i < n_ext; i++) { + jvmtiExtensionFunctionInfo* cur_info = &infos[i]; + if (strcmp("com.android.art.classloader.add_to_dex_class_loader_in_memory", cur_info->id) == + 0) { + result = reinterpret_cast<AddToDexClassLoaderInMemory>(cur_info->func); + } + // Cleanup the cur_info + DeallocParams(cur_info->params, cur_info->param_count); + Dealloc(cur_info->id, cur_info->short_description, cur_info->params, cur_info->errors); + } + // Cleanup the array. + Dealloc(infos); + return result; +} + +extern "C" JNIEXPORT void JNICALL Java_art_Test1963_addToClassLoaderNative(JNIEnv* env, + jclass, + jobject loader, + jobject bytebuffer) { + AddToDexClassLoaderInMemory add_func = GetAddFunction(env); + if (add_func == nullptr) { + env->ThrowNew(env->FindClass("java/lang/RuntimeError"), "Failed to find extension function"); + return; + } + JvmtiErrorToException( + env, + jvmti_env, + add_func(jvmti_env, + loader, + reinterpret_cast<unsigned char*>(env->GetDirectBufferAddress(bytebuffer)), + env->GetDirectBufferCapacity(bytebuffer))); +} + +} // namespace Test1963AddToDexClassLoaderInMemory +} // namespace art diff --git a/test/1963-add-to-dex-classloader-in-memory/expected.txt b/test/1963-add-to-dex-classloader-in-memory/expected.txt new file mode 100644 index 0000000000..c3cc448178 --- /dev/null +++ b/test/1963-add-to-dex-classloader-in-memory/expected.txt @@ -0,0 +1,19 @@ + - Run while adding new referenced class. + -- Running sayHi before redefinition +Hello from TestClass sayHi function +Goodbye from TestClass! + -- Adding NewClass to classloader! + -- Redefine the TestClass + -- call TestClass again, now with NewClass refs +Hello again from TestClass sayHi function +Hello from NewClass sayHi function +Goodbye again from TestClass! + - Run without adding new referenced class. + -- Running sayHi before redefinition +Hello from TestClass sayHi function +Goodbye from TestClass! + -- Redefine the TestClass + -- call TestClass again, now with NewClass refs +Hello again from TestClass sayHi function + -- Exception caught when running test without new class added! java.lang.NoClassDefFoundError + --- java.lang.NoClassDefFoundError At foobar.TestClass.sayHi(TestClass.java:5) diff --git a/test/1963-add-to-dex-classloader-in-memory/info.txt b/test/1963-add-to-dex-classloader-in-memory/info.txt new file mode 100644 index 0000000000..48df9821d6 --- /dev/null +++ b/test/1963-add-to-dex-classloader-in-memory/info.txt @@ -0,0 +1 @@ +Tests we can add dex-file buffers to an existing classloader and the old classes can see them.
\ No newline at end of file diff --git a/test/1963-add-to-dex-classloader-in-memory/run b/test/1963-add-to-dex-classloader-in-memory/run new file mode 100755 index 0000000000..c6e62ae6cd --- /dev/null +++ b/test/1963-add-to-dex-classloader-in-memory/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 diff --git a/test/1963-add-to-dex-classloader-in-memory/src/Main.java b/test/1963-add-to-dex-classloader-in-memory/src/Main.java new file mode 100644 index 0000000000..3728b45be6 --- /dev/null +++ b/test/1963-add-to-dex-classloader-in-memory/src/Main.java @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2019 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.Test1963.run(); + } +} diff --git a/test/1963-add-to-dex-classloader-in-memory/src/art/Redefinition.java b/test/1963-add-to-dex-classloader-in-memory/src/art/Redefinition.java new file mode 120000 index 0000000000..81eaf31bbb --- /dev/null +++ b/test/1963-add-to-dex-classloader-in-memory/src/art/Redefinition.java @@ -0,0 +1 @@ +../../../jvmti-common/Redefinition.java
\ No newline at end of file diff --git a/test/1963-add-to-dex-classloader-in-memory/src/art/Test1963.java b/test/1963-add-to-dex-classloader-in-memory/src/art/Test1963.java new file mode 100644 index 0000000000..a42b3ff691 --- /dev/null +++ b/test/1963-add-to-dex-classloader-in-memory/src/art/Test1963.java @@ -0,0 +1,243 @@ +/* + * Copyright (C) 2019 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.lang.reflect.Constructor; +import java.lang.reflect.Method; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Base64; + +public final class Test1963 { + private static boolean IS_ART = System.getProperty("java.vm.name").equals("Dalvik"); + + private static String TEST_CLASS_NAME = "foobar.TestClass"; + private static String NEW_CLASS_NAME = "foobar.NewClass"; + + /** + * base64 encoded class/dex file for + * package foobar; + * public class NewClass { + * static void sayHi() { + * System.out.println("Hello from NewClass sayHi function"); + * TestClass.sayBye(); + * } + * } + */ + private static byte[] NEW_CLASS_BYTES = Base64.getDecoder().decode( + "yv66vgAAADUAIQoABwAPCQAQABEIABIKABMAFAoAFQAWBwAXBwAYAQAGPGluaXQ+AQADKClWAQAE" + + "Q29kZQEAD0xpbmVOdW1iZXJUYWJsZQEABXNheUhpAQAKU291cmNlRmlsZQEADU5ld0NsYXNzLmph" + + "dmEMAAgACQcAGQwAGgAbAQAiSGVsbG8gZnJvbSBOZXdDbGFzcyBzYXlIaSBmdW5jdGlvbgcAHAwA" + + "HQAeBwAfDAAgAAkBAA9mb29iYXIvTmV3Q2xhc3MBABBqYXZhL2xhbmcvT2JqZWN0AQAQamF2YS9s" + + "YW5nL1N5c3RlbQEAA291dAEAFUxqYXZhL2lvL1ByaW50U3RyZWFtOwEAE2phdmEvaW8vUHJpbnRT" + + "dHJlYW0BAAdwcmludGxuAQAVKExqYXZhL2xhbmcvU3RyaW5nOylWAQAQZm9vYmFyL1Rlc3RDbGFz" + + "cwEABnNheUJ5ZQAhAAYABwAAAAAAAgABAAgACQABAAoAAAAdAAEAAQAAAAUqtwABsQAAAAEACwAA" + + "AAYAAQAAAAIACAAMAAkAAQAKAAAALAACAAAAAAAMsgACEgO2AAS4AAWxAAAAAQALAAAADgADAAAA" + + "BAAIAAUACwAGAAEADQAAAAIADg=="); + private static byte[] NEW_DEX_BYTES = Base64.getDecoder().decode( + "ZGV4CjAzNQA8kzH5IALCWT88v716WlU7OfqukCT2o6WQAwAAcAAAAHhWNBIAAAAAAAAAAOQCAAAQ" + + "AAAAcAAAAAcAAACwAAAAAgAAAMwAAAABAAAA5AAAAAUAAADsAAAAAQAAABQBAABcAgAANAEAAIIB" + + "AACKAQAArgEAAMEBAADVAQAA7AEAAAACAAAUAgAAKAIAADcCAAA6AgAAPgIAAEMCAABMAgAAVAIA" + + "AFsCAAACAAAAAwAAAAQAAAAFAAAABgAAAAcAAAAJAAAACQAAAAYAAAAAAAAACgAAAAYAAAB8AQAA" + + "BQACAAsAAAAAAAAAAAAAAAAAAAAOAAAAAQAAAA0AAAACAAEADAAAAAMAAAAAAAAAAAAAAAEAAAAD" + + "AAAAAAAAAAgAAAAAAAAA0gIAAAAAAAABAAEAAQAAAHIBAAAEAAAAcBAEAAAADgACAAAAAgAAAHYB" + + "AAALAAAAYgAAABoBAQBuIAMAEABxAAIAAAAOAAIADgAEAA54PAABAAAABAAGPGluaXQ+ACJIZWxs" + + "byBmcm9tIE5ld0NsYXNzIHNheUhpIGZ1bmN0aW9uABFMZm9vYmFyL05ld0NsYXNzOwASTGZvb2Jh" + + "ci9UZXN0Q2xhc3M7ABVMamF2YS9pby9QcmludFN0cmVhbTsAEkxqYXZhL2xhbmcvT2JqZWN0OwAS" + + "TGphdmEvbGFuZy9TdHJpbmc7ABJMamF2YS9sYW5nL1N5c3RlbTsADU5ld0NsYXNzLmphdmEAAVYA" + + "AlZMAANvdXQAB3ByaW50bG4ABnNheUJ5ZQAFc2F5SGkAdX5+RDh7ImNvbXBpbGF0aW9uLW1vZGUi" + + "OiJkZWJ1ZyIsIm1pbi1hcGkiOjEsInNoYS0xIjoiZDMyODJiOGY1NDdjMjM0YzRlNGM5MzA5YzM2" + + "Yzc5NWEyOTg1NmVhYiIsInZlcnNpb24iOiIxLjYuMS1kZXYifQAAAAIAAIGABLQCAQjMAgAAAAAO" + + "AAAAAAAAAAEAAAAAAAAAAQAAABAAAABwAAAAAgAAAAcAAACwAAAAAwAAAAIAAADMAAAABAAAAAEA" + + "AADkAAAABQAAAAUAAADsAAAABgAAAAEAAAAUAQAAASAAAAIAAAA0AQAAAyAAAAIAAAByAQAAARAA" + + "AAEAAAB8AQAAAiAAABAAAACCAQAAACAAAAEAAADSAgAAAxAAAAEAAADgAgAAABAAAAEAAADkAgAA"); + /** + * base64 encoded class/dex file for + * package foobar; + * public class TestClass { + * public static void sayHi() { + * System.out.println("Hello again from TestClass sayHi function"); + * TestClass.sayBye(); + * } + * static void sayBye() { + * System.out.println("Goodbye from TestClass!"); + * } + * } + */ + private static byte[] CLASS_BYTES = Base64.getDecoder().decode( + "yv66vgAAADUAIQoACAARCQASABMIABQKABUAFgoABwAXCAAYBwAZBwAaAQAGPGluaXQ+AQADKClW" + + "AQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEABXNheUhpAQAGc2F5QnllAQAKU291cmNlRmlsZQEA" + + "DlRlc3RDbGFzcy5qYXZhDAAJAAoHABsMABwAHQEAI0hlbGxvIGZyb20gVGVzdENsYXNzIHNheUhp" + + "IGZ1bmN0aW9uBwAeDAAfACAMAA4ACgEAF0dvb2RieWUgZnJvbSBUZXN0Q2xhc3MhAQAQZm9vYmFy" + + "L1Rlc3RDbGFzcwEAEGphdmEvbGFuZy9PYmplY3QBABBqYXZhL2xhbmcvU3lzdGVtAQADb3V0AQAV" + + "TGphdmEvaW8vUHJpbnRTdHJlYW07AQATamF2YS9pby9QcmludFN0cmVhbQEAB3ByaW50bG4BABUo" + + "TGphdmEvbGFuZy9TdHJpbmc7KVYAIQAHAAgAAAAAAAMAAQAJAAoAAQALAAAAHQABAAEAAAAFKrcA" + + "AbEAAAABAAwAAAAGAAEAAAACAAkADQAKAAEACwAAACwAAgAAAAAADLIAAhIDtgAEuAAFsQAAAAEA" + + "DAAAAA4AAwAAAAQACAAFAAsABgAIAA4ACgABAAsAAAAlAAIAAAAAAAmyAAISBrYABLEAAAABAAwA" + + "AAAKAAIAAAAIAAgACQABAA8AAAACABA="); + + private static byte[] DEX_BYTES = Base64.getDecoder().decode( + "ZGV4CjAzNQARmtFTPdWXebnrTNy5b71tEiJKC96qIPXAAwAAcAAAAHhWNBIAAAAAAAAAABQDAAAQ" + + "AAAAcAAAAAYAAACwAAAAAgAAAMgAAAABAAAA4AAAAAUAAADoAAAAAQAAABABAACQAgAAMAEAAKYB" + + "AACuAQAAxwEAAOwBAAAAAgAAFwIAACsCAAA/AgAAUwIAAGMCAABmAgAAagIAAG8CAAB4AgAAgAIA" + + "AIcCAAADAAAABAAAAAUAAAAGAAAABwAAAAkAAAAJAAAABQAAAAAAAAAKAAAABQAAAKABAAAEAAEA" + + "CwAAAAAAAAAAAAAAAAAAAA0AAAAAAAAADgAAAAEAAQAMAAAAAgAAAAAAAAAAAAAAAQAAAAIAAAAA" + + "AAAACAAAAAAAAAD+AgAAAAAAAAEAAQABAAAAjgEAAAQAAABwEAQAAAAOAAIAAAACAAAAkgEAAAgA" + + "AABiAAAAGgEBAG4gAwAQAA4AAgAAAAIAAACXAQAACwAAAGIAAAAaAQIAbiADABAAcQABAAAADgAC" + + "AA4ACAAOeAAEAA54PAAAAAABAAAAAwAGPGluaXQ+ABdHb29kYnllIGZyb20gVGVzdENsYXNzIQAj" + + "SGVsbG8gZnJvbSBUZXN0Q2xhc3Mgc2F5SGkgZnVuY3Rpb24AEkxmb29iYXIvVGVzdENsYXNzOwAV" + + "TGphdmEvaW8vUHJpbnRTdHJlYW07ABJMamF2YS9sYW5nL09iamVjdDsAEkxqYXZhL2xhbmcvU3Ry" + + "aW5nOwASTGphdmEvbGFuZy9TeXN0ZW07AA5UZXN0Q2xhc3MuamF2YQABVgACVkwAA291dAAHcHJp" + + "bnRsbgAGc2F5QnllAAVzYXlIaQB1fn5EOHsiY29tcGlsYXRpb24tbW9kZSI6ImRlYnVnIiwibWlu" + + "LWFwaSI6MSwic2hhLTEiOiJkMzI4MmI4ZjU0N2MyMzRjNGU0YzkzMDljMzZjNzk1YTI5ODU2ZWFi" + + "IiwidmVyc2lvbiI6IjEuNi4xLWRldiJ9AAAAAwAAgYAEsAIBCMgCAQnoAgAAAAAOAAAAAAAAAAEA" + + "AAAAAAAAAQAAABAAAABwAAAAAgAAAAYAAACwAAAAAwAAAAIAAADIAAAABAAAAAEAAADgAAAABQAA" + + "AAUAAADoAAAABgAAAAEAAAAQAQAAASAAAAMAAAAwAQAAAyAAAAMAAACOAQAAARAAAAEAAACgAQAA" + + "AiAAABAAAACmAQAAACAAAAEAAAD+AgAAAxAAAAEAAAAQAwAAABAAAAEAAAAUAwAA"); + /** + * base64 encoded class/dex file for + * package foobar; + * public class TestClass { + * public static void sayHi() { + * System.out.println("Hello again from TestClass sayHi function"); + * NewClass.sayHi(); + * } + * static void sayBye() { + * System.out.println("Goodbye again from TestClass!"); + * } + * } + */ + private static byte[] REDEF_CLASS_BYTES = Base64.getDecoder().decode( + "yv66vgAAADUAIwoACAARCQASABMIABQKABUAFgoAFwAYCAAZBwAaBwAbAQAGPGluaXQ+AQADKClW" + + "AQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEABXNheUhpAQAGc2F5QnllAQAKU291cmNlRmlsZQEA" + + "DlRlc3RDbGFzcy5qYXZhDAAJAAoHABwMAB0AHgEAKUhlbGxvIGFnYWluIGZyb20gVGVzdENsYXNz" + + "IHNheUhpIGZ1bmN0aW9uBwAfDAAgACEHACIMAA0ACgEAHUdvb2RieWUgYWdhaW4gZnJvbSBUZXN0" + + "Q2xhc3MhAQAQZm9vYmFyL1Rlc3RDbGFzcwEAEGphdmEvbGFuZy9PYmplY3QBABBqYXZhL2xhbmcv" + + "U3lzdGVtAQADb3V0AQAVTGphdmEvaW8vUHJpbnRTdHJlYW07AQATamF2YS9pby9QcmludFN0cmVh" + + "bQEAB3ByaW50bG4BABUoTGphdmEvbGFuZy9TdHJpbmc7KVYBAA9mb29iYXIvTmV3Q2xhc3MAIQAH" + + "AAgAAAAAAAMAAQAJAAoAAQALAAAAHQABAAEAAAAFKrcAAbEAAAABAAwAAAAGAAEAAAACAAkADQAK" + + "AAEACwAAACwAAgAAAAAADLIAAhIDtgAEuAAFsQAAAAEADAAAAA4AAwAAAAQACAAFAAsABgAIAA4A" + + "CgABAAsAAAAlAAIAAAAAAAmyAAISBrYABLEAAAABAAwAAAAKAAIAAAAIAAgACQABAA8AAAACABA="); + + private static byte[] REDEF_DEX_BYTES = Base64.getDecoder().decode( + "ZGV4CjAzNQA2plEeYRH4vl6wJgnAZOVcZ537QN9NXB3wAwAAcAAAAHhWNBIAAAAAAAAAAEQDAAAR" + + "AAAAcAAAAAcAAAC0AAAAAgAAANAAAAABAAAA6AAAAAYAAADwAAAAAQAAACABAACwAgAAQAEAALYB" + + "AAC+AQAA3QEAAAgCAAAbAgAALwIAAEYCAABaAgAAbgIAAIICAACSAgAAlQIAAJkCAACeAgAApwIA" + + "AK8CAAC2AgAAAwAAAAQAAAAFAAAABgAAAAcAAAAIAAAACgAAAAoAAAAGAAAAAAAAAAsAAAAGAAAA" + + "sAEAAAUAAgAMAAAAAAAAAA8AAAABAAAAAAAAAAEAAAAOAAAAAQAAAA8AAAACAAEADQAAAAMAAAAA" + + "AAAAAQAAAAEAAAADAAAAAAAAAAkAAAAAAAAALQMAAAAAAAABAAEAAQAAAJ4BAAAEAAAAcBAFAAAA" + + "DgACAAAAAgAAAKIBAAAIAAAAYgAAABoBAQBuIAQAEAAOAAIAAAACAAAApwEAAAsAAABiAAAAGgEC" + + "AG4gBAAQAHEAAAAAAA4AAgAOAAgADngABAAOeDwAAAAAAQAAAAQABjxpbml0PgAdR29vZGJ5ZSBh" + + "Z2FpbiBmcm9tIFRlc3RDbGFzcyEAKUhlbGxvIGFnYWluIGZyb20gVGVzdENsYXNzIHNheUhpIGZ1" + + "bmN0aW9uABFMZm9vYmFyL05ld0NsYXNzOwASTGZvb2Jhci9UZXN0Q2xhc3M7ABVMamF2YS9pby9Q" + + "cmludFN0cmVhbTsAEkxqYXZhL2xhbmcvT2JqZWN0OwASTGphdmEvbGFuZy9TdHJpbmc7ABJMamF2" + + "YS9sYW5nL1N5c3RlbTsADlRlc3RDbGFzcy5qYXZhAAFWAAJWTAADb3V0AAdwcmludGxuAAZzYXlC" + + "eWUABXNheUhpAHV+fkQ4eyJjb21waWxhdGlvbi1tb2RlIjoiZGVidWciLCJtaW4tYXBpIjoxLCJz" + + "aGEtMSI6ImQzMjgyYjhmNTQ3YzIzNGM0ZTRjOTMwOWMzNmM3OTVhMjk4NTZlYWIiLCJ2ZXJzaW9u" + + "IjoiMS42LjEtZGV2In0AAAADAAGBgATAAgEI2AIBCfgCAAAAAAAOAAAAAAAAAAEAAAAAAAAAAQAA" + + "ABEAAABwAAAAAgAAAAcAAAC0AAAAAwAAAAIAAADQAAAABAAAAAEAAADoAAAABQAAAAYAAADwAAAA" + + "BgAAAAEAAAAgAQAAASAAAAMAAABAAQAAAyAAAAMAAACeAQAAARAAAAEAAACwAQAAAiAAABEAAAC2" + + "AQAAACAAAAEAAAAtAwAAAxAAAAEAAABAAwAAABAAAAEAAABEAwAA"); + + public static void SafePrintCause(Throwable t) { + StackTraceElement cause = t.getStackTrace()[0]; + System.out.println(" --- " + t.getClass().getName() + " At " + cause.getClassName() + "." + + cause.getMethodName() + "(" + cause.getFileName() + ":" + + cause.getLineNumber() + ")"); + } + + public static void run() throws Exception { + System.out.println(" - Run while adding new referenced class."); + try { + run(true); + } catch (Exception e) { + // Unfortunately art and RI have different messages here so just return the type. + System.out.println(" -- Exception caught when running test with new class added! " + + e.getCause().getClass().getName()); + SafePrintCause(e.getCause()); + } + System.out.println(" - Run without adding new referenced class."); + try { + run(false); + } catch (Exception e) { + // Unfortunately art and RI have different messages here so just return the type. + System.out.println(" -- Exception caught when running test without new class added! " + + e.getCause().getClass().getName()); + SafePrintCause(e.getCause()); + } + } + + public static void run(boolean add_new) throws Exception { + ClassLoader cl = getClassLoader(); + Class<?> target = cl.loadClass(TEST_CLASS_NAME); + Method sayHi = target.getDeclaredMethod("sayHi"); + System.out.println(" -- Running sayHi before redefinition"); + sayHi.invoke(null); + if (add_new) { + System.out.println(" -- Adding NewClass to classloader!"); + addToClassLoader(cl, NEW_CLASS_BYTES, NEW_DEX_BYTES); + } + System.out.println(" -- Redefine the TestClass"); + Redefinition.doCommonClassRedefinition(target, REDEF_CLASS_BYTES, REDEF_DEX_BYTES); + System.out.println(" -- call TestClass again, now with NewClass refs"); + sayHi.invoke(null); + } + + public static class ExtensibleClassLoader extends ClassLoader { + private byte[] new_class = null; + public ExtensibleClassLoader() { + super(ExtensibleClassLoader.class.getClassLoader()); + } + + public void addSingleClass(byte[] bb) { + new_class = bb; + } + + protected Class<?> findClass(String name) throws ClassNotFoundException { + if (name.equals(TEST_CLASS_NAME)) { + return this.defineClass(TEST_CLASS_NAME, CLASS_BYTES, 0, CLASS_BYTES.length); + } + if (name.equals(NEW_CLASS_NAME) && new_class != null) { + return this.defineClass(name, new_class, 0, new_class.length); + } else { + return super.findClass(name); + } + } + } + + public static ClassLoader getClassLoader() throws Exception { + if (!IS_ART) { + return new ExtensibleClassLoader(); + } else { + Class<?> class_loader_class = Class.forName("dalvik.system.InMemoryDexClassLoader"); + Constructor<?> ctor = class_loader_class.getConstructor(ByteBuffer.class, ClassLoader.class); + return (ClassLoader)ctor.newInstance(ByteBuffer.wrap(DEX_BYTES), + Test1963.class.getClassLoader()); + } + } + + public static void addToClassLoader(ClassLoader cl, byte[] class_bytes, byte[] dex_bytes) { + if (IS_ART) { + addToClassLoaderNative(cl, ByteBuffer.allocateDirect(dex_bytes.length).put(dex_bytes)); + } else { + ((ExtensibleClassLoader)cl).addSingleClass(class_bytes); + } + } + + public static native void addToClassLoaderNative(ClassLoader loader, ByteBuffer buff); +} diff --git a/test/1964-add-to-dex-classloader-file/add_to_loader.cc b/test/1964-add-to-dex-classloader-file/add_to_loader.cc new file mode 100644 index 0000000000..9fbea97124 --- /dev/null +++ b/test/1964-add-to-dex-classloader-file/add_to_loader.cc @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <atomic> + +#include "jvmti.h" + +// Test infrastructure +#include "jvmti_helper.h" +#include "scoped_local_ref.h" +#include "test_env.h" + +namespace art { +namespace Test1964AddToDexClassLoader { + +using AddToDexClassLoader = jvmtiError (*)(jvmtiEnv* env, + jobject loader, + const char* segment); + +template <typename T> static void Dealloc(T* t) { + jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(t)); +} + +template <typename T, typename... Rest> static void Dealloc(T* t, Rest... rs) { + Dealloc(t); + Dealloc(rs...); +} +static void DeallocParams(jvmtiParamInfo* params, jint n_params) { + for (jint i = 0; i < n_params; i++) { + Dealloc(params[i].name); + } +} + +AddToDexClassLoader GetAddFunction(JNIEnv* env) { + // Get the extensions. + jint n_ext = 0; + jvmtiExtensionFunctionInfo* infos = nullptr; + if (JvmtiErrorToException(env, jvmti_env, jvmti_env->GetExtensionFunctions(&n_ext, &infos))) { + return nullptr; + } + AddToDexClassLoader result = nullptr; + for (jint i = 0; i < n_ext; i++) { + jvmtiExtensionFunctionInfo* cur_info = &infos[i]; + if (strcmp("com.android.art.classloader.add_to_dex_class_loader", cur_info->id) == + 0) { + result = reinterpret_cast<AddToDexClassLoader>(cur_info->func); + } + // Cleanup the cur_info + DeallocParams(cur_info->params, cur_info->param_count); + Dealloc(cur_info->id, cur_info->short_description, cur_info->params, cur_info->errors); + } + // Cleanup the array. + Dealloc(infos); + return result; +} + +extern "C" JNIEXPORT void JNICALL Java_Main_addToClassLoaderNative(JNIEnv* env, + jclass, + jobject loader, + jstring segment) { + AddToDexClassLoader add_func = GetAddFunction(env); + if (add_func == nullptr) { + env->ThrowNew(env->FindClass("java/lang/RuntimeError"), "Failed to find extension function"); + return; + } + const char* chars = env->GetStringUTFChars(segment, nullptr); + JvmtiErrorToException( + env, + jvmti_env, + add_func(jvmti_env, + loader, + chars)); + env->ReleaseStringUTFChars(segment, chars); +} + +} // namespace Test1964AddToDexClassLoader +} // namespace art diff --git a/test/1964-add-to-dex-classloader-file/expected.txt b/test/1964-add-to-dex-classloader-file/expected.txt new file mode 100644 index 0000000000..58b86ef896 --- /dev/null +++ b/test/1964-add-to-dex-classloader-file/expected.txt @@ -0,0 +1,23 @@ + - Run while adding new referenced class. + -- Running sayHi before redefinition +Hello from TestClass sayHi function +Goodbye from TestClass! + -- Adding NewClass to classloader! + -- Redefine the TestClass + -- call TestClass again, now with NewClass refs +Hello again from TestClass sayHi function +Hello from NewClass sayHi function +Nearby stack: + private static native art.StackTrace$StackFrameData[] art.StackTrace.nativeGetStackTrace(java.lang.Thread)(line: -1) + public static art.StackTrace$StackFrameData[] art.StackTrace.GetStackTrace(java.lang.Thread)(line: 61) + static void foobar.NewClass.sayHi() throws java.lang.Exception(line: 27) + public static void foobar.TestClass.sayHi()(line: 5) + - Run without adding new referenced class. + -- Running sayHi before redefinition +Hello from TestClass sayHi function +Goodbye from TestClass! + -- Redefine the TestClass + -- call TestClass again, now with NewClass refs +Hello again from TestClass sayHi function + -- Exception caught when running test without new class added! java.lang.NoClassDefFoundError + --- java.lang.NoClassDefFoundError At foobar.TestClass.sayHi(TestClass.java:5) diff --git a/test/1964-add-to-dex-classloader-file/info.txt b/test/1964-add-to-dex-classloader-file/info.txt new file mode 100644 index 0000000000..48df9821d6 --- /dev/null +++ b/test/1964-add-to-dex-classloader-file/info.txt @@ -0,0 +1 @@ +Tests we can add dex-file buffers to an existing classloader and the old classes can see them.
\ No newline at end of file diff --git a/test/1964-add-to-dex-classloader-file/run b/test/1964-add-to-dex-classloader-file/run new file mode 100755 index 0000000000..c6e62ae6cd --- /dev/null +++ b/test/1964-add-to-dex-classloader-file/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 diff --git a/test/1964-add-to-dex-classloader-file/src-ex/foobar/NewClass.java b/test/1964-add-to-dex-classloader-file/src-ex/foobar/NewClass.java new file mode 100644 index 0000000000..a27d5d3c58 --- /dev/null +++ b/test/1964-add-to-dex-classloader-file/src-ex/foobar/NewClass.java @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2019 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 foobar; +import art.Breakpoint; +import art.StackTrace; + +public class NewClass { + static void sayHi() throws Exception { + System.out.println("Hello from NewClass sayHi function"); + // Doing this would be nice but it would make compiling the test more tricky. Just use + // reflection and check the classloader is the same. + // TestClass.sayBye(); + StackTrace.StackFrameData[] stack = StackTrace.GetStackTrace(Thread.currentThread()); + StackTrace.StackFrameData caller = null; + System.out.println("Nearby stack:"); + for (StackTrace.StackFrameData sfd : stack) { + String caller_name = sfd.method.getDeclaringClass().getName(); + if (caller_name.startsWith("art.") || caller_name.startsWith("foobar.")) { + System.out.println("\t" + sfd.method + "(line: " + + Breakpoint.locationToLine(sfd.method, sfd.current_location) + ")"); + caller = sfd; + } else { + break; + } + } + if (NewClass.class.getClassLoader() != caller.method.getDeclaringClass().getClassLoader()) { + System.out.println("Different classloader for TestClass and my class."); + } + } +}
\ No newline at end of file diff --git a/test/1964-add-to-dex-classloader-file/src/Main.java b/test/1964-add-to-dex-classloader-file/src/Main.java new file mode 100644 index 0000000000..2293d42193 --- /dev/null +++ b/test/1964-add-to-dex-classloader-file/src/Main.java @@ -0,0 +1,210 @@ +/* + * Copyright (C) 2019 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 art.Redefinition; +import java.lang.reflect.Constructor; +import java.lang.reflect.Method; +import java.net.URL; +import java.net.URLClassLoader; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Base64; + +public class Main { + private static String TEST_NAME = "1964-add-to-dex-classloader-file"; + private static boolean IS_ART = System.getProperty("java.vm.name").equals("Dalvik"); + + private static String TEST_CLASS_NAME = "foobar.TestClass"; + private static String NEW_CLASS_NAME = "foobar.NewClass"; + + /** + * base64 encoded class/dex file for + * package foobar; + * public class TestClass { + * public static void sayHi() { + * System.out.println("Hello again from TestClass sayHi function"); + * TestClass.sayBye(); + * } + * static void sayBye() { + * System.out.println("Goodbye from TestClass!"); + * } + * } + */ + private static byte[] CLASS_BYTES = Base64.getDecoder().decode( + "yv66vgAAADUAIQoACAARCQASABMIABQKABUAFgoABwAXCAAYBwAZBwAaAQAGPGluaXQ+AQADKClW" + + "AQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEABXNheUhpAQAGc2F5QnllAQAKU291cmNlRmlsZQEA" + + "DlRlc3RDbGFzcy5qYXZhDAAJAAoHABsMABwAHQEAI0hlbGxvIGZyb20gVGVzdENsYXNzIHNheUhp" + + "IGZ1bmN0aW9uBwAeDAAfACAMAA4ACgEAF0dvb2RieWUgZnJvbSBUZXN0Q2xhc3MhAQAQZm9vYmFy" + + "L1Rlc3RDbGFzcwEAEGphdmEvbGFuZy9PYmplY3QBABBqYXZhL2xhbmcvU3lzdGVtAQADb3V0AQAV" + + "TGphdmEvaW8vUHJpbnRTdHJlYW07AQATamF2YS9pby9QcmludFN0cmVhbQEAB3ByaW50bG4BABUo" + + "TGphdmEvbGFuZy9TdHJpbmc7KVYAIQAHAAgAAAAAAAMAAQAJAAoAAQALAAAAHQABAAEAAAAFKrcA" + + "AbEAAAABAAwAAAAGAAEAAAACAAkADQAKAAEACwAAACwAAgAAAAAADLIAAhIDtgAEuAAFsQAAAAEA" + + "DAAAAA4AAwAAAAQACAAFAAsABgAIAA4ACgABAAsAAAAlAAIAAAAAAAmyAAISBrYABLEAAAABAAwA" + + "AAAKAAIAAAAIAAgACQABAA8AAAACABA="); + + private static byte[] DEX_BYTES = Base64.getDecoder().decode( + "ZGV4CjAzNQARmtFTPdWXebnrTNy5b71tEiJKC96qIPXAAwAAcAAAAHhWNBIAAAAAAAAAABQDAAAQ" + + "AAAAcAAAAAYAAACwAAAAAgAAAMgAAAABAAAA4AAAAAUAAADoAAAAAQAAABABAACQAgAAMAEAAKYB" + + "AACuAQAAxwEAAOwBAAAAAgAAFwIAACsCAAA/AgAAUwIAAGMCAABmAgAAagIAAG8CAAB4AgAAgAIA" + + "AIcCAAADAAAABAAAAAUAAAAGAAAABwAAAAkAAAAJAAAABQAAAAAAAAAKAAAABQAAAKABAAAEAAEA" + + "CwAAAAAAAAAAAAAAAAAAAA0AAAAAAAAADgAAAAEAAQAMAAAAAgAAAAAAAAAAAAAAAQAAAAIAAAAA" + + "AAAACAAAAAAAAAD+AgAAAAAAAAEAAQABAAAAjgEAAAQAAABwEAQAAAAOAAIAAAACAAAAkgEAAAgA" + + "AABiAAAAGgEBAG4gAwAQAA4AAgAAAAIAAACXAQAACwAAAGIAAAAaAQIAbiADABAAcQABAAAADgAC" + + "AA4ACAAOeAAEAA54PAAAAAABAAAAAwAGPGluaXQ+ABdHb29kYnllIGZyb20gVGVzdENsYXNzIQAj" + + "SGVsbG8gZnJvbSBUZXN0Q2xhc3Mgc2F5SGkgZnVuY3Rpb24AEkxmb29iYXIvVGVzdENsYXNzOwAV" + + "TGphdmEvaW8vUHJpbnRTdHJlYW07ABJMamF2YS9sYW5nL09iamVjdDsAEkxqYXZhL2xhbmcvU3Ry" + + "aW5nOwASTGphdmEvbGFuZy9TeXN0ZW07AA5UZXN0Q2xhc3MuamF2YQABVgACVkwAA291dAAHcHJp" + + "bnRsbgAGc2F5QnllAAVzYXlIaQB1fn5EOHsiY29tcGlsYXRpb24tbW9kZSI6ImRlYnVnIiwibWlu" + + "LWFwaSI6MSwic2hhLTEiOiJkMzI4MmI4ZjU0N2MyMzRjNGU0YzkzMDljMzZjNzk1YTI5ODU2ZWFi" + + "IiwidmVyc2lvbiI6IjEuNi4xLWRldiJ9AAAAAwAAgYAEsAIBCMgCAQnoAgAAAAAOAAAAAAAAAAEA" + + "AAAAAAAAAQAAABAAAABwAAAAAgAAAAYAAACwAAAAAwAAAAIAAADIAAAABAAAAAEAAADgAAAABQAA" + + "AAUAAADoAAAABgAAAAEAAAAQAQAAASAAAAMAAAAwAQAAAyAAAAMAAACOAQAAARAAAAEAAACgAQAA" + + "AiAAABAAAACmAQAAACAAAAEAAAD+AgAAAxAAAAEAAAAQAwAAABAAAAEAAAAUAwAA"); + /** + * base64 encoded class/dex file for + * package foobar; + * public class TestClass { + * public static void sayHi() { + * System.out.println("Hello again from TestClass sayHi function"); + * NewClass.sayHi(); + * } + * static void sayBye() { + * System.out.println("Goodbye again from TestClass!"); + * } + * } + */ + private static byte[] REDEF_CLASS_BYTES = Base64.getDecoder().decode( + "yv66vgAAADUAIwoACAARCQASABMIABQKABUAFgoAFwAYCAAZBwAaBwAbAQAGPGluaXQ+AQADKClW" + + "AQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEABXNheUhpAQAGc2F5QnllAQAKU291cmNlRmlsZQEA" + + "DlRlc3RDbGFzcy5qYXZhDAAJAAoHABwMAB0AHgEAKUhlbGxvIGFnYWluIGZyb20gVGVzdENsYXNz" + + "IHNheUhpIGZ1bmN0aW9uBwAfDAAgACEHACIMAA0ACgEAHUdvb2RieWUgYWdhaW4gZnJvbSBUZXN0" + + "Q2xhc3MhAQAQZm9vYmFyL1Rlc3RDbGFzcwEAEGphdmEvbGFuZy9PYmplY3QBABBqYXZhL2xhbmcv" + + "U3lzdGVtAQADb3V0AQAVTGphdmEvaW8vUHJpbnRTdHJlYW07AQATamF2YS9pby9QcmludFN0cmVh" + + "bQEAB3ByaW50bG4BABUoTGphdmEvbGFuZy9TdHJpbmc7KVYBAA9mb29iYXIvTmV3Q2xhc3MAIQAH" + + "AAgAAAAAAAMAAQAJAAoAAQALAAAAHQABAAEAAAAFKrcAAbEAAAABAAwAAAAGAAEAAAACAAkADQAK" + + "AAEACwAAACwAAgAAAAAADLIAAhIDtgAEuAAFsQAAAAEADAAAAA4AAwAAAAQACAAFAAsABgAIAA4A" + + "CgABAAsAAAAlAAIAAAAAAAmyAAISBrYABLEAAAABAAwAAAAKAAIAAAAIAAgACQABAA8AAAACABA="); + + private static byte[] REDEF_DEX_BYTES = Base64.getDecoder().decode( + "ZGV4CjAzNQA2plEeYRH4vl6wJgnAZOVcZ537QN9NXB3wAwAAcAAAAHhWNBIAAAAAAAAAAEQDAAAR" + + "AAAAcAAAAAcAAAC0AAAAAgAAANAAAAABAAAA6AAAAAYAAADwAAAAAQAAACABAACwAgAAQAEAALYB" + + "AAC+AQAA3QEAAAgCAAAbAgAALwIAAEYCAABaAgAAbgIAAIICAACSAgAAlQIAAJkCAACeAgAApwIA" + + "AK8CAAC2AgAAAwAAAAQAAAAFAAAABgAAAAcAAAAIAAAACgAAAAoAAAAGAAAAAAAAAAsAAAAGAAAA" + + "sAEAAAUAAgAMAAAAAAAAAA8AAAABAAAAAAAAAAEAAAAOAAAAAQAAAA8AAAACAAEADQAAAAMAAAAA" + + "AAAAAQAAAAEAAAADAAAAAAAAAAkAAAAAAAAALQMAAAAAAAABAAEAAQAAAJ4BAAAEAAAAcBAFAAAA" + + "DgACAAAAAgAAAKIBAAAIAAAAYgAAABoBAQBuIAQAEAAOAAIAAAACAAAApwEAAAsAAABiAAAAGgEC" + + "AG4gBAAQAHEAAAAAAA4AAgAOAAgADngABAAOeDwAAAAAAQAAAAQABjxpbml0PgAdR29vZGJ5ZSBh" + + "Z2FpbiBmcm9tIFRlc3RDbGFzcyEAKUhlbGxvIGFnYWluIGZyb20gVGVzdENsYXNzIHNheUhpIGZ1" + + "bmN0aW9uABFMZm9vYmFyL05ld0NsYXNzOwASTGZvb2Jhci9UZXN0Q2xhc3M7ABVMamF2YS9pby9Q" + + "cmludFN0cmVhbTsAEkxqYXZhL2xhbmcvT2JqZWN0OwASTGphdmEvbGFuZy9TdHJpbmc7ABJMamF2" + + "YS9sYW5nL1N5c3RlbTsADlRlc3RDbGFzcy5qYXZhAAFWAAJWTAADb3V0AAdwcmludGxuAAZzYXlC" + + "eWUABXNheUhpAHV+fkQ4eyJjb21waWxhdGlvbi1tb2RlIjoiZGVidWciLCJtaW4tYXBpIjoxLCJz" + + "aGEtMSI6ImQzMjgyYjhmNTQ3YzIzNGM0ZTRjOTMwOWMzNmM3OTVhMjk4NTZlYWIiLCJ2ZXJzaW9u" + + "IjoiMS42LjEtZGV2In0AAAADAAGBgATAAgEI2AIBCfgCAAAAAAAOAAAAAAAAAAEAAAAAAAAAAQAA" + + "ABEAAABwAAAAAgAAAAcAAAC0AAAAAwAAAAIAAADQAAAABAAAAAEAAADoAAAABQAAAAYAAADwAAAA" + + "BgAAAAEAAAAgAQAAASAAAAMAAABAAQAAAyAAAAMAAACeAQAAARAAAAEAAACwAQAAAiAAABEAAAC2" + + "AQAAACAAAAEAAAAtAwAAAxAAAAEAAABAAwAAABAAAAEAAABEAwAA"); + + public static void SafePrintCause(Throwable t) { + StackTraceElement cause = t.getStackTrace()[0]; + System.out.println(" --- " + t.getClass().getName() + " At " + cause.getClassName() + "." + + cause.getMethodName() + "(" + cause.getFileName() + ":" + + cause.getLineNumber() + ")"); + } + + public static void run() throws Exception { + System.out.println(" - Run while adding new referenced class."); + try { + run(true); + } catch (Exception e) { + // Unfortunately art and RI have different messages here so just return the type. + System.out.println(" -- Exception caught when running test with new class added! " + + e.getCause().getClass().getName()); + SafePrintCause(e.getCause()); + System.out.println(e); + e.printStackTrace(); + } + System.out.println(" - Run without adding new referenced class."); + try { + run(false); + } catch (Exception e) { + // Unfortunately art and RI have different messages here so just return the type. + System.out.println(" -- Exception caught when running test without new class added! " + + e.getCause().getClass().getName()); + SafePrintCause(e.getCause()); + } + } + + public static void run(boolean add_new) throws Exception { + ClassLoader cl = getClassLoader(); + Class<?> target = cl.loadClass(TEST_CLASS_NAME); + Method sayHi = target.getDeclaredMethod("sayHi"); + System.out.println(" -- Running sayHi before redefinition"); + sayHi.invoke(null); + if (add_new) { + System.out.println(" -- Adding NewClass to classloader!"); + addToClassLoader(cl); + } + System.out.println(" -- Redefine the TestClass"); + Redefinition.doCommonClassRedefinition(target, REDEF_CLASS_BYTES, REDEF_DEX_BYTES); + System.out.println(" -- call TestClass again, now with NewClass refs"); + sayHi.invoke(null); + } + + public static class ExtensibleClassLoader extends URLClassLoader { + public ExtensibleClassLoader() { + // Initially we don't have any URLs + super(new URL[] {}, ExtensibleClassLoader.class.getClassLoader()); + } + + public void addSingleUrl(String file) throws Exception { + this.addURL(new URL("file://" + file)); + } + + protected Class<?> findClass(String name) throws ClassNotFoundException { + // Just define the TestClass without other jars. + if (name.equals(TEST_CLASS_NAME)) { + return this.defineClass(TEST_CLASS_NAME, CLASS_BYTES, 0, CLASS_BYTES.length); + } else { + return super.findClass(name); + } + } + } + + public static ClassLoader getClassLoader() throws Exception { + if (!IS_ART) { + return new ExtensibleClassLoader(); + } else { + Class<?> class_loader_class = Class.forName("dalvik.system.InMemoryDexClassLoader"); + Constructor<?> ctor = class_loader_class.getConstructor(ByteBuffer.class, ClassLoader.class); + return (ClassLoader)ctor.newInstance(ByteBuffer.wrap(DEX_BYTES), Main.class.getClassLoader()); + } + } + + public static void addToClassLoader(ClassLoader cl) throws Exception { + if (IS_ART) { + addToClassLoaderNative(cl, System.getenv("DEX_LOCATION") + "/" + TEST_NAME + "-ex.jar"); + } else { + ((ExtensibleClassLoader)cl).addSingleUrl(System.getenv("DEX_LOCATION") + "/classes-ex/"); + } + } + + public static native void addToClassLoaderNative(ClassLoader loader, String segment); + public static void main(String[] args) throws Exception { + run(); + } +} diff --git a/test/1964-add-to-dex-classloader-file/src/art/Breakpoint.java b/test/1964-add-to-dex-classloader-file/src/art/Breakpoint.java new file mode 120000 index 0000000000..3673916cc6 --- /dev/null +++ b/test/1964-add-to-dex-classloader-file/src/art/Breakpoint.java @@ -0,0 +1 @@ +../../../jvmti-common/Breakpoint.java
\ No newline at end of file diff --git a/test/1964-add-to-dex-classloader-file/src/art/Redefinition.java b/test/1964-add-to-dex-classloader-file/src/art/Redefinition.java new file mode 120000 index 0000000000..81eaf31bbb --- /dev/null +++ b/test/1964-add-to-dex-classloader-file/src/art/Redefinition.java @@ -0,0 +1 @@ +../../../jvmti-common/Redefinition.java
\ No newline at end of file diff --git a/test/1964-add-to-dex-classloader-file/src/art/StackTrace.java b/test/1964-add-to-dex-classloader-file/src/art/StackTrace.java new file mode 120000 index 0000000000..e1a08aadbd --- /dev/null +++ b/test/1964-add-to-dex-classloader-file/src/art/StackTrace.java @@ -0,0 +1 @@ +../../../jvmti-common/StackTrace.java
\ No newline at end of file diff --git a/test/1964-add-to-dex-classloader-file/src/art/Suspension.java b/test/1964-add-to-dex-classloader-file/src/art/Suspension.java new file mode 120000 index 0000000000..bcef96f69d --- /dev/null +++ b/test/1964-add-to-dex-classloader-file/src/art/Suspension.java @@ -0,0 +1 @@ +../../../jvmti-common/Suspension.java
\ No newline at end of file diff --git a/test/Android.bp b/test/Android.bp index 5ebca660b9..7f25a6a02c 100644 --- a/test/Android.bp +++ b/test/Android.bp @@ -294,6 +294,7 @@ art_cc_defaults { "1953-pop-frame/pop_frame.cc", "1957-error-ext/lasterror.cc", "1962-multi-thread-events/multi_thread_events.cc", + "1963-add-to-dex-classloader-in-memory/add_to_loader.cc", ], // Use NDK-compatible headers for ctstiagent. header_libs: [ @@ -326,6 +327,7 @@ art_cc_defaults { // "1952-pop-frame-jit/pop_frame.cc", "1959-redefine-object-instrument/fake_redef_object.cc", "1960-obsolete-jit-multithread-native/native_say_hi.cc", + "1964-add-to-dex-classloader-file/add_to_loader.cc", ], static_libs: [ "libz", |