diff options
-rw-r--r-- | runtime/openjdkjvmti/ti_class_loader.cc | 28 | ||||
-rw-r--r-- | runtime/openjdkjvmti/ti_class_loader.h | 5 | ||||
-rw-r--r-- | runtime/openjdkjvmti/ti_redefine.cc | 61 | ||||
-rw-r--r-- | runtime/openjdkjvmti/ti_redefine.h | 7 | ||||
-rwxr-xr-x | test/944-transform-classloaders/build | 17 | ||||
-rw-r--r-- | test/944-transform-classloaders/classloader.cc | 44 | ||||
-rw-r--r-- | test/944-transform-classloaders/expected.txt | 5 | ||||
-rw-r--r-- | test/944-transform-classloaders/info.txt | 7 | ||||
-rwxr-xr-x | test/944-transform-classloaders/run | 17 | ||||
-rw-r--r-- | test/944-transform-classloaders/src/CommonClassDefinition.java | 27 | ||||
-rw-r--r-- | test/944-transform-classloaders/src/Main.java | 265 | ||||
-rw-r--r-- | test/944-transform-classloaders/src/Transform.java | 28 | ||||
-rw-r--r-- | test/944-transform-classloaders/src/Transform2.java | 21 | ||||
-rw-r--r-- | test/Android.bp | 1 | ||||
-rw-r--r-- | test/ti-agent/common_load.cc | 1 |
15 files changed, 516 insertions, 18 deletions
diff --git a/runtime/openjdkjvmti/ti_class_loader.cc b/runtime/openjdkjvmti/ti_class_loader.cc index c2f17924da..afec0bfac0 100644 --- a/runtime/openjdkjvmti/ti_class_loader.cc +++ b/runtime/openjdkjvmti/ti_class_loader.cc @@ -62,7 +62,7 @@ bool ClassLoaderHelper::AddToClassLoader(art::Thread* self, art::Handle<art::mirror::ClassLoader> loader, const art::DexFile* dex_file) { art::ScopedObjectAccessUnchecked soa(self); - art::StackHandleScope<2> hs(self); + art::StackHandleScope<3> hs(self); if (art::ClassLinker::IsBootClassLoader(soa, loader.Get())) { art::Runtime::Current()->GetClassLinker()->AppendToBootClassPath(self, *dex_file); return true; @@ -72,8 +72,9 @@ bool ClassLoaderHelper::AddToClassLoader(art::Thread* self, if (java_dex_file_obj.IsNull()) { return false; } + art::Handle<art::mirror::LongArray> old_cookie(hs.NewHandle(GetDexFileCookie(java_dex_file_obj))); art::Handle<art::mirror::LongArray> cookie(hs.NewHandle( - AllocateNewDexFileCookie(self, java_dex_file_obj, dex_file))); + AllocateNewDexFileCookie(self, old_cookie, dex_file))); if (cookie.IsNull()) { return false; } @@ -99,12 +100,8 @@ void ClassLoaderHelper::UpdateJavaDexFile(art::ObjPtr<art::mirror::Object> java_ } } -// TODO Really wishing I had that mirror of java.lang.DexFile now. -art::ObjPtr<art::mirror::LongArray> ClassLoaderHelper::AllocateNewDexFileCookie( - art::Thread* self, - art::Handle<art::mirror::Object> java_dex_file_obj, - const art::DexFile* dex_file) { - art::StackHandleScope<2> hs(self); +art::ObjPtr<art::mirror::LongArray> ClassLoaderHelper::GetDexFileCookie( + art::Handle<art::mirror::Object> java_dex_file_obj) { // mCookie is nulled out if the DexFile has been closed but mInternalCookie sticks around until // the object is finalized. Since they always point to the same array if mCookie is not null we // just use the mInternalCookie field. We will update one or both of these fields later. @@ -113,9 +110,15 @@ art::ObjPtr<art::mirror::LongArray> ClassLoaderHelper::AllocateNewDexFileCookie( "mInternalCookie", "Ljava/lang/Object;"); // TODO Add check that mCookie is either null or same as mInternalCookie CHECK(internal_cookie_field != nullptr); - art::Handle<art::mirror::LongArray> cookie( - hs.NewHandle(internal_cookie_field->GetObject(java_dex_file_obj.Get())->AsLongArray())); - // TODO Maybe make these non-fatal. + return internal_cookie_field->GetObject(java_dex_file_obj.Get())->AsLongArray(); +} + +// TODO Really wishing I had that mirror of java.lang.DexFile now. +art::ObjPtr<art::mirror::LongArray> ClassLoaderHelper::AllocateNewDexFileCookie( + art::Thread* self, + art::Handle<art::mirror::LongArray> cookie, + const art::DexFile* dex_file) { + art::StackHandleScope<1> hs(self); CHECK(cookie.Get() != nullptr); CHECK_GE(cookie->GetLength(), 1); art::Handle<art::mirror::LongArray> new_cookie( @@ -128,8 +131,9 @@ art::ObjPtr<art::mirror::LongArray> ClassLoaderHelper::AllocateNewDexFileCookie( // TODO Should I clear this field? // TODO This is a really crappy thing here with the first element being different. new_cookie->SetWithoutChecks<false>(0, cookie->GetWithoutChecks(0)); + // This must match the casts in runtime/native/dalvik_system_DexFile.cc:ConvertDexFilesToJavaArray new_cookie->SetWithoutChecks<false>( - 1, static_cast<int64_t>(reinterpret_cast<intptr_t>(dex_file))); + 1, static_cast<int64_t>(reinterpret_cast<uintptr_t>(dex_file))); new_cookie->Memcpy(2, cookie.Get(), 1, cookie->GetLength() - 1); return new_cookie.Get(); } diff --git a/runtime/openjdkjvmti/ti_class_loader.h b/runtime/openjdkjvmti/ti_class_loader.h index 17ed0eb196..1ac49886cb 100644 --- a/runtime/openjdkjvmti/ti_class_loader.h +++ b/runtime/openjdkjvmti/ti_class_loader.h @@ -82,9 +82,12 @@ class ClassLoaderHelper { art::Thread* self, art::Handle<art::mirror::ClassLoader> loader) REQUIRES_SHARED(art::Locks::mutator_lock_); + static art::ObjPtr<art::mirror::LongArray> GetDexFileCookie( + art::Handle<art::mirror::Object> java_dex_file) REQUIRES_SHARED(art::Locks::mutator_lock_); + static art::ObjPtr<art::mirror::LongArray> AllocateNewDexFileCookie( art::Thread* self, - art::Handle<art::mirror::Object> java_dex_file, + art::Handle<art::mirror::LongArray> old_dex_file_cookie, const art::DexFile* new_dex_file) REQUIRES_SHARED(art::Locks::mutator_lock_); static void UpdateJavaDexFile(art::ObjPtr<art::mirror::Object> java_dex_file, diff --git a/runtime/openjdkjvmti/ti_redefine.cc b/runtime/openjdkjvmti/ti_redefine.cc index 4b8108accf..eb4c2f9f21 100644 --- a/runtime/openjdkjvmti/ti_redefine.cc +++ b/runtime/openjdkjvmti/ti_redefine.cc @@ -701,6 +701,60 @@ class RedefinitionDataHolder { DISALLOW_COPY_AND_ASSIGN(RedefinitionDataHolder); }; +// Looks through the previously allocated cookies to see if we need to update them with another new +// dexfile. This is so that even if multiple classes with the same classloader are redefined at +// once they are all added to the classloader. +bool Redefiner::ClassRedefinition::AllocateAndRememberNewDexFileCookie( + int32_t klass_index, + art::Handle<art::mirror::ClassLoader> source_class_loader, + art::Handle<art::mirror::Object> dex_file_obj, + /*out*/RedefinitionDataHolder* holder) { + art::StackHandleScope<2> hs(driver_->self_); + art::MutableHandle<art::mirror::LongArray> old_cookie( + hs.NewHandle<art::mirror::LongArray>(nullptr)); + bool has_older_cookie = false; + // See if we already have a cookie that a previous redefinition got from the same classloader. + for (int32_t i = 0; i < klass_index; i++) { + if (holder->GetSourceClassLoader(i) == source_class_loader.Get()) { + // Since every instance of this classloader should have the same cookie associated with it we + // can stop looking here. + has_older_cookie = true; + old_cookie.Assign(holder->GetNewDexFileCookie(i)); + break; + } + } + if (old_cookie.IsNull()) { + // No older cookie. Get it directly from the dex_file_obj + // We should not have seen this classloader elsewhere. + CHECK(!has_older_cookie); + old_cookie.Assign(ClassLoaderHelper::GetDexFileCookie(dex_file_obj)); + } + // Use the old cookie to generate the new one with the new DexFile* added in. + art::Handle<art::mirror::LongArray> + new_cookie(hs.NewHandle(ClassLoaderHelper::AllocateNewDexFileCookie(driver_->self_, + old_cookie, + dex_file_.get()))); + // Make sure the allocation worked. + if (new_cookie.IsNull()) { + return false; + } + + // Save the cookie. + holder->SetNewDexFileCookie(klass_index, new_cookie.Get()); + // If there are other copies of this same classloader we need to make sure that we all have the + // same cookie. + if (has_older_cookie) { + for (int32_t i = 0; i < klass_index; i++) { + // We will let the GC take care of the cookie we allocated for this one. + if (holder->GetSourceClassLoader(i) == source_class_loader.Get()) { + holder->SetNewDexFileCookie(i, new_cookie.Get()); + } + } + } + + return true; +} + bool Redefiner::ClassRedefinition::FinishRemainingAllocations( int32_t klass_index, /*out*/RedefinitionDataHolder* holder) { art::ScopedObjectAccessUnchecked soa(driver_->self_); @@ -719,11 +773,8 @@ bool Redefiner::ClassRedefinition::FinishRemainingAllocations( RecordFailure(ERR(INTERNAL), "Unable to find dex file!"); return false; } - holder->SetNewDexFileCookie(klass_index, - ClassLoaderHelper::AllocateNewDexFileCookie(driver_->self_, - dex_file_obj, - dex_file_.get()).Ptr()); - if (holder->GetNewDexFileCookie(klass_index) == nullptr) { + // Allocate the new dex file cookie. + if (!AllocateAndRememberNewDexFileCookie(klass_index, loader, dex_file_obj, holder)) { driver_->self_->AssertPendingOOMException(); driver_->self_->ClearException(); RecordFailure(ERR(OUT_OF_MEMORY), "Unable to allocate dex file array for class loader"); diff --git a/runtime/openjdkjvmti/ti_redefine.h b/runtime/openjdkjvmti/ti_redefine.h index 5bcaef8971..5aa7dde55c 100644 --- a/runtime/openjdkjvmti/ti_redefine.h +++ b/runtime/openjdkjvmti/ti_redefine.h @@ -145,6 +145,13 @@ class Redefiner { bool FinishRemainingAllocations(int32_t klass_index, /*out*/RedefinitionDataHolder* holder) REQUIRES_SHARED(art::Locks::mutator_lock_); + bool AllocateAndRememberNewDexFileCookie( + int32_t klass_index, + art::Handle<art::mirror::ClassLoader> source_class_loader, + art::Handle<art::mirror::Object> dex_file_obj, + /*out*/RedefinitionDataHolder* holder) + REQUIRES_SHARED(art::Locks::mutator_lock_); + void FindAndAllocateObsoleteMethods(art::mirror::Class* art_klass) REQUIRES(art::Locks::mutator_lock_); diff --git a/test/944-transform-classloaders/build b/test/944-transform-classloaders/build new file mode 100755 index 0000000000..898e2e54a2 --- /dev/null +++ b/test/944-transform-classloaders/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/944-transform-classloaders/classloader.cc b/test/944-transform-classloaders/classloader.cc new file mode 100644 index 0000000000..5fbd8e11c9 --- /dev/null +++ b/test/944-transform-classloaders/classloader.cc @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "base/macros.h" +#include "jni.h" +#include "mirror/class-inl.h" +#include "openjdkjvmti/jvmti.h" +#include "ScopedLocalRef.h" + +#include "ti-agent/common_helper.h" +#include "ti-agent/common_load.h" + +namespace art { +namespace Test944TransformClassloaders { + + +extern "C" JNIEXPORT jlong JNICALL Java_Main_getDexFilePointer(JNIEnv* env, jclass, jclass klass) { + if (Runtime::Current() == nullptr) { + env->ThrowNew(env->FindClass("java/lang/Exception"), + "We do not seem to be running in ART! Unable to get dex file."); + return 0; + } + ScopedObjectAccess soa(env); + // This sequence of casts must be the same as those done in + // runtime/native/dalvik_system_DexFile.cc in order to ensure that we get the same results. + return static_cast<jlong>(reinterpret_cast<uintptr_t>( + &soa.Decode<mirror::Class>(klass)->GetDexFile())); +} + +} // namespace Test944TransformClassloaders +} // namespace art diff --git a/test/944-transform-classloaders/expected.txt b/test/944-transform-classloaders/expected.txt new file mode 100644 index 0000000000..79522479dd --- /dev/null +++ b/test/944-transform-classloaders/expected.txt @@ -0,0 +1,5 @@ +hello +hello2 +Goodbye +Goodbye2 +Passed diff --git a/test/944-transform-classloaders/info.txt b/test/944-transform-classloaders/info.txt new file mode 100644 index 0000000000..9155564d62 --- /dev/null +++ b/test/944-transform-classloaders/info.txt @@ -0,0 +1,7 @@ +Tests that redefined dex files are stored in the appropriate classloader. + +This test cannot run on the RI. + +We use reflection with setAccessible(true) to examine the private internals of +classloaders. Changes to the internal operation or definition of +dalvik.system.BaseDexClassLoader might cause this test to fail. diff --git a/test/944-transform-classloaders/run b/test/944-transform-classloaders/run new file mode 100755 index 0000000000..c6e62ae6cd --- /dev/null +++ b/test/944-transform-classloaders/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/944-transform-classloaders/src/CommonClassDefinition.java b/test/944-transform-classloaders/src/CommonClassDefinition.java new file mode 100644 index 0000000000..62602a02e9 --- /dev/null +++ b/test/944-transform-classloaders/src/CommonClassDefinition.java @@ -0,0 +1,27 @@ +/* + * 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. + */ + +class CommonClassDefinition { + public final Class<?> target; + public final byte[] class_file_bytes; + public final byte[] dex_file_bytes; + + CommonClassDefinition(Class<?> target, byte[] class_file_bytes, byte[] dex_file_bytes) { + this.target = target; + this.class_file_bytes = class_file_bytes; + this.dex_file_bytes = dex_file_bytes; + } +} diff --git a/test/944-transform-classloaders/src/Main.java b/test/944-transform-classloaders/src/Main.java new file mode 100644 index 0000000000..4911e00a70 --- /dev/null +++ b/test/944-transform-classloaders/src/Main.java @@ -0,0 +1,265 @@ +/* + * 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. + */ + +import java.util.Arrays; +import java.util.ArrayList; +import java.util.Base64; +import java.lang.reflect.*; +public class Main { + + /** + * base64 encoded class/dex file for + * class Transform { + * public void sayHi() { + * System.out.println("Goodbye"); + * } + * } + */ + private static CommonClassDefinition TRANSFORM_DEFINITION = new CommonClassDefinition( + Transform.class, + Base64.getDecoder().decode( + "yv66vgAAADQAHAoABgAOCQAPABAIABEKABIAEwcAFAcAFQEABjxpbml0PgEAAygpVgEABENvZGUB" + + "AA9MaW5lTnVtYmVyVGFibGUBAAVzYXlIaQEAClNvdXJjZUZpbGUBAA5UcmFuc2Zvcm0uamF2YQwA" + + "BwAIBwAWDAAXABgBAAdHb29kYnllBwAZDAAaABsBAAlUcmFuc2Zvcm0BABBqYXZhL2xhbmcvT2Jq" + + "ZWN0AQAQamF2YS9sYW5nL1N5c3RlbQEAA291dAEAFUxqYXZhL2lvL1ByaW50U3RyZWFtOwEAE2ph" + + "dmEvaW8vUHJpbnRTdHJlYW0BAAdwcmludGxuAQAVKExqYXZhL2xhbmcvU3RyaW5nOylWACAABQAG" + + "AAAAAAACAAAABwAIAAEACQAAAB0AAQABAAAABSq3AAGxAAAAAQAKAAAABgABAAAAEQABAAsACAAB" + + "AAkAAAAlAAIAAQAAAAmyAAISA7YABLEAAAABAAoAAAAKAAIAAAATAAgAFAABAAwAAAACAA0="), + Base64.getDecoder().decode( + "ZGV4CjAzNQCLXSBQ5FiS3f16krSYZFF8xYZtFVp0GRXMAgAAcAAAAHhWNBIAAAAAAAAAACwCAAAO" + + "AAAAcAAAAAYAAACoAAAAAgAAAMAAAAABAAAA2AAAAAQAAADgAAAAAQAAAAABAACsAQAAIAEAAGIB" + + "AABqAQAAcwEAAIABAACXAQAAqwEAAL8BAADTAQAA4wEAAOYBAADqAQAA/gEAAAMCAAAMAgAAAgAA" + + "AAMAAAAEAAAABQAAAAYAAAAIAAAACAAAAAUAAAAAAAAACQAAAAUAAABcAQAABAABAAsAAAAAAAAA" + + "AAAAAAAAAAANAAAAAQABAAwAAAACAAAAAAAAAAAAAAAAAAAAAgAAAAAAAAAHAAAAAAAAAB4CAAAA" + + "AAAAAQABAAEAAAATAgAABAAAAHAQAwAAAA4AAwABAAIAAAAYAgAACQAAAGIAAAAbAQEAAABuIAIA" + + "EAAOAAAAAQAAAAMABjxpbml0PgAHR29vZGJ5ZQALTFRyYW5zZm9ybTsAFUxqYXZhL2lvL1ByaW50" + + "U3RyZWFtOwASTGphdmEvbGFuZy9PYmplY3Q7ABJMamF2YS9sYW5nL1N0cmluZzsAEkxqYXZhL2xh" + + "bmcvU3lzdGVtOwAOVHJhbnNmb3JtLmphdmEAAVYAAlZMABJlbWl0dGVyOiBqYWNrLTMuMzYAA291" + + "dAAHcHJpbnRsbgAFc2F5SGkAEQAHDgATAAcOhQAAAAEBAICABKACAQG4Ag0AAAAAAAAAAQAAAAAA" + + "AAABAAAADgAAAHAAAAACAAAABgAAAKgAAAADAAAAAgAAAMAAAAAEAAAAAQAAANgAAAAFAAAABAAA" + + "AOAAAAAGAAAAAQAAAAABAAABIAAAAgAAACABAAABEAAAAQAAAFwBAAACIAAADgAAAGIBAAADIAAA" + + "AgAAABMCAAAAIAAAAQAAAB4CAAAAEAAAAQAAACwCAAA=")); + + /** + * base64 encoded class/dex file for + * class Transform2 { + * public void sayHi() { + * System.out.println("Goodbye2"); + * } + * } + */ + private static CommonClassDefinition TRANSFORM2_DEFINITION = new CommonClassDefinition( + Transform2.class, + Base64.getDecoder().decode( + "yv66vgAAADQAHAoABgAOCQAPABAIABEKABIAEwcAFAcAFQEABjxpbml0PgEAAygpVgEABENvZGUB" + + "AA9MaW5lTnVtYmVyVGFibGUBAAVzYXlIaQEAClNvdXJjZUZpbGUBAA9UcmFuc2Zvcm0yLmphdmEM" + + "AAcACAcAFgwAFwAYAQAIR29vZGJ5ZTIHABkMABoAGwEAClRyYW5zZm9ybTIBABBqYXZhL2xhbmcv" + + "T2JqZWN0AQAQamF2YS9sYW5nL1N5c3RlbQEAA291dAEAFUxqYXZhL2lvL1ByaW50U3RyZWFtOwEA" + + "E2phdmEvaW8vUHJpbnRTdHJlYW0BAAdwcmludGxuAQAVKExqYXZhL2xhbmcvU3RyaW5nOylWACAA" + + "BQAGAAAAAAACAAAABwAIAAEACQAAAB0AAQABAAAABSq3AAGxAAAAAQAKAAAABgABAAAAAQABAAsA" + + "CAABAAkAAAAlAAIAAQAAAAmyAAISA7YABLEAAAABAAoAAAAKAAIAAAADAAgABAABAAwAAAACAA0="), + Base64.getDecoder().decode( + "ZGV4CjAzNQABX6vL8OT7aGLjbzFBEfCM9Aaz+zzGzVnQAgAAcAAAAHhWNBIAAAAAAAAAADACAAAO" + + "AAAAcAAAAAYAAACoAAAAAgAAAMAAAAABAAAA2AAAAAQAAADgAAAAAQAAAAABAACwAQAAIAEAAGIB" + + "AABqAQAAdAEAAIIBAACZAQAArQEAAMEBAADVAQAA5gEAAOkBAADtAQAAAQIAAAYCAAAPAgAAAgAA" + + "AAMAAAAEAAAABQAAAAYAAAAIAAAACAAAAAUAAAAAAAAACQAAAAUAAABcAQAABAABAAsAAAAAAAAA" + + "AAAAAAAAAAANAAAAAQABAAwAAAACAAAAAAAAAAAAAAAAAAAAAgAAAAAAAAAHAAAAAAAAACECAAAA" + + "AAAAAQABAAEAAAAWAgAABAAAAHAQAwAAAA4AAwABAAIAAAAbAgAACQAAAGIAAAAbAQEAAABuIAIA" + + "EAAOAAAAAQAAAAMABjxpbml0PgAIR29vZGJ5ZTIADExUcmFuc2Zvcm0yOwAVTGphdmEvaW8vUHJp" + + "bnRTdHJlYW07ABJMamF2YS9sYW5nL09iamVjdDsAEkxqYXZhL2xhbmcvU3RyaW5nOwASTGphdmEv" + + "bGFuZy9TeXN0ZW07AA9UcmFuc2Zvcm0yLmphdmEAAVYAAlZMABJlbWl0dGVyOiBqYWNrLTQuMjQA" + + "A291dAAHcHJpbnRsbgAFc2F5SGkAAQAHDgADAAcOhwAAAAEBAICABKACAQG4AgANAAAAAAAAAAEA" + + "AAAAAAAAAQAAAA4AAABwAAAAAgAAAAYAAACoAAAAAwAAAAIAAADAAAAABAAAAAEAAADYAAAABQAA" + + "AAQAAADgAAAABgAAAAEAAAAAAQAAASAAAAIAAAAgAQAAARAAAAEAAABcAQAAAiAAAA4AAABiAQAA" + + "AyAAAAIAAAAWAgAAACAAAAEAAAAhAgAAABAAAAEAAAAwAgAA")); + + public static void main(String[] args) throws Exception { + doTest(); + System.out.println("Passed"); + } + + private static void checkIsInstance(Class<?> klass, Object o) throws Exception { + if (!klass.isInstance(o)) { + throw new Exception(klass + " is not the class of " + o); + } + } + + private static boolean arrayContains(long[] arr, long value) { + if (arr == null) { + return false; + } + for (int i = 0; i < arr.length; i++) { + if (arr[i] == value) { + return true; + } + } + return false; + } + + /** + * Checks that we can find the dex-file for the given class in its classloader. + * + * Throws if it fails. + */ + private static void checkDexFileInClassLoader(Class<?> klass) throws Exception { + // If all the android BCP classes were availible when compiling this test and access checks + // weren't a thing this function would be written as follows: + // + // long dexFilePtr = getDexFilePointer(klass); + // dalvik.system.BaseDexClassLoader loader = + // (dalvik.system.BaseDexClassLoader)klass.getClassLoader(); + // dalvik.system.DexPathList pathListValue = loader.pathList; + // dalvik.system.DexPathList.Element[] elementArrayValue = pathListValue.dexElements; + // int array_length = elementArrayValue.length; + // for (int i = 0; i < array_length; i++) { + // dalvik.system.DexPathList.Element curElement = elementArrayValue[i]; + // dalvik.system.DexFile curDexFile = curElement.dexFile; + // if (curDexFile == null) { + // continue; + // } + // long[] curCookie = (long[])curDexFile.mCookie; + // long[] curInternalCookie = (long[])curDexFile.mInternalCookie; + // if (arrayContains(curCookie, dexFilePtr) || arrayContains(curInternalCookie, dexFilePtr)) { + // return; + // } + // } + // throw new Exception( + // "Unable to find dex file pointer " + dexFilePtr + " in class loader for " + klass); + + // Get all the fields and classes we need by reflection. + Class<?> baseDexClassLoaderClass = Class.forName("dalvik.system.BaseDexClassLoader"); + Field pathListField = baseDexClassLoaderClass.getDeclaredField("pathList"); + + Class<?> dexPathListClass = Class.forName("dalvik.system.DexPathList"); + Field elementArrayField = dexPathListClass.getDeclaredField("dexElements"); + + Class<?> dexPathListElementClass = Class.forName("dalvik.system.DexPathList$Element"); + Field dexFileField = dexPathListElementClass.getDeclaredField("dexFile"); + + Class<?> dexFileClass = Class.forName("dalvik.system.DexFile"); + Field dexFileCookieField = dexFileClass.getDeclaredField("mCookie"); + Field dexFileInternalCookieField = dexFileClass.getDeclaredField("mInternalCookie"); + + // Make all the fields accessible + AccessibleObject.setAccessible(new AccessibleObject[] { pathListField, + elementArrayField, + dexFileField, + dexFileCookieField, + dexFileInternalCookieField }, true); + + long dexFilePtr = getDexFilePointer(klass); + + ClassLoader loader = klass.getClassLoader(); + checkIsInstance(baseDexClassLoaderClass, loader); + // DexPathList pathListValue = ((BaseDexClassLoader) loader).pathList; + Object pathListValue = pathListField.get(loader); + + checkIsInstance(dexPathListClass, pathListValue); + + // DexPathList.Element[] elementArrayValue = pathListValue.dexElements; + Object elementArrayValue = elementArrayField.get(pathListValue); + if (!elementArrayValue.getClass().isArray() || + elementArrayValue.getClass().getComponentType() != dexPathListElementClass) { + throw new Exception("elementArrayValue is not an " + dexPathListElementClass + " array!"); + } + // int array_length = elementArrayValue.length; + int array_length = Array.getLength(elementArrayValue); + for (int i = 0; i < array_length; i++) { + // DexPathList.Element curElement = elementArrayValue[i]; + Object curElement = Array.get(elementArrayValue, i); + checkIsInstance(dexPathListElementClass, curElement); + + // DexFile curDexFile = curElement.dexFile; + Object curDexFile = dexFileField.get(curElement); + if (curDexFile == null) { + continue; + } + checkIsInstance(dexFileClass, curDexFile); + + // long[] curCookie = (long[])curDexFile.mCookie; + long[] curCookie = (long[])dexFileCookieField.get(curDexFile); + // long[] curInternalCookie = (long[])curDexFile.mInternalCookie; + long[] curInternalCookie = (long[])dexFileInternalCookieField.get(curDexFile); + + if (arrayContains(curCookie, dexFilePtr) || arrayContains(curInternalCookie, dexFilePtr)) { + return; + } + } + throw new Exception( + "Unable to find dex file pointer " + dexFilePtr + " in class loader for " + klass); + } + + private static void doTest() throws Exception { + Transform t = new Transform(); + Transform2 t2 = new Transform2(); + + long initial_t1_dex = getDexFilePointer(Transform.class); + long initial_t2_dex = getDexFilePointer(Transform2.class); + if (initial_t2_dex != initial_t1_dex) { + throw new Exception("The classes " + Transform.class + " and " + Transform2.class + " " + + "have different initial dex files!"); + } + checkDexFileInClassLoader(Transform.class); + checkDexFileInClassLoader(Transform2.class); + + // Make sure they are loaded + t.sayHi(); + t2.sayHi(); + // Redefine both of the classes. + doMultiClassRedefinition(TRANSFORM_DEFINITION, TRANSFORM2_DEFINITION); + // Make sure we actually transformed them! + t.sayHi(); + t2.sayHi(); + + long final_t1_dex = getDexFilePointer(Transform.class); + long final_t2_dex = getDexFilePointer(Transform2.class); + if (final_t2_dex == final_t1_dex) { + throw new Exception("The classes " + Transform.class + " and " + Transform2.class + " " + + "have the same initial dex files!"); + } else if (final_t1_dex == initial_t1_dex) { + throw new Exception("The class " + Transform.class + " did not get a new dex file!"); + } else if (final_t2_dex == initial_t2_dex) { + throw new Exception("The class " + Transform2.class + " did not get a new dex file!"); + } + // Check to make sure the new dex files are in the class loader. + checkDexFileInClassLoader(Transform.class); + checkDexFileInClassLoader(Transform2.class); + } + + private static void doMultiClassRedefinition(CommonClassDefinition... defs) { + ArrayList<Class<?>> classes = new ArrayList<>(); + ArrayList<byte[]> class_files = new ArrayList<>(); + ArrayList<byte[]> dex_files = new ArrayList<>(); + + for (CommonClassDefinition d : defs) { + classes.add(d.target); + class_files.add(d.class_file_bytes); + dex_files.add(d.dex_file_bytes); + } + doCommonMultiClassRedefinition(classes.toArray(new Class<?>[0]), + class_files.toArray(new byte[0][]), + dex_files.toArray(new byte[0][])); + } + + // Gets the 'long' (really a native pointer) that is stored in the ClassLoader representing the + // DexFile a class is loaded from. This is converted from the DexFile* in the same way it is done + // in runtime/native/dalvik_system_DexFile.cc + private static native long getDexFilePointer(Class<?> target); + // Transforms the classes + private static native void doCommonMultiClassRedefinition(Class<?>[] targets, + byte[][] classfiles, + byte[][] dexfiles); +} diff --git a/test/944-transform-classloaders/src/Transform.java b/test/944-transform-classloaders/src/Transform.java new file mode 100644 index 0000000000..8e8af355da --- /dev/null +++ b/test/944-transform-classloaders/src/Transform.java @@ -0,0 +1,28 @@ +/* + * 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() { + // 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"); + } +} diff --git a/test/944-transform-classloaders/src/Transform2.java b/test/944-transform-classloaders/src/Transform2.java new file mode 100644 index 0000000000..eb22842184 --- /dev/null +++ b/test/944-transform-classloaders/src/Transform2.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 Transform2 { + public void sayHi() { + System.out.println("hello2"); + } +} diff --git a/test/Android.bp b/test/Android.bp index 1070645040..d3244a683a 100644 --- a/test/Android.bp +++ b/test/Android.bp @@ -273,6 +273,7 @@ art_cc_defaults { "931-agent-thread/agent_thread.cc", "933-misc-events/misc_events.cc", "936-search-onload/search_onload.cc", + "944-transform-classloaders/classloader.cc", ], shared_libs: [ "libbase", diff --git a/test/ti-agent/common_load.cc b/test/ti-agent/common_load.cc index 008e2e5672..c5a93568c6 100644 --- a/test/ti-agent/common_load.cc +++ b/test/ti-agent/common_load.cc @@ -121,6 +121,7 @@ static AgentLib agents[] = { { "941-recursive-obsolete-jit", common_redefine::OnLoad, nullptr }, { "942-private-recursive", common_redefine::OnLoad, nullptr }, { "943-private-recursive-jit", common_redefine::OnLoad, nullptr }, + { "944-transform-classloaders", common_redefine::OnLoad, nullptr }, }; static AgentLib* FindAgent(char* name) { |