summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--runtime/dex_file.cc28
-rw-r--r--runtime/dex_file.h13
-rw-r--r--runtime/dex_file_test.cc71
-rw-r--r--runtime/dex_file_verifier_test.cc76
-rw-r--r--runtime/openjdkjvmti/Android.bp3
-rw-r--r--runtime/openjdkjvmti/OpenjdkJvmTi.cc75
-rw-r--r--runtime/openjdkjvmti/transform.cc362
-rw-r--r--runtime/openjdkjvmti/transform.h64
-rw-r--r--runtime/utils.cc71
-rw-r--r--runtime/utils.h2
-rwxr-xr-xtest/902-hello-transformation/build17
-rw-r--r--test/902-hello-transformation/expected.txt3
-rw-r--r--test/902-hello-transformation/info.txt1
-rwxr-xr-xtest/902-hello-transformation/run43
-rw-r--r--test/902-hello-transformation/src/Main.java31
-rw-r--r--test/902-hello-transformation/src/Transform.java21
-rw-r--r--test/902-hello-transformation/transform.cc154
-rw-r--r--test/902-hello-transformation/transform.h30
-rw-r--r--test/Android.bp2
-rwxr-xr-xtest/etc/run-test-jar3
-rwxr-xr-xtest/run-test2
-rw-r--r--test/ti-agent/common_load.cc2
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) {