diff options
-rw-r--r-- | runtime/dex_file.cc | 28 | ||||
-rw-r--r-- | runtime/dex_file.h | 13 | ||||
-rw-r--r-- | runtime/dex_file_test.cc | 71 | ||||
-rw-r--r-- | runtime/dex_file_verifier_test.cc | 76 | ||||
-rw-r--r-- | runtime/openjdkjvmti/Android.bp | 3 | ||||
-rw-r--r-- | runtime/openjdkjvmti/OpenjdkJvmTi.cc | 75 | ||||
-rw-r--r-- | runtime/openjdkjvmti/transform.cc | 362 | ||||
-rw-r--r-- | runtime/openjdkjvmti/transform.h | 64 | ||||
-rw-r--r-- | runtime/utils.cc | 71 | ||||
-rw-r--r-- | runtime/utils.h | 2 | ||||
-rwxr-xr-x | test/902-hello-transformation/build | 17 | ||||
-rw-r--r-- | test/902-hello-transformation/expected.txt | 3 | ||||
-rw-r--r-- | test/902-hello-transformation/info.txt | 1 | ||||
-rwxr-xr-x | test/902-hello-transformation/run | 43 | ||||
-rw-r--r-- | test/902-hello-transformation/src/Main.java | 31 | ||||
-rw-r--r-- | test/902-hello-transformation/src/Transform.java | 21 | ||||
-rw-r--r-- | test/902-hello-transformation/transform.cc | 154 | ||||
-rw-r--r-- | test/902-hello-transformation/transform.h | 30 | ||||
-rw-r--r-- | test/Android.bp | 2 | ||||
-rwxr-xr-x | test/etc/run-test-jar | 3 | ||||
-rwxr-xr-x | test/run-test | 2 | ||||
-rw-r--r-- | test/ti-agent/common_load.cc | 2 |
22 files changed, 931 insertions, 143 deletions
diff --git a/runtime/dex_file.cc b/runtime/dex_file.cc index 70b7f877bc..409fbba66a 100644 --- a/runtime/dex_file.cc +++ b/runtime/dex_file.cc @@ -564,6 +564,34 @@ const DexFile::ClassDef* DexFile::FindClassDef(uint16_t type_idx) const { return nullptr; } +uint32_t DexFile::FindCodeItemOffset(const DexFile::ClassDef& class_def, + uint32_t method_idx) const { + const uint8_t* class_data = GetClassData(class_def); + CHECK(class_data != nullptr); + ClassDataItemIterator it(*this, class_data); + // Skip fields + while (it.HasNextStaticField()) { + it.Next(); + } + while (it.HasNextInstanceField()) { + it.Next(); + } + while (it.HasNextDirectMethod()) { + if (it.GetMemberIndex() == method_idx) { + return it.GetMethodCodeItemOffset(); + } + it.Next(); + } + while (it.HasNextVirtualMethod()) { + if (it.GetMemberIndex() == method_idx) { + return it.GetMethodCodeItemOffset(); + } + it.Next(); + } + LOG(FATAL) << "Unable to find method " << method_idx; + UNREACHABLE(); +} + const DexFile::FieldId* DexFile::FindFieldId(const DexFile::TypeId& declaring_klass, const DexFile::StringId& name, const DexFile::TypeId& type) const { diff --git a/runtime/dex_file.h b/runtime/dex_file.h index 14bde09ee4..28aeb1e490 100644 --- a/runtime/dex_file.h +++ b/runtime/dex_file.h @@ -590,6 +590,9 @@ class DexFile { const DexFile::StringId& name, const DexFile::TypeId& type) const; + uint32_t FindCodeItemOffset(const DexFile::ClassDef& class_def, + uint32_t dex_method_idx) const; + // Returns the declaring class descriptor string of a field id. const char* GetFieldDeclaringClassDescriptor(const FieldId& field_id) const { const DexFile::TypeId& type_id = GetTypeId(field_id.class_idx_); @@ -1060,6 +1063,16 @@ class DexFile { std::string* error_msg, VerifyResult* verify_result = nullptr); + + // Opens a .dex file at the given address, optionally backed by a MemMap + static std::unique_ptr<const DexFile> OpenMemory(const uint8_t* dex_file, + size_t size, + const std::string& location, + uint32_t location_checksum, + std::unique_ptr<MemMap> mem_map, + const OatDexFile* oat_dex_file, + std::string* error_msg); + DexFile(const uint8_t* base, size_t size, const std::string& location, diff --git a/runtime/dex_file_test.cc b/runtime/dex_file_test.cc index 6a06177bb6..3dffc40011 100644 --- a/runtime/dex_file_test.cc +++ b/runtime/dex_file_test.cc @@ -26,6 +26,7 @@ #include "os.h" #include "scoped_thread_state_change.h" #include "thread-inl.h" +#include "utils.h" namespace art { @@ -37,65 +38,13 @@ TEST_F(DexFileTest, Open) { ASSERT_TRUE(dex.get() != nullptr); } -static const uint8_t kBase64Map[256] = { - 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, - 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, - 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, - 255, 255, 255, 255, 255, 255, 255, 62, 255, 255, 255, 63, - 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 255, 255, - 255, 254, 255, 255, 255, 0, 1, 2, 3, 4, 5, 6, - 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, // NOLINT - 19, 20, 21, 22, 23, 24, 25, 255, 255, 255, 255, 255, // NOLINT - 255, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, - 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, // NOLINT - 49, 50, 51, 255, 255, 255, 255, 255, 255, 255, 255, 255, // NOLINT - 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, - 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, - 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, - 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, - 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, - 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, - 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, - 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, - 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, - 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, - 255, 255, 255, 255 -}; - -static inline std::vector<uint8_t> DecodeBase64(const char* src) { - std::vector<uint8_t> tmp; - uint32_t t = 0, y = 0; - int g = 3; - for (size_t i = 0; src[i] != '\0'; ++i) { - uint8_t c = kBase64Map[src[i] & 0xFF]; - if (c == 255) continue; - // the final = symbols are read and used to trim the remaining bytes - if (c == 254) { - c = 0; - // prevent g < 0 which would potentially allow an overflow later - if (--g < 0) { - return std::vector<uint8_t>(); - } - } else if (g != 3) { - // we only allow = to be at the end - return std::vector<uint8_t>(); - } - t = (t << 6) | c; - if (++y == 4) { - tmp.push_back((t >> 16) & 255); - if (g > 1) { - tmp.push_back((t >> 8) & 255); - } - if (g > 2) { - tmp.push_back(t & 255); - } - y = t = 0; - } - } - if (y != 0) { - return std::vector<uint8_t>(); - } - return tmp; +static inline std::vector<uint8_t> DecodeBase64Vec(const char* src) { + std::vector<uint8_t> res; + size_t size; + std::unique_ptr<uint8_t[]> data(DecodeBase64(src, &size)); + res.resize(size); + memcpy(res.data(), data.get(), size); + return res; } // Although this is the same content logically as the Nested test dex, @@ -166,7 +115,7 @@ static const char kRawDexZeroLength[] = static void DecodeAndWriteDexFile(const char* base64, const char* location) { // decode base64 CHECK(base64 != nullptr); - std::vector<uint8_t> dex_bytes = DecodeBase64(base64); + std::vector<uint8_t> dex_bytes = DecodeBase64Vec(base64); CHECK_NE(dex_bytes.size(), 0u); // write to provided file @@ -202,7 +151,7 @@ static std::unique_ptr<const DexFile> OpenDexFileInMemoryBase64(const char* base const char* location, uint32_t location_checksum) { CHECK(base64 != nullptr); - std::vector<uint8_t> dex_bytes = DecodeBase64(base64); + std::vector<uint8_t> dex_bytes = DecodeBase64Vec(base64); CHECK_NE(dex_bytes.size(), 0u); std::string error_message; diff --git a/runtime/dex_file_verifier_test.cc b/runtime/dex_file_verifier_test.cc index 5939ef3e95..c5a4d7534c 100644 --- a/runtime/dex_file_verifier_test.cc +++ b/runtime/dex_file_verifier_test.cc @@ -29,34 +29,10 @@ #include "leb128.h" #include "scoped_thread_state_change.h" #include "thread-inl.h" +#include "utils.h" namespace art { -static const uint8_t kBase64Map[256] = { - 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, - 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, - 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, - 255, 255, 255, 255, 255, 255, 255, 62, 255, 255, 255, 63, - 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 255, 255, - 255, 254, 255, 255, 255, 0, 1, 2, 3, 4, 5, 6, - 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, // NOLINT - 19, 20, 21, 22, 23, 24, 25, 255, 255, 255, 255, 255, // NOLINT - 255, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, - 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, // NOLINT - 49, 50, 51, 255, 255, 255, 255, 255, 255, 255, 255, 255, // NOLINT - 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, - 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, - 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, - 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, - 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, - 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, - 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, - 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, - 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, - 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, - 255, 255, 255, 255 -}; - // Make the Dex file version 37. static void MakeDexVersion37(DexFile* dex_file) { size_t offset = OFFSETOF_MEMBER(DexFile::Header, magic_) + 6; @@ -64,52 +40,6 @@ static void MakeDexVersion37(DexFile* dex_file) { *(const_cast<uint8_t*>(dex_file->Begin()) + offset) = '7'; } -static inline std::unique_ptr<uint8_t[]> DecodeBase64(const char* src, size_t* dst_size) { - std::vector<uint8_t> tmp; - uint32_t t = 0, y = 0; - int g = 3; - for (size_t i = 0; src[i] != '\0'; ++i) { - uint8_t c = kBase64Map[src[i] & 0xFF]; - if (c == 255) continue; - // the final = symbols are read and used to trim the remaining bytes - if (c == 254) { - c = 0; - // prevent g < 0 which would potentially allow an overflow later - if (--g < 0) { - *dst_size = 0; - return nullptr; - } - } else if (g != 3) { - // we only allow = to be at the end - *dst_size = 0; - return nullptr; - } - t = (t << 6) | c; - if (++y == 4) { - tmp.push_back((t >> 16) & 255); - if (g > 1) { - tmp.push_back((t >> 8) & 255); - } - if (g > 2) { - tmp.push_back(t & 255); - } - y = t = 0; - } - } - if (y != 0) { - *dst_size = 0; - return nullptr; - } - std::unique_ptr<uint8_t[]> dst(new uint8_t[tmp.size()]); - if (dst_size != nullptr) { - *dst_size = tmp.size(); - } else { - *dst_size = 0; - } - std::copy(tmp.begin(), tmp.end(), dst.get()); - return dst; -} - static void FixUpChecksum(uint8_t* dex_file) { DexFile::Header* header = reinterpret_cast<DexFile::Header*>(dex_file); uint32_t expected_size = header->file_size_; @@ -131,7 +61,7 @@ class DexFileVerifierTest : public CommonRuntimeTest { std::function<void(DexFile*)> f, const char* expected_error) { size_t length; - std::unique_ptr<uint8_t[]> dex_bytes = DecodeBase64(dex_file_base64_content, &length); + std::unique_ptr<uint8_t[]> dex_bytes(DecodeBase64(dex_file_base64_content, &length)); CHECK(dex_bytes != nullptr); // Note: `dex_file` will be destroyed before `dex_bytes`. std::unique_ptr<DexFile> dex_file(GetDexFile(dex_bytes.get(), length)); @@ -1704,7 +1634,7 @@ TEST_F(DexFileVerifierTest, CircularInterfaceImplementation) { TEST_F(DexFileVerifierTest, Checksum) { size_t length; - std::unique_ptr<uint8_t[]> dex_bytes = DecodeBase64(kGoodTestDex, &length); + std::unique_ptr<uint8_t[]> dex_bytes(DecodeBase64(kGoodTestDex, &length)); CHECK(dex_bytes != nullptr); // Note: `dex_file` will be destroyed before `dex_bytes`. std::unique_ptr<DexFile> dex_file(GetDexFile(dex_bytes.get(), length)); diff --git a/runtime/openjdkjvmti/Android.bp b/runtime/openjdkjvmti/Android.bp index 977ef44de2..08272fa999 100644 --- a/runtime/openjdkjvmti/Android.bp +++ b/runtime/openjdkjvmti/Android.bp @@ -17,7 +17,8 @@ cc_defaults { name: "libopenjdkjvmti_defaults", defaults: ["art_defaults"], host_supported: true, - srcs: ["OpenjdkJvmTi.cc"], + srcs: ["OpenjdkJvmTi.cc", + "transform.cc"], include_dirs: ["art/runtime"], shared_libs: ["libnativehelper"], } diff --git a/runtime/openjdkjvmti/OpenjdkJvmTi.cc b/runtime/openjdkjvmti/OpenjdkJvmTi.cc index d3561c1e1b..a1a23619f3 100644 --- a/runtime/openjdkjvmti/OpenjdkJvmTi.cc +++ b/runtime/openjdkjvmti/OpenjdkJvmTi.cc @@ -29,15 +29,17 @@ * questions. */ +#include <string> +#include <vector> + #include <jni.h> + #include "openjdkjvmti/jvmti.h" #include "art_jvmti.h" -#include "gc_root-inl.h" -#include "globals.h" #include "jni_env_ext-inl.h" -#include "scoped_thread_state_change.h" -#include "thread_list.h" +#include "runtime.h" +#include "transform.h" // TODO Remove this at some point by annotating all the methods. It was put in to make the skeleton // easier to create. @@ -904,6 +906,66 @@ class JvmtiFunctions { static jvmtiError GetJLocationFormat(jvmtiEnv* env, jvmtiJlocationFormat* format_ptr) { return ERR(NOT_IMPLEMENTED); } + + // TODO Remove this once events are working. + static jvmtiError RetransformClassWithHook(jvmtiEnv* env, + jclass klass, + jvmtiEventClassFileLoadHook hook) { + std::vector<jclass> classes; + classes.push_back(klass); + return RetransformClassesWithHook(reinterpret_cast<ArtJvmTiEnv*>(env), classes, hook); + } + + // TODO This will be called by the event handler for the art::ti Event Load Event + static jvmtiError RetransformClassesWithHook(ArtJvmTiEnv* env, + const std::vector<jclass>& classes, + jvmtiEventClassFileLoadHook hook) { + if (!IsValidEnv(env)) { + return ERR(INVALID_ENVIRONMENT); + } + for (jclass klass : classes) { + JNIEnv* jni_env = nullptr; + jobject loader = nullptr; + std::string name; + jobject protection_domain = nullptr; + jint data_len = 0; + unsigned char* dex_data = nullptr; + jvmtiError ret = OK; + std::string location; + if ((ret = GetTransformationData(env, + klass, + /*out*/&location, + /*out*/&jni_env, + /*out*/&loader, + /*out*/&name, + /*out*/&protection_domain, + /*out*/&data_len, + /*out*/&dex_data)) != OK) { + // TODO Do something more here? Maybe give log statements? + return ret; + } + jint new_data_len = 0; + unsigned char* new_dex_data = nullptr; + hook(env, + jni_env, + klass, + loader, + name.c_str(), + protection_domain, + data_len, + dex_data, + /*out*/&new_data_len, + /*out*/&new_dex_data); + // Check if anything actually changed. + if ((new_data_len != 0 || new_dex_data != nullptr) && new_dex_data != dex_data) { + MoveTransformedFileIntoRuntime(klass, std::move(location), new_data_len, new_dex_data); + env->Deallocate(new_dex_data); + } + // Deallocate the old dex data. + env->Deallocate(dex_data); + } + return OK; + } }; static bool IsJvmtiVersion(jint version) { @@ -942,7 +1004,10 @@ extern "C" bool ArtPlugin_Initialize() { // The actual struct holding all of the entrypoints into the jvmti interface. const jvmtiInterface_1 gJvmtiInterface = { - nullptr, // reserved1 + // SPECIAL FUNCTION: RetransformClassWithHook Is normally reserved1 + // TODO Remove once we have events working. + reinterpret_cast<void*>(JvmtiFunctions::RetransformClassWithHook), + // nullptr, // reserved1 JvmtiFunctions::SetEventNotificationMode, nullptr, // reserved3 JvmtiFunctions::GetAllThreads, diff --git a/runtime/openjdkjvmti/transform.cc b/runtime/openjdkjvmti/transform.cc new file mode 100644 index 0000000000..a0d79f3982 --- /dev/null +++ b/runtime/openjdkjvmti/transform.cc @@ -0,0 +1,362 @@ +/* Copyright (C) 2016 The Android Open Source Project + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This file implements interfaces from the file jvmti.h. This implementation + * is licensed under the same terms as the file jvmti.h. The + * copyright and license information for the file jvmti.h follows. + * + * Copyright (c) 2003, 2011, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +#include "transform.h" + +#include "class_linker.h" +#include "dex_file.h" +#include "gc_root-inl.h" +#include "globals.h" +#include "jni_env_ext-inl.h" +#include "jvmti.h" +#include "linear_alloc.h" +#include "mem_map.h" +#include "mirror/array.h" +#include "mirror/class-inl.h" +#include "mirror/class_loader-inl.h" +#include "mirror/string-inl.h" +#include "scoped_thread_state_change.h" +#include "thread_list.h" +#include "transform.h" +#include "utf.h" +#include "utils/dex_cache_arrays_layout-inl.h" + +namespace openjdkjvmti { + +static bool ReadChecksum(jint data_len, const unsigned char* dex, /*out*/uint32_t* res) { + if (data_len < static_cast<jint>(sizeof(art::DexFile::Header))) { + return false; + } + *res = reinterpret_cast<const art::DexFile::Header*>(dex)->checksum_; + return true; +} + +static std::unique_ptr<art::MemMap> MoveDataToMemMap(const std::string& original_location, + jint data_len, + unsigned char* dex_data) { + std::string error_msg; + std::unique_ptr<art::MemMap> map(art::MemMap::MapAnonymous( + art::StringPrintf("%s-transformed", original_location.c_str()).c_str(), + nullptr, + data_len, + PROT_READ|PROT_WRITE, + /*low_4gb*/false, + /*reuse*/false, + &error_msg)); + if (map == nullptr) { + return map; + } + memcpy(map->Begin(), dex_data, data_len); + map->Protect(PROT_READ); + return map; +} + +static void InvalidateExistingMethods(art::Thread* self, + art::Handle<art::mirror::Class> klass, + art::Handle<art::mirror::DexCache> cache, + const art::DexFile* dex_file) + REQUIRES_SHARED(art::Locks::mutator_lock_) { + // Create new DexCache with new DexFile. + // reset dex_class_def_idx_ + // for each method reset entry_point_from_quick_compiled_code_ to bridge + // for each method reset dex_code_item_offset_ + // for each method reset dex_method_index_ + // for each method set dex_cache_resolved_methods_ to new DexCache + // for each method set dex_cache_resolved_types_ to new DexCache + auto* runtime = art::Runtime::Current(); + art::ClassLinker* linker = runtime->GetClassLinker(); + art::PointerSize image_pointer_size = linker->GetImagePointerSize(); + std::string descriptor_storage; + const char* descriptor = klass->GetDescriptor(&descriptor_storage); + // Get the new class def + const art::DexFile::ClassDef* class_def = art::OatFile::OatDexFile::FindClassDef( + *dex_file, descriptor, art::ComputeModifiedUtf8Hash(descriptor)); + CHECK(class_def != nullptr); + const art::DexFile::TypeId& declaring_class_id = dex_file->GetTypeId(class_def->class_idx_); + art::StackHandleScope<6> hs(self); + const art::DexFile& old_dex_file = klass->GetDexFile(); + for (art::ArtMethod& method : klass->GetMethods(image_pointer_size)) { + // Find the code_item for the method then find the dex_method_index and dex_code_item_offset to + // set. + const art::DexFile::StringId* new_name_id = dex_file->FindStringId(method.GetName()); + uint16_t method_return_idx = + dex_file->GetIndexForTypeId(*dex_file->FindTypeId(method.GetReturnTypeDescriptor())); + const auto* old_type_list = method.GetParameterTypeList(); + std::vector<uint16_t> new_type_list; + for (uint32_t i = 0; old_type_list != nullptr && i < old_type_list->Size(); i++) { + new_type_list.push_back( + dex_file->GetIndexForTypeId( + *dex_file->FindTypeId( + old_dex_file.GetTypeDescriptor( + old_dex_file.GetTypeId( + old_type_list->GetTypeItem(i).type_idx_))))); + } + const art::DexFile::ProtoId* proto_id = dex_file->FindProtoId(method_return_idx, + new_type_list); + CHECK(proto_id != nullptr || old_type_list == nullptr); + const art::DexFile::MethodId* method_id = dex_file->FindMethodId(declaring_class_id, + *new_name_id, + *proto_id); + CHECK(method_id != nullptr); + uint32_t dex_method_idx = dex_file->GetIndexForMethodId(*method_id); + method.SetDexMethodIndex(dex_method_idx); + linker->SetEntryPointsToInterpreter(&method); + method.SetCodeItemOffset(dex_file->FindCodeItemOffset(*class_def, dex_method_idx)); + method.SetDexCacheResolvedMethods(cache->GetResolvedMethods(), image_pointer_size); + method.SetDexCacheResolvedTypes(cache->GetResolvedTypes(), image_pointer_size); + } + + // Update the class fields. + // Need to update class last since the ArtMethod gets its DexFile from the class (which is needed + // to call GetReturnTypeDescriptor and GetParameterTypeList above). + klass->SetDexCache(cache.Get()); + klass->SetDexCacheStrings(cache->GetStrings()); + klass->SetDexClassDefIndex(dex_file->GetIndexForClassDef(*class_def)); + klass->SetDexTypeIndex(dex_file->GetIndexForTypeId(*dex_file->FindTypeId(descriptor))); +} + +// Adds the dex file. +static art::mirror::LongArray* InsertDexFileIntoArray(art::Thread* self, + const art::DexFile* dex, + art::Handle<art::mirror::LongArray>& orig) + REQUIRES_SHARED(art::Locks::mutator_lock_) { + art::StackHandleScope<1> hs(self); + CHECK_GE(orig->GetLength(), 1); + art::Handle<art::mirror::LongArray> ret( + hs.NewHandle(art::mirror::LongArray::Alloc(self, orig->GetLength() + 1))); + CHECK(ret.Get() != nullptr); + // Copy the oat-dex. + // TODO Should I clear the oatdex element? + ret->SetWithoutChecks<false>(0, orig->GetWithoutChecks(0)); + ret->SetWithoutChecks<false>(1, static_cast<int64_t>(reinterpret_cast<intptr_t>(dex))); + ret->Memcpy(2, orig.Get(), 1, orig->GetLength() - 1); + return ret.Get(); +} + +// TODO Handle all types of class loaders. +static bool FindDalvikSystemDexFileAndLoaderForClass( + art::Handle<art::mirror::Class> klass, + /*out*/art::mirror::Object** dex_file, + /*out*/art::mirror::ClassLoader** loader) + REQUIRES_SHARED(art::Locks::mutator_lock_) { + const char* dex_path_list_element_array_name = "[Ldalvik/system/DexPathList$Element;"; + const char* dex_path_list_element_name = "Ldalvik/system/DexPathList$Element;"; + const char* dex_file_name = "Ldalvik/system/DexFile;"; + const char* dex_path_list_name = "Ldalvik/system/DexPathList;"; + const char* dex_class_loader_name = "Ldalvik/system/BaseDexClassLoader;"; + + art::Thread* self = art::Thread::Current(); + CHECK(!self->IsExceptionPending()); + art::StackHandleScope<11> hs(self); + art::ClassLinker* class_linker = art::Runtime::Current()->GetClassLinker(); + + art::Handle<art::mirror::ClassLoader> null_loader(hs.NewHandle<art::mirror::ClassLoader>( + nullptr)); + art::Handle<art::mirror::Class> base_dex_loader_class(hs.NewHandle(class_linker->FindClass( + self, dex_class_loader_name, null_loader))); + + art::ArtField* path_list_field = base_dex_loader_class->FindDeclaredInstanceField( + "pathList", dex_path_list_name); + CHECK(path_list_field != nullptr); + + art::ArtField* dex_path_list_element_field = + class_linker->FindClass(self, dex_path_list_name, null_loader) + ->FindDeclaredInstanceField("dexElements", dex_path_list_element_array_name); + CHECK(dex_path_list_element_field != nullptr); + + art::ArtField* element_dex_file_field = + class_linker->FindClass(self, dex_path_list_element_name, null_loader) + ->FindDeclaredInstanceField("dexFile", dex_file_name); + CHECK(element_dex_file_field != nullptr); + + art::Handle<art::mirror::ClassLoader> h_class_loader(hs.NewHandle(klass->GetClassLoader())); + art::Handle<art::mirror::Class> loader_class(hs.NewHandle(h_class_loader->GetClass())); + // Check if loader is a BaseDexClassLoader + if (!loader_class->IsSubClass(base_dex_loader_class.Get())) { + LOG(art::ERROR) << "The classloader is not a BaseDexClassLoader which is currently the only " + << "supported class loader type!"; + return false; + } + art::Handle<art::mirror::Object> path_list( + hs.NewHandle(path_list_field->GetObject(h_class_loader.Get()))); + CHECK(path_list.Get() != nullptr); + CHECK(!self->IsExceptionPending()); + art::Handle<art::mirror::ObjectArray<art::mirror::Object>> dex_elements_list( + hs.NewHandle(art::down_cast<art::mirror::ObjectArray<art::mirror::Object>*>( + dex_path_list_element_field->GetObject(path_list.Get())))); + CHECK(!self->IsExceptionPending()); + CHECK(dex_elements_list.Get() != nullptr); + size_t num_elements = dex_elements_list->GetLength(); + art::MutableHandle<art::mirror::Object> current_element( + hs.NewHandle<art::mirror::Object>(nullptr)); + art::MutableHandle<art::mirror::Object> first_dex_file( + hs.NewHandle<art::mirror::Object>(nullptr)); + for (size_t i = 0; i < num_elements; i++) { + current_element.Assign(dex_elements_list->Get(i)); + CHECK(current_element.Get() != nullptr); + CHECK(!self->IsExceptionPending()); + CHECK(dex_elements_list.Get() != nullptr); + CHECK_EQ(current_element->GetClass(), class_linker->FindClass(self, + dex_path_list_element_name, + null_loader)); + // TODO It would be cleaner to put the art::DexFile into the dalvik.system.DexFile the class + // comes from but it is more annoying because we would need to find this class. It is not + // necessary for proper function since we just need to be in front of the classes old dex file + // in the path. + first_dex_file.Assign(element_dex_file_field->GetObject(current_element.Get())); + if (first_dex_file.Get() != nullptr) { + *dex_file = first_dex_file.Get(); + *loader = h_class_loader.Get(); + return true; + } + } + return false; +} + +// Gets the data surrounding the given class. +jvmtiError GetTransformationData(ArtJvmTiEnv* env, + jclass klass, + /*out*/std::string* location, + /*out*/JNIEnv** jni_env_ptr, + /*out*/jobject* loader, + /*out*/std::string* name, + /*out*/jobject* protection_domain, + /*out*/jint* data_len, + /*out*/unsigned char** dex_data) { + jint ret = env->art_vm->GetEnv(reinterpret_cast<void**>(jni_env_ptr), JNI_VERSION_1_1); + if (ret != JNI_OK) { + // TODO Different error might be better? + return ERR(INTERNAL); + } + JNIEnv* jni_env = *jni_env_ptr; + art::ScopedObjectAccess soa(jni_env); + art::StackHandleScope<3> hs(art::Thread::Current()); + art::Handle<art::mirror::Class> hs_klass(hs.NewHandle(soa.Decode<art::mirror::Class*>(klass))); + *loader = soa.AddLocalReference<jobject>(hs_klass->GetClassLoader()); + *name = art::mirror::Class::ComputeName(hs_klass)->ToModifiedUtf8(); + // TODO is this always null? + *protection_domain = nullptr; + const art::DexFile& dex = hs_klass->GetDexFile(); + *location = dex.GetLocation(); + *data_len = static_cast<jint>(dex.Size()); + // TODO We should maybe change env->Allocate to allow us to mprotect this memory and stop writes. + jvmtiError alloc_error = env->Allocate(*data_len, dex_data); + if (alloc_error != OK) { + return alloc_error; + } + // Copy the data into a temporary buffer. + memcpy(reinterpret_cast<void*>(*dex_data), + reinterpret_cast<const void*>(dex.Begin()), + *data_len); + return OK; +} + +// Install the new dex file. +// TODO do error checks for bad state (method in a stack, changes to number of methods/fields/etc). +jvmtiError MoveTransformedFileIntoRuntime(jclass jklass, + std::string original_location, + jint data_len, + unsigned char* dex_data) { + const char* dex_file_name = "Ldalvik/system/DexFile;"; + art::Thread* self = art::Thread::Current(); + art::Runtime* runtime = art::Runtime::Current(); + art::ThreadList* threads = runtime->GetThreadList(); + art::ClassLinker* class_linker = runtime->GetClassLinker(); + uint32_t checksum = 0; + if (!ReadChecksum(data_len, dex_data, &checksum)) { + return ERR(INVALID_CLASS_FORMAT); + } + + std::unique_ptr<art::MemMap> map(MoveDataToMemMap(original_location, data_len, dex_data)); + if (map.get() == nullptr) { + return ERR(INTERNAL); + } + std::string error_msg; + // Load the new dex_data in memory (mmap it, etc) + std::unique_ptr<const art::DexFile> new_dex_file = art::DexFile::Open(map->GetName(), + checksum, + std::move(map), + /*verify*/ true, + /*verify_checksum*/ true, + &error_msg); + CHECK(new_dex_file.get() != nullptr) << "Unable to load dex file! " << error_msg; + + // Get mutator lock. We need the lifetimes of these variables (hs, the classes, etc.) to be longer + // then current lock (since there isn't upgrading of the lock) so we don't use soa. + art::ThreadState old_state = self->TransitionFromSuspendedToRunnable(); + // This scope is needed to make sure that the HandleScope dies with mutator_lock_ since we need to + // upgrade the mutator_lock during the execution. + { + art::StackHandleScope<11> hs(self); + art::Handle<art::mirror::ClassLoader> null_loader( + hs.NewHandle<art::mirror::ClassLoader>(nullptr)); + CHECK(null_loader.Get() == nullptr); + art::ArtField* dex_file_cookie_field = class_linker-> + FindClass(self, dex_file_name, null_loader)-> + FindDeclaredInstanceField("mCookie", "Ljava/lang/Object;"); + art::ArtField* dex_file_internal_cookie_field = + class_linker->FindClass(self, dex_file_name, null_loader) + ->FindDeclaredInstanceField("mInternalCookie", "Ljava/lang/Object;"); + CHECK(dex_file_cookie_field != nullptr); + art::Handle<art::mirror::Class> klass( + hs.NewHandle(art::down_cast<art::mirror::Class*>(self->DecodeJObject(jklass)))); + art::mirror::Object* dex_file_ptr = nullptr; + art::mirror::ClassLoader* class_loader_ptr = nullptr; + // Find dalvik.system.DexFile that represents the dex file we are changing. + if (!FindDalvikSystemDexFileAndLoaderForClass(klass, &dex_file_ptr, &class_loader_ptr)) { + self->TransitionFromRunnableToSuspended(old_state); + LOG(art::ERROR) << "Could not find DexFile."; + return ERR(INTERNAL); + } + art::Handle<art::mirror::Object> dex_file_obj(hs.NewHandle(dex_file_ptr)); + art::Handle<art::mirror::ClassLoader> class_loader(hs.NewHandle(class_loader_ptr)); + art::Handle<art::mirror::LongArray> art_dex_array( + hs.NewHandle<art::mirror::LongArray>( + dex_file_cookie_field->GetObject(dex_file_obj.Get())->AsLongArray())); + art::Handle<art::mirror::LongArray> new_art_dex_array( + hs.NewHandle<art::mirror::LongArray>( + InsertDexFileIntoArray(self, new_dex_file.get(), art_dex_array))); + art::Handle<art::mirror::DexCache> cache( + hs.NewHandle(class_linker->RegisterDexFile(*new_dex_file.get(), class_loader.Get()))); + self->TransitionFromRunnableToSuspended(old_state); + + threads->SuspendAll("moving dex file into runtime", /*long_suspend*/true); + // Change the mCookie field. Old value will be GC'd as normal. + dex_file_cookie_field->SetObject<false>(dex_file_obj.Get(), new_art_dex_array.Get()); + dex_file_internal_cookie_field->SetObject<false>(dex_file_obj.Get(), new_art_dex_array.Get()); + // Invalidate existing methods. + InvalidateExistingMethods(self, klass, cache, new_dex_file.release()); + } + threads->ResumeAll(); + return OK; +} + +} // namespace openjdkjvmti diff --git a/runtime/openjdkjvmti/transform.h b/runtime/openjdkjvmti/transform.h new file mode 100644 index 0000000000..85bcb00eca --- /dev/null +++ b/runtime/openjdkjvmti/transform.h @@ -0,0 +1,64 @@ +/* Copyright (C) 2016 The Android Open Source Project + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This file implements interfaces from the file jvmti.h. This implementation + * is licensed under the same terms as the file jvmti.h. The + * copyright and license information for the file jvmti.h follows. + * + * Copyright (c) 2003, 2011, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +#ifndef ART_RUNTIME_OPENJDKJVMTI_TRANSFORM_H_ +#define ART_RUNTIME_OPENJDKJVMTI_TRANSFORM_H_ + +#include <string> + +#include <jni.h> + +#include "art_jvmti.h" +#include "jvmti.h" + +namespace openjdkjvmti { + +// Gets the data surrounding the given class. +jvmtiError GetTransformationData(ArtJvmTiEnv* env, + jclass klass, + /*out*/std::string* location, + /*out*/JNIEnv** jni_env_ptr, + /*out*/jobject* loader, + /*out*/std::string* name, + /*out*/jobject* protection_domain, + /*out*/jint* data_len, + /*out*/unsigned char** dex_data); + +// Install the new dex file. +jvmtiError MoveTransformedFileIntoRuntime(jclass jklass, + std::string original_location, + jint data_len, + unsigned char* dex_data); + +} // namespace openjdkjvmti + +#endif // ART_RUNTIME_OPENJDKJVMTI_TRANSFORM_H_ + diff --git a/runtime/utils.cc b/runtime/utils.cc index 6f10aaacaf..b52e2f2bca 100644 --- a/runtime/utils.cc +++ b/runtime/utils.cc @@ -52,6 +52,77 @@ namespace art { +static const uint8_t kBase64Map[256] = { + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 62, 255, 255, 255, 63, + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 255, 255, + 255, 254, 255, 255, 255, 0, 1, 2, 3, 4, 5, 6, + 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, // NOLINT + 19, 20, 21, 22, 23, 24, 25, 255, 255, 255, 255, 255, // NOLINT + 255, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, + 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, // NOLINT + 49, 50, 51, 255, 255, 255, 255, 255, 255, 255, 255, 255, // NOLINT + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255 +}; + +uint8_t* DecodeBase64(const char* src, size_t* dst_size) { + std::vector<uint8_t> tmp; + uint32_t t = 0, y = 0; + int g = 3; + for (size_t i = 0; src[i] != '\0'; ++i) { + uint8_t c = kBase64Map[src[i] & 0xFF]; + if (c == 255) continue; + // the final = symbols are read and used to trim the remaining bytes + if (c == 254) { + c = 0; + // prevent g < 0 which would potentially allow an overflow later + if (--g < 0) { + *dst_size = 0; + return nullptr; + } + } else if (g != 3) { + // we only allow = to be at the end + *dst_size = 0; + return nullptr; + } + t = (t << 6) | c; + if (++y == 4) { + tmp.push_back((t >> 16) & 255); + if (g > 1) { + tmp.push_back((t >> 8) & 255); + } + if (g > 2) { + tmp.push_back(t & 255); + } + y = t = 0; + } + } + if (y != 0) { + *dst_size = 0; + return nullptr; + } + std::unique_ptr<uint8_t[]> dst(new uint8_t[tmp.size()]); + if (dst_size != nullptr) { + *dst_size = tmp.size(); + } else { + *dst_size = 0; + } + std::copy(tmp.begin(), tmp.end(), dst.get()); + return dst.release(); +} + pid_t GetTid() { #if defined(__APPLE__) uint64_t owner; diff --git a/runtime/utils.h b/runtime/utils.h index f3284e8304..e65b947e73 100644 --- a/runtime/utils.h +++ b/runtime/utils.h @@ -116,6 +116,8 @@ inline typename std::make_unsigned<T>::type MakeUnsigned(T x) { return static_cast<typename std::make_unsigned<T>::type>(x); } +uint8_t* DecodeBase64(const char* src, size_t* dst_size); + std::string PrintableChar(uint16_t ch); // Returns an ASCII string corresponding to the given UTF-8 string. diff --git a/test/902-hello-transformation/build b/test/902-hello-transformation/build new file mode 100755 index 0000000000..898e2e54a2 --- /dev/null +++ b/test/902-hello-transformation/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/902-hello-transformation/expected.txt b/test/902-hello-transformation/expected.txt new file mode 100644 index 0000000000..e86e814cab --- /dev/null +++ b/test/902-hello-transformation/expected.txt @@ -0,0 +1,3 @@ +Hello +modifying class 'Transform' +Goodbye diff --git a/test/902-hello-transformation/info.txt b/test/902-hello-transformation/info.txt new file mode 100644 index 0000000000..875a5f6ec1 --- /dev/null +++ b/test/902-hello-transformation/info.txt @@ -0,0 +1 @@ +Tests basic functions in the jvmti plugin. diff --git a/test/902-hello-transformation/run b/test/902-hello-transformation/run new file mode 100755 index 0000000000..204e4cc5d1 --- /dev/null +++ b/test/902-hello-transformation/run @@ -0,0 +1,43 @@ +#!/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. + +plugin=libopenjdkjvmtid.so +agent=libtiagentd.so +lib=tiagentd +if [[ "$@" == *"-O"* ]]; then + agent=libtiagent.so + plugin=libopenjdkjvmti.so + lib=tiagent +fi + +if [[ "$@" == *"--jvm"* ]]; then + arg="jvm" +else + arg="art" +fi + +if [[ "$@" != *"--debuggable"* ]]; then + other_args=" -Xcompiler-option --debuggable " +else + other_args="" +fi + +./default-run "$@" --experimental agents \ + --experimental runtime-plugins \ + --runtime-option -agentpath:${agent}=902-hello-transformation,${arg} \ + --android-runtime-option -Xplugin:${plugin} \ + ${other_args} \ + --args ${lib} diff --git a/test/902-hello-transformation/src/Main.java b/test/902-hello-transformation/src/Main.java new file mode 100644 index 0000000000..204b6e757d --- /dev/null +++ b/test/902-hello-transformation/src/Main.java @@ -0,0 +1,31 @@ +/* + * 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. + */ + +public class Main { + public static void main(String[] args) { + System.loadLibrary(args[1]); + doTest(new Transform()); + } + + public static void doTest(Transform t) { + t.sayHi(); + doClassTransformation(Transform.class); + t.sayHi(); + } + + // Transforms the class + private static native void doClassTransformation(Class target); +} diff --git a/test/902-hello-transformation/src/Transform.java b/test/902-hello-transformation/src/Transform.java new file mode 100644 index 0000000000..dc0a0c4f04 --- /dev/null +++ b/test/902-hello-transformation/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() { + System.out.println("Hello"); + } +} diff --git a/test/902-hello-transformation/transform.cc b/test/902-hello-transformation/transform.cc new file mode 100644 index 0000000000..e0d623e6e1 --- /dev/null +++ b/test/902-hello-transformation/transform.cc @@ -0,0 +1,154 @@ +/* + * Copyright (C) 2013 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 <iostream> +#include <pthread.h> +#include <stdio.h> +#include <vector> + +#include "art_method-inl.h" +#include "base/logging.h" +#include "jni.h" +#include "openjdkjvmti/jvmti.h" +#include "utils.h" + +namespace art { +namespace Test902HelloTransformation { + +static bool RuntimeIsJvm = false; + +jvmtiEnv* jvmti_env; +bool IsJVM() { + return RuntimeIsJvm; +} + +// base64 encoded class/dex file for +// +// class Transform { +// public void sayHi() { +// System.out.println("Goodbye"); +// } +// } +const char* class_file_base64 = + "yv66vgAAADQAHAoABgAOCQAPABAIABEKABIAEwcAFAcAFQEABjxpbml0PgEAAygpVgEABENvZGUB" + "AA9MaW5lTnVtYmVyVGFibGUBAAVzYXlIaQEAClNvdXJjZUZpbGUBAA5UcmFuc2Zvcm0uamF2YQwA" + "BwAIBwAWDAAXABgBAAdHb29kYnllBwAZDAAaABsBAAlUcmFuc2Zvcm0BABBqYXZhL2xhbmcvT2Jq" + "ZWN0AQAQamF2YS9sYW5nL1N5c3RlbQEAA291dAEAFUxqYXZhL2lvL1ByaW50U3RyZWFtOwEAE2ph" + "dmEvaW8vUHJpbnRTdHJlYW0BAAdwcmludGxuAQAVKExqYXZhL2xhbmcvU3RyaW5nOylWACAABQAG" + "AAAAAAACAAAABwAIAAEACQAAAB0AAQABAAAABSq3AAGxAAAAAQAKAAAABgABAAAAEQABAAsACAAB" + "AAkAAAAlAAIAAQAAAAmyAAISA7YABLEAAAABAAoAAAAKAAIAAAATAAgAFAABAAwAAAACAA0="; + +const char* dex_file_base64 = + "ZGV4CjAzNQCLXSBQ5FiS3f16krSYZFF8xYZtFVp0GRXMAgAAcAAAAHhWNBIAAAAAAAAAACwCAAAO" + "AAAAcAAAAAYAAACoAAAAAgAAAMAAAAABAAAA2AAAAAQAAADgAAAAAQAAAAABAACsAQAAIAEAAGIB" + "AABqAQAAcwEAAIABAACXAQAAqwEAAL8BAADTAQAA4wEAAOYBAADqAQAA/gEAAAMCAAAMAgAAAgAA" + "AAMAAAAEAAAABQAAAAYAAAAIAAAACAAAAAUAAAAAAAAACQAAAAUAAABcAQAABAABAAsAAAAAAAAA" + "AAAAAAAAAAANAAAAAQABAAwAAAACAAAAAAAAAAAAAAAAAAAAAgAAAAAAAAAHAAAAAAAAAB4CAAAA" + "AAAAAQABAAEAAAATAgAABAAAAHAQAwAAAA4AAwABAAIAAAAYAgAACQAAAGIAAAAbAQEAAABuIAIA" + "EAAOAAAAAQAAAAMABjxpbml0PgAHR29vZGJ5ZQALTFRyYW5zZm9ybTsAFUxqYXZhL2lvL1ByaW50" + "U3RyZWFtOwASTGphdmEvbGFuZy9PYmplY3Q7ABJMamF2YS9sYW5nL1N0cmluZzsAEkxqYXZhL2xh" + "bmcvU3lzdGVtOwAOVHJhbnNmb3JtLmphdmEAAVYAAlZMABJlbWl0dGVyOiBqYWNrLTMuMzYAA291" + "dAAHcHJpbnRsbgAFc2F5SGkAEQAHDgATAAcOhQAAAAEBAICABKACAQG4Ag0AAAAAAAAAAQAAAAAA" + "AAABAAAADgAAAHAAAAACAAAABgAAAKgAAAADAAAAAgAAAMAAAAAEAAAAAQAAANgAAAAFAAAABAAA" + "AOAAAAAGAAAAAQAAAAABAAABIAAAAgAAACABAAABEAAAAQAAAFwBAAACIAAADgAAAGIBAAADIAAA" + "AgAAABMCAAAAIAAAAQAAAB4CAAAAEAAAAQAAACwCAAA="; + +static void JNICALL transformationHook(jvmtiEnv *jvmtienv, + JNIEnv* jni_env ATTRIBUTE_UNUSED, + jclass class_being_redefined ATTRIBUTE_UNUSED, + jobject loader ATTRIBUTE_UNUSED, + const char* name, + jobject protection_domain ATTRIBUTE_UNUSED, + jint class_data_len ATTRIBUTE_UNUSED, + const unsigned char* class_data ATTRIBUTE_UNUSED, + jint* new_class_data_len, + unsigned char** new_class_data) { + if (strcmp("Transform", name)) { + return; + } + printf("modifying class '%s'\n", name); + bool is_jvm = IsJVM(); + size_t decode_len = 0; + unsigned char* new_data; + std::unique_ptr<uint8_t[]> file_data( + DecodeBase64((is_jvm) ? class_file_base64 : dex_file_base64, &decode_len)); + jvmtiError ret = JVMTI_ERROR_NONE; + if ((ret = jvmtienv->Allocate(static_cast<jlong>(decode_len), &new_data)) != JVMTI_ERROR_NONE) { + printf("Unable to allocate buffer!\n"); + return; + } + memcpy(new_data, file_data.get(), decode_len); + *new_class_data_len = static_cast<jint>(decode_len); + *new_class_data = new_data; + return; +} + +using RetransformWithHookFunction = jvmtiError (*)(jvmtiEnv*, jclass, jvmtiEventClassFileLoadHook); +static void DoClassTransformation(jvmtiEnv* jvmtienv, JNIEnv* jnienv, jclass target) { + if (IsJVM()) { + UNUSED(jnienv); + jvmtienv->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_CLASS_FILE_LOAD_HOOK, nullptr); + jvmtiError ret = jvmtienv->RetransformClasses(1, &target); + if (ret != JVMTI_ERROR_NONE) { + char* err; + jvmtienv->GetErrorName(ret, &err); + printf("Error transforming: %s\n", err); + } + } else { + RetransformWithHookFunction f = + reinterpret_cast<RetransformWithHookFunction>(jvmtienv->functions->reserved1); + if (f(jvmtienv, target, transformationHook) != JVMTI_ERROR_NONE) { + printf("Failed to tranform class!"); + return; + } + } +} + +extern "C" JNIEXPORT void JNICALL Java_Main_doClassTransformation(JNIEnv* env, + jclass, + jclass target) { + JavaVM* vm; + if (env->GetJavaVM(&vm)) { + printf("Unable to get javaVM!\n"); + return; + } + DoClassTransformation(jvmti_env, env, target); +} + +// Don't do anything +jint OnLoad(JavaVM* vm, + char* options, + void* reserved ATTRIBUTE_UNUSED) { + jvmtiCapabilities caps; + RuntimeIsJvm = (strcmp("jvm", options) == 0); + if (vm->GetEnv(reinterpret_cast<void**>(&jvmti_env), JVMTI_VERSION_1_0)) { + printf("Unable to get jvmti env!\n"); + return 1; + } + if (IsJVM()) { + jvmti_env->GetPotentialCapabilities(&caps); + jvmti_env->AddCapabilities(&caps); + jvmtiEventCallbacks cbs; + memset(&cbs, 0, sizeof(cbs)); + cbs.ClassFileLoadHook = transformationHook; + jvmti_env->SetEventCallbacks(&cbs, sizeof(jvmtiEventCallbacks)); + } + return 0; +} + +} // namespace Test902HelloTransformation +} // namespace art + diff --git a/test/902-hello-transformation/transform.h b/test/902-hello-transformation/transform.h new file mode 100644 index 0000000000..661058dd99 --- /dev/null +++ b/test/902-hello-transformation/transform.h @@ -0,0 +1,30 @@ +/* + * 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. + */ + +#ifndef ART_TEST_902_HELLO_TRANSFORMATION_TRANSFORM_H_ +#define ART_TEST_902_HELLO_TRANSFORMATION_TRANSFORM_H_ + +#include <jni.h> + +namespace art { +namespace Test902HelloTransformation { + +jint OnLoad(JavaVM* vm, char* options, void* reserved); + +} // namespace Test902HelloTransformation +} // namespace art + +#endif // ART_TEST_902_HELLO_TRANSFORMATION_TRANSFORM_H_ diff --git a/test/Android.bp b/test/Android.bp index ff408f41a7..84e82da583 100644 --- a/test/Android.bp +++ b/test/Android.bp @@ -239,6 +239,7 @@ art_cc_test_library { srcs: [ "ti-agent/common_load.cc", "901-hello-ti-agent/basics.cc", + "902-hello-transformation/transform.cc", ], shared_libs: [ "libart", @@ -255,6 +256,7 @@ art_cc_test_library { srcs: [ "ti-agent/common_load.cc", "901-hello-ti-agent/basics.cc", + "902-hello-transformation/transform.cc", ], shared_libs: [ "libartd", diff --git a/test/etc/run-test-jar b/test/etc/run-test-jar index a445f4d630..c51cb0db2a 100755 --- a/test/etc/run-test-jar +++ b/test/etc/run-test-jar @@ -310,8 +310,9 @@ if [ "$DEBUGGER" = "y" ]; then fi if [ "$USE_JVM" = "y" ]; then + export LD_LIBRARY_PATH=${ANDROID_HOST_OUT}/lib64 # Xmx is necessary since we don't pass down the ART flags to JVM. - cmdline="${JAVA} ${DEBUGGER_OPTS} ${JVM_VERIFY_ARG} -Xmx256m -classpath classes ${FLAGS} $MAIN $@" + cmdline="${JAVA} ${DEBUGGER_OPTS} ${JVM_VERIFY_ARG} -Xmx256m -classpath classes ${FLAGS} $MAIN $@ ${ARGS}" if [ "$DEV_MODE" = "y" ]; then echo $cmdline fi diff --git a/test/run-test b/test/run-test index ae53f9ecc0..250263a928 100755 --- a/test/run-test +++ b/test/run-test @@ -743,9 +743,7 @@ if [[ "$TEST_NAME" =~ ^[0-9]+-checker- ]]; then fi fi -if [ "$runtime" != "jvm" ]; then run_args="${run_args} --testlib ${testlib}" -fi # To cause tests to fail fast, limit the file sizes created by dx, dex2oat and ART output to 2MB. build_file_size_limit=2048 diff --git a/test/ti-agent/common_load.cc b/test/ti-agent/common_load.cc index ed280e4fdc..53bb1533e7 100644 --- a/test/ti-agent/common_load.cc +++ b/test/ti-agent/common_load.cc @@ -24,6 +24,7 @@ #include "base/macros.h" #include "901-hello-ti-agent/basics.h" +#include "902-hello-transformation/transform.h" namespace art { @@ -39,6 +40,7 @@ struct AgentLib { // A list of all the agents we have for testing. AgentLib agents[] = { { "901-hello-ti-agent", Test901HelloTi::OnLoad, nullptr }, + { "902-hello-transformation", Test902HelloTransformation::OnLoad, nullptr }, }; static AgentLib* FindAgent(char* name) { |