Get a basic modification of dex file working

This allows the modification of a single classes methods through
transformation. One must ensure that the provided dex file only
contains one function and does not add or remove any methods or fields
and does not change the inheritance hierarchy in any way. The provided
dex file must verify and there must be no frames of the old code
present on any thread. These constraints are not checked or verified.
Breaking them might cause undefined behavior in all parts of the
runtime. Code that has been inlined in any way might not be replaced.
This feature is extremely experimental.

Bug: 31455788
Test: ./test/run-test --host 902-hello-transformation

Change-Id: I35133d24f6cdafdd2af9dc9863e15ba8493fc50e
diff --git a/runtime/dex_file.cc b/runtime/dex_file.cc
index 70b7f87..409fbba 100644
--- a/runtime/dex_file.cc
+++ b/runtime/dex_file.cc
@@ -564,6 +564,34 @@
   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 14bde09..28aeb1e 100644
--- a/runtime/dex_file.h
+++ b/runtime/dex_file.h
@@ -590,6 +590,9 @@
                              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 @@
                                              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 6a06177..3dffc40 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 @@
   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 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 @@
                                                                 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 5939ef3..c5a4d75 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 @@
   *(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 @@
                           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, 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 977ef44..08272fa 100644
--- a/runtime/openjdkjvmti/Android.bp
+++ b/runtime/openjdkjvmti/Android.bp
@@ -17,7 +17,8 @@
     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 d3561c1..a1a2361 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 @@
   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 @@
 
 // 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 0000000..a0d79f3
--- /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 0000000..85bcb00
--- /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 6f10aaa..b52e2f2 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 f3284e8..e65b947 100644
--- a/runtime/utils.h
+++ b/runtime/utils.h
@@ -116,6 +116,8 @@
   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 0000000..898e2e5
--- /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 0000000..e86e814
--- /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 0000000..875a5f6
--- /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 0000000..204e4cc
--- /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 0000000..204b6e7
--- /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 0000000..dc0a0c4
--- /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 0000000..e0d623e
--- /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 0000000..661058d
--- /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 ff408f4..84e82da 100644
--- a/test/Android.bp
+++ b/test/Android.bp
@@ -239,6 +239,7 @@
     srcs: [
         "ti-agent/common_load.cc",
         "901-hello-ti-agent/basics.cc",
+        "902-hello-transformation/transform.cc",
     ],
     shared_libs: [
         "libart",
@@ -255,6 +256,7 @@
     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 a445f4d..c51cb0d 100755
--- a/test/etc/run-test-jar
+++ b/test/etc/run-test-jar
@@ -310,8 +310,9 @@
 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 ae53f9e..250263a 100755
--- a/test/run-test
+++ b/test/run-test
@@ -743,9 +743,7 @@
   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 ed280e4..53bb153 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 @@
 // 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) {