Merge "Remove alignment bits in between stack maps"
diff --git a/runtime/art_method-inl.h b/runtime/art_method-inl.h
index a35c7ab..7ec3900 100644
--- a/runtime/art_method-inl.h
+++ b/runtime/art_method-inl.h
@@ -244,7 +244,9 @@
 }
 
 inline const DexFile* ArtMethod::GetDexFile() {
-  return GetDexCache()->GetDexFile();
+  // It is safe to avoid the read barrier here since the dex file is constant, so if we read the
+  // from-space dex file pointer it will be equal to the to-space copy.
+  return GetDexCache<kWithoutReadBarrier>()->GetDexFile();
 }
 
 inline const char* ArtMethod::GetDeclaringClassDescriptor() {
@@ -361,9 +363,11 @@
   return GetDeclaringClass()->GetClassLoader();
 }
 
+template <ReadBarrierOption kReadBarrierOption>
 inline mirror::DexCache* ArtMethod::GetDexCache() {
   if (LIKELY(!IsObsolete())) {
-    return GetDeclaringClass()->GetDexCache();
+    mirror::Class* klass = GetDeclaringClass<kReadBarrierOption>();
+    return klass->GetDexCache<kDefaultVerifyFlags, kReadBarrierOption>();
   } else {
     DCHECK(!IsProxyMethod());
     return GetObsoleteDexCache();
@@ -379,14 +383,13 @@
   if (LIKELY(!IsProxyMethod())) {
     return this;
   }
-  mirror::Class* klass = GetDeclaringClass();
   ArtMethod* interface_method = mirror::DexCache::GetElementPtrSize(
       GetDexCacheResolvedMethods(pointer_size),
       GetDexMethodIndex(),
       pointer_size);
   DCHECK(interface_method != nullptr);
   DCHECK_EQ(interface_method,
-            Runtime::Current()->GetClassLinker()->FindMethodForProxy(klass, this));
+            Runtime::Current()->GetClassLinker()->FindMethodForProxy(GetDeclaringClass(), this));
   return interface_method;
 }
 
diff --git a/runtime/art_method.h b/runtime/art_method.h
index a33111a..f145d7c 100644
--- a/runtime/art_method.h
+++ b/runtime/art_method.h
@@ -563,6 +563,7 @@
 
   mirror::ClassLoader* GetClassLoader() REQUIRES_SHARED(Locks::mutator_lock_);
 
+  template <ReadBarrierOption kReadBarrierOption = kWithReadBarrier>
   mirror::DexCache* GetDexCache() REQUIRES_SHARED(Locks::mutator_lock_);
   mirror::DexCache* GetObsoleteDexCache() REQUIRES_SHARED(Locks::mutator_lock_);
 
diff --git a/runtime/mirror/class-inl.h b/runtime/mirror/class-inl.h
index 2fb8d28..6a65e12 100644
--- a/runtime/mirror/class-inl.h
+++ b/runtime/mirror/class-inl.h
@@ -71,9 +71,10 @@
       OFFSET_OF_OBJECT_MEMBER(Class, class_loader_));
 }
 
-template<VerifyObjectFlags kVerifyFlags>
+template<VerifyObjectFlags kVerifyFlags, ReadBarrierOption kReadBarrierOption>
 inline DexCache* Class::GetDexCache() {
-  return GetFieldObject<DexCache, kVerifyFlags>(OFFSET_OF_OBJECT_MEMBER(Class, dex_cache_));
+  return GetFieldObject<DexCache, kVerifyFlags, kReadBarrierOption>(
+      OFFSET_OF_OBJECT_MEMBER(Class, dex_cache_));
 }
 
 inline uint32_t Class::GetCopiedMethodsStartOffset() {
diff --git a/runtime/mirror/class.h b/runtime/mirror/class.h
index 7f6aa12..c9f27ad 100644
--- a/runtime/mirror/class.h
+++ b/runtime/mirror/class.h
@@ -722,7 +722,8 @@
 
   void DumpClass(std::ostream& os, int flags) REQUIRES_SHARED(Locks::mutator_lock_);
 
-  template<VerifyObjectFlags kVerifyFlags = kDefaultVerifyFlags>
+  template<VerifyObjectFlags kVerifyFlags = kDefaultVerifyFlags,
+           ReadBarrierOption kReadBarrierOption = kWithReadBarrier>
   DexCache* GetDexCache() REQUIRES_SHARED(Locks::mutator_lock_);
 
   // Also updates the dex_cache_strings_ variable from new_dex_cache.
diff --git a/runtime/openjdkjvmti/Android.bp b/runtime/openjdkjvmti/Android.bp
index 976a1e7..c01e3f4 100644
--- a/runtime/openjdkjvmti/Android.bp
+++ b/runtime/openjdkjvmti/Android.bp
@@ -22,6 +22,7 @@
            "OpenjdkJvmTi.cc",
            "ti_class.cc",
            "ti_class_definition.cc",
+           "ti_class_loader.cc",
            "ti_dump.cc",
            "ti_field.cc",
            "ti_heap.cc",
diff --git a/runtime/openjdkjvmti/OpenjdkJvmTi.cc b/runtime/openjdkjvmti/OpenjdkJvmTi.cc
index c0c301f..a815a60 100644
--- a/runtime/openjdkjvmti/OpenjdkJvmTi.cc
+++ b/runtime/openjdkjvmti/OpenjdkJvmTi.cc
@@ -1368,6 +1368,7 @@
   ThreadUtil::Register(&gEventHandler);
   ClassUtil::Register(&gEventHandler);
   DumpUtil::Register(&gEventHandler);
+  SearchUtil::Register();
 
   runtime->GetJavaVM()->AddEnvironmentHook(GetEnvHandler);
   runtime->AddSystemWeakHolder(&gObjectTagTable);
@@ -1380,6 +1381,7 @@
   ThreadUtil::Unregister();
   ClassUtil::Unregister();
   DumpUtil::Unregister();
+  SearchUtil::Unregister();
 
   return true;
 }
diff --git a/runtime/openjdkjvmti/ti_class.cc b/runtime/openjdkjvmti/ti_class.cc
index d484c98..c14fd84 100644
--- a/runtime/openjdkjvmti/ti_class.cc
+++ b/runtime/openjdkjvmti/ti_class.cc
@@ -54,6 +54,7 @@
 #include "scoped_thread_state_change-inl.h"
 #include "thread-inl.h"
 #include "thread_list.h"
+#include "ti_class_loader.h"
 #include "ti_redefine.h"
 #include "utils.h"
 
@@ -228,12 +229,15 @@
         return;
       }
 
-      // TODO Check Redefined dex file for invariants.
+      // TODO Check Redefined dex file for all invariants.
       LOG(WARNING) << "Dex file created by class-definition time transformation of "
                    << descriptor << " is not checked for all retransformation invariants.";
-      // TODO Put it in classpath
-      LOG(WARNING) << "Dex file created for class-definition time transformation of "
-                   << descriptor << " was not added to any classpaths!";
+
+      if (!ClassLoaderHelper::AddToClassLoader(self, class_loader, dex_file.get())) {
+        LOG(ERROR) << "Unable to add " << descriptor << " to class loader!";
+        return;
+      }
+
       // Actually set the ClassExt's original bytes once we have actually succeeded.
       ext->SetOriginalDexFileBytes(arr.Get());
       // Set the return values
diff --git a/runtime/openjdkjvmti/ti_class_loader.cc b/runtime/openjdkjvmti/ti_class_loader.cc
new file mode 100644
index 0000000..b68fc60
--- /dev/null
+++ b/runtime/openjdkjvmti/ti_class_loader.cc
@@ -0,0 +1,202 @@
+/* Copyright (C) 2017 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 "ti_class_loader.h"
+
+#include <limits>
+
+#include "android-base/stringprintf.h"
+
+#include "art_jvmti.h"
+#include "base/array_slice.h"
+#include "base/logging.h"
+#include "dex_file.h"
+#include "dex_file_types.h"
+#include "events-inl.h"
+#include "gc/allocation_listener.h"
+#include "gc/heap.h"
+#include "instrumentation.h"
+#include "jit/jit.h"
+#include "jit/jit_code_cache.h"
+#include "jni_env_ext-inl.h"
+#include "jvmti_allocator.h"
+#include "mirror/class.h"
+#include "mirror/class_ext.h"
+#include "mirror/object.h"
+#include "object_lock.h"
+#include "runtime.h"
+#include "ScopedLocalRef.h"
+#include "transform.h"
+
+namespace openjdkjvmti {
+
+bool ClassLoaderHelper::AddToClassLoader(art::Thread* self,
+                                         art::Handle<art::mirror::ClassLoader> loader,
+                                         const art::DexFile* dex_file) {
+  art::StackHandleScope<2> hs(self);
+  art::Handle<art::mirror::Object> java_dex_file_obj(hs.NewHandle(FindSourceDexFileObject(self,
+                                                                                          loader)));
+  if (java_dex_file_obj.IsNull()) {
+    return false;
+  }
+  art::Handle<art::mirror::LongArray> cookie(hs.NewHandle(
+      AllocateNewDexFileCookie(self, java_dex_file_obj, dex_file)));
+  if (cookie.IsNull()) {
+    return false;
+  }
+  art::ScopedAssertNoThreadSuspension nts("Replacing cookie fields in j.l.DexFile object");
+  UpdateJavaDexFile(java_dex_file_obj.Get(), cookie.Get());
+  return true;
+}
+
+void ClassLoaderHelper::UpdateJavaDexFile(art::ObjPtr<art::mirror::Object> java_dex_file,
+                                          art::ObjPtr<art::mirror::LongArray> new_cookie) {
+  art::ArtField* internal_cookie_field = java_dex_file->GetClass()->FindDeclaredInstanceField(
+      "mInternalCookie", "Ljava/lang/Object;");
+  art::ArtField* cookie_field = java_dex_file->GetClass()->FindDeclaredInstanceField(
+      "mCookie", "Ljava/lang/Object;");
+  CHECK(internal_cookie_field != nullptr);
+  art::ObjPtr<art::mirror::LongArray> orig_internal_cookie(
+      internal_cookie_field->GetObject(java_dex_file)->AsLongArray());
+  art::ObjPtr<art::mirror::LongArray> orig_cookie(
+      cookie_field->GetObject(java_dex_file)->AsLongArray());
+  internal_cookie_field->SetObject<false>(java_dex_file, new_cookie);
+  if (!orig_cookie.IsNull()) {
+    cookie_field->SetObject<false>(java_dex_file, new_cookie);
+  }
+}
+
+// TODO Really wishing I had that mirror of java.lang.DexFile now.
+art::ObjPtr<art::mirror::LongArray> ClassLoaderHelper::AllocateNewDexFileCookie(
+    art::Thread* self,
+    art::Handle<art::mirror::Object> java_dex_file_obj,
+    const art::DexFile* dex_file) {
+  art::StackHandleScope<2> hs(self);
+  // mCookie is nulled out if the DexFile has been closed but mInternalCookie sticks around until
+  // the object is finalized. Since they always point to the same array if mCookie is not null we
+  // just use the mInternalCookie field. We will update one or both of these fields later.
+  // TODO Should I get the class from the classloader or directly?
+  art::ArtField* internal_cookie_field = java_dex_file_obj->GetClass()->FindDeclaredInstanceField(
+      "mInternalCookie", "Ljava/lang/Object;");
+  // TODO Add check that mCookie is either null or same as mInternalCookie
+  CHECK(internal_cookie_field != nullptr);
+  art::Handle<art::mirror::LongArray> cookie(
+      hs.NewHandle(internal_cookie_field->GetObject(java_dex_file_obj.Get())->AsLongArray()));
+  // TODO Maybe make these non-fatal.
+  CHECK(cookie.Get() != nullptr);
+  CHECK_GE(cookie->GetLength(), 1);
+  art::Handle<art::mirror::LongArray> new_cookie(
+      hs.NewHandle(art::mirror::LongArray::Alloc(self, cookie->GetLength() + 1)));
+  if (new_cookie.Get() == nullptr) {
+    self->AssertPendingOOMException();
+    return nullptr;
+  }
+  // Copy the oat-dex field at the start.
+  // TODO Should I clear this field?
+  // TODO This is a really crappy thing here with the first element being different.
+  new_cookie->SetWithoutChecks<false>(0, cookie->GetWithoutChecks(0));
+  new_cookie->SetWithoutChecks<false>(
+      1, static_cast<int64_t>(reinterpret_cast<intptr_t>(dex_file)));
+  new_cookie->Memcpy(2, cookie.Get(), 1, cookie->GetLength() - 1);
+  return new_cookie.Get();
+}
+
+// TODO This should return the actual source java.lang.DexFile object for the klass being loaded.
+art::ObjPtr<art::mirror::Object> ClassLoaderHelper::FindSourceDexFileObject(
+    art::Thread* self, art::Handle<art::mirror::ClassLoader> loader) {
+  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;";
+
+  CHECK(!self->IsExceptionPending());
+  art::StackHandleScope<5> 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)));
+
+  // Get all the ArtFields so we can look in the BaseDexClassLoader
+  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);
+
+  // Check if loader is a BaseDexClassLoader
+  art::Handle<art::mirror::Class> loader_class(hs.NewHandle(loader->GetClass()));
+  // Currently only base_dex_loader is allowed to actually define classes but if this changes in the
+  // future we should make sure to support all class loader types.
+  if (!loader_class->IsSubClass(base_dex_loader_class.Get())) {
+    LOG(ERROR) << "The classloader is not a BaseDexClassLoader which is currently the only "
+               << "supported class loader type!";
+    return nullptr;
+  }
+  // Start navigating the fields of the loader (now known to be a BaseDexClassLoader derivative)
+  art::Handle<art::mirror::Object> path_list(
+      hs.NewHandle(path_list_field->GetObject(loader.Get())));
+  CHECK(path_list.Get() != nullptr);
+  CHECK(!self->IsExceptionPending());
+  art::Handle<art::mirror::ObjectArray<art::mirror::Object>> dex_elements_list(hs.NewHandle(
+      dex_path_list_element_field->GetObject(path_list.Get())->
+      AsObjectArray<art::mirror::Object>()));
+  CHECK(!self->IsExceptionPending());
+  CHECK(dex_elements_list.Get() != nullptr);
+  size_t num_elements = dex_elements_list->GetLength();
+  // Iterate over the DexPathList$Element to find the right one
+  for (size_t i = 0; i < num_elements; i++) {
+    art::ObjPtr<art::mirror::Object> current_element = dex_elements_list->Get(i);
+    CHECK(!current_element.IsNull());
+    // 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.
+    art::ObjPtr<art::mirror::Object> first_dex_file(
+        element_dex_file_field->GetObject(current_element));
+    if (!first_dex_file.IsNull()) {
+      return first_dex_file;
+    }
+  }
+  return nullptr;
+}
+
+}  // namespace openjdkjvmti
diff --git a/runtime/openjdkjvmti/ti_class_loader.h b/runtime/openjdkjvmti/ti_class_loader.h
new file mode 100644
index 0000000..17ed0eb
--- /dev/null
+++ b/runtime/openjdkjvmti/ti_class_loader.h
@@ -0,0 +1,96 @@
+/* Copyright (C) 2017 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_TI_CLASS_LOADER_H_
+#define ART_RUNTIME_OPENJDKJVMTI_TI_CLASS_LOADER_H_
+
+#include <string>
+
+#include <jni.h>
+
+#include "art_jvmti.h"
+#include "art_method.h"
+#include "base/array_slice.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-inl.h"
+#include "mirror/array.h"
+#include "mirror/class-inl.h"
+#include "mirror/class.h"
+#include "mirror/class_loader-inl.h"
+#include "mirror/string-inl.h"
+#include "oat_file.h"
+#include "obj_ptr.h"
+#include "scoped_thread_state_change-inl.h"
+#include "stack.h"
+#include "ti_class_definition.h"
+#include "thread_list.h"
+#include "transform.h"
+#include "utf.h"
+#include "utils/dex_cache_arrays_layout-inl.h"
+
+namespace openjdkjvmti {
+
+// Class that can redefine a single class's methods.
+// TODO We should really make this be driven by an outside class so we can do multiple classes at
+// the same time and have less required cleanup.
+class ClassLoaderHelper {
+ public:
+  static bool AddToClassLoader(art::Thread* self,
+                               art::Handle<art::mirror::ClassLoader> loader,
+                               const art::DexFile* dex_file)
+      REQUIRES_SHARED(art::Locks::mutator_lock_);
+
+  // Finds a java.lang.DexFile object that is associated with the given ClassLoader. Each of these
+  // j.l.DexFile objects holds several art::DexFile*s in it.
+  // TODO This should return the actual source java.lang.DexFile object for the klass being loaded.
+  static art::ObjPtr<art::mirror::Object> FindSourceDexFileObject(
+      art::Thread* self, art::Handle<art::mirror::ClassLoader> loader)
+      REQUIRES_SHARED(art::Locks::mutator_lock_);
+
+  static art::ObjPtr<art::mirror::LongArray> AllocateNewDexFileCookie(
+      art::Thread* self,
+      art::Handle<art::mirror::Object> java_dex_file,
+      const art::DexFile* new_dex_file) REQUIRES_SHARED(art::Locks::mutator_lock_);
+
+  static void UpdateJavaDexFile(art::ObjPtr<art::mirror::Object> java_dex_file,
+                                art::ObjPtr<art::mirror::LongArray> new_cookie)
+      REQUIRES(art::Roles::uninterruptible_) REQUIRES_SHARED(art::Locks::mutator_lock_);
+};
+
+}  // namespace openjdkjvmti
+#endif  // ART_RUNTIME_OPENJDKJVMTI_TI_CLASS_LOADER_H_
diff --git a/runtime/openjdkjvmti/ti_phase.cc b/runtime/openjdkjvmti/ti_phase.cc
index 4970288..60371cf 100644
--- a/runtime/openjdkjvmti/ti_phase.cc
+++ b/runtime/openjdkjvmti/ti_phase.cc
@@ -136,4 +136,8 @@
   art::Runtime::Current()->GetRuntimeCallbacks()->RemoveRuntimePhaseCallback(&gPhaseCallback);
 }
 
+jvmtiPhase PhaseUtil::GetPhaseUnchecked() {
+  return PhaseUtil::current_phase_;
+}
+
 }  // namespace openjdkjvmti
diff --git a/runtime/openjdkjvmti/ti_phase.h b/runtime/openjdkjvmti/ti_phase.h
index bd15fa6..851fc27 100644
--- a/runtime/openjdkjvmti/ti_phase.h
+++ b/runtime/openjdkjvmti/ti_phase.h
@@ -57,6 +57,8 @@
 
   struct PhaseCallback;
 
+  static jvmtiPhase GetPhaseUnchecked();
+
  private:
   static jvmtiPhase current_phase_;
 };
diff --git a/runtime/openjdkjvmti/ti_redefine.cc b/runtime/openjdkjvmti/ti_redefine.cc
index d2ddc21..b76d74a 100644
--- a/runtime/openjdkjvmti/ti_redefine.cc
+++ b/runtime/openjdkjvmti/ti_redefine.cc
@@ -54,6 +54,7 @@
 #include "object_lock.h"
 #include "runtime.h"
 #include "ScopedLocalRef.h"
+#include "ti_class_loader.h"
 #include "transform.h"
 
 namespace openjdkjvmti {
@@ -386,85 +387,6 @@
   return OK;
 }
 
-// TODO *MAJOR* This should return the actual source java.lang.DexFile object for the klass.
-// TODO Make mirror of DexFile and associated types to make this less hellish.
-// TODO Make mirror of BaseDexClassLoader and associated types to make this less hellish.
-art::mirror::Object* Redefiner::ClassRedefinition::FindSourceDexFileObject(
-    art::Handle<art::mirror::ClassLoader> loader) {
-  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;";
-
-  CHECK(!driver_->self_->IsExceptionPending());
-  art::StackHandleScope<11> hs(driver_->self_);
-  art::ClassLinker* class_linker = driver_->runtime_->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(
-      driver_->self_, dex_class_loader_name, null_loader)));
-
-  // Get all the ArtFields so we can look in the BaseDexClassLoader
-  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(driver_->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(driver_->self_, dex_path_list_element_name, null_loader)
-        ->FindDeclaredInstanceField("dexFile", dex_file_name);
-  CHECK(element_dex_file_field != nullptr);
-
-  // Check if loader is a BaseDexClassLoader
-  art::Handle<art::mirror::Class> loader_class(hs.NewHandle(loader->GetClass()));
-  if (!loader_class->IsSubClass(base_dex_loader_class.Get())) {
-    LOG(ERROR) << "The classloader is not a BaseDexClassLoader which is currently the only "
-               << "supported class loader type!";
-    return nullptr;
-  }
-  // Start navigating the fields of the loader (now known to be a BaseDexClassLoader derivative)
-  art::Handle<art::mirror::Object> path_list(
-      hs.NewHandle(path_list_field->GetObject(loader.Get())));
-  CHECK(path_list.Get() != nullptr);
-  CHECK(!driver_->self_->IsExceptionPending());
-  art::Handle<art::mirror::ObjectArray<art::mirror::Object>> dex_elements_list(hs.NewHandle(
-      dex_path_list_element_field->GetObject(path_list.Get())->
-      AsObjectArray<art::mirror::Object>()));
-  CHECK(!driver_->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));
-  // Iterate over the DexPathList$Element to find the right one
-  // TODO Or not ATM just return the first one.
-  for (size_t i = 0; i < num_elements; i++) {
-    current_element.Assign(dex_elements_list->Get(i));
-    CHECK(current_element.Get() != nullptr);
-    CHECK(!driver_->self_->IsExceptionPending());
-    CHECK(dex_elements_list.Get() != nullptr);
-    CHECK_EQ(current_element->GetClass(), class_linker->FindClass(driver_->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) {
-      return first_dex_file.Get();
-    }
-  }
-  return nullptr;
-}
-
 art::mirror::Class* Redefiner::ClassRedefinition::GetMirrorClass() {
   return driver_->self_->DecodeJObject(klass_)->AsClass();
 }
@@ -478,39 +400,6 @@
   return driver_->runtime_->GetClassLinker()->RegisterDexFile(*dex_file_, loader.Get());
 }
 
-// TODO Really wishing I had that mirror of java.lang.DexFile now.
-art::mirror::LongArray* Redefiner::ClassRedefinition::AllocateDexFileCookie(
-    art::Handle<art::mirror::Object> java_dex_file_obj) {
-  art::StackHandleScope<2> hs(driver_->self_);
-  // mCookie is nulled out if the DexFile has been closed but mInternalCookie sticks around until
-  // the object is finalized. Since they always point to the same array if mCookie is not null we
-  // just use the mInternalCookie field. We will update one or both of these fields later.
-  // TODO Should I get the class from the classloader or directly?
-  art::ArtField* internal_cookie_field = java_dex_file_obj->GetClass()->FindDeclaredInstanceField(
-      "mInternalCookie", "Ljava/lang/Object;");
-  // TODO Add check that mCookie is either null or same as mInternalCookie
-  CHECK(internal_cookie_field != nullptr);
-  art::Handle<art::mirror::LongArray> cookie(
-      hs.NewHandle(internal_cookie_field->GetObject(java_dex_file_obj.Get())->AsLongArray()));
-  // TODO Maybe make these non-fatal.
-  CHECK(cookie.Get() != nullptr);
-  CHECK_GE(cookie->GetLength(), 1);
-  art::Handle<art::mirror::LongArray> new_cookie(
-      hs.NewHandle(art::mirror::LongArray::Alloc(driver_->self_, cookie->GetLength() + 1)));
-  if (new_cookie.Get() == nullptr) {
-    driver_->self_->AssertPendingOOMException();
-    return nullptr;
-  }
-  // Copy the oat-dex field at the start.
-  // TODO Should I clear this field?
-  // TODO This is a really crappy thing here with the first element being different.
-  new_cookie->SetWithoutChecks<false>(0, cookie->GetWithoutChecks(0));
-  new_cookie->SetWithoutChecks<false>(
-      1, static_cast<int64_t>(reinterpret_cast<intptr_t>(dex_file_.get())));
-  new_cookie->Memcpy(2, cookie.Get(), 1, cookie->GetLength() - 1);
-  return new_cookie.Get();
-}
-
 void Redefiner::RecordFailure(jvmtiError result,
                               const std::string& class_sig,
                               const std::string& error_msg) {
@@ -854,14 +743,18 @@
     RecordFailure(ERR(INTERNAL), "Unable to find class loader!");
     return false;
   }
-  art::Handle<art::mirror::Object> dex_file_obj(hs.NewHandle(FindSourceDexFileObject(loader)));
+  art::Handle<art::mirror::Object> dex_file_obj(hs.NewHandle(
+      ClassLoaderHelper::FindSourceDexFileObject(driver_->self_, loader)));
   holder->SetJavaDexFile(klass_index, dex_file_obj.Get());
   if (dex_file_obj.Get() == nullptr) {
     // TODO Better error msg.
     RecordFailure(ERR(INTERNAL), "Unable to find class loader!");
     return false;
   }
-  holder->SetNewDexFileCookie(klass_index, AllocateDexFileCookie(dex_file_obj));
+  holder->SetNewDexFileCookie(klass_index,
+                              ClassLoaderHelper::AllocateNewDexFileCookie(driver_->self_,
+                                                                          dex_file_obj,
+                                                                          dex_file_.get()).Ptr());
   if (holder->GetNewDexFileCookie(klass_index) == nullptr) {
     driver_->self_->AssertPendingOOMException();
     driver_->self_->ClearException();
@@ -973,8 +866,10 @@
   // TODO We need to decide on & implement semantics for JNI jmethodids when we redefine methods.
   int32_t cnt = 0;
   for (Redefiner::ClassRedefinition& redef : redefinitions_) {
+    art::ScopedAssertNoThreadSuspension nts("Updating runtime objects for redefinition");
     art::mirror::Class* klass = holder.GetMirrorClass(cnt);
-    redef.UpdateJavaDexFile(holder.GetJavaDexFile(cnt), holder.GetNewDexFileCookie(cnt));
+    ClassLoaderHelper::UpdateJavaDexFile(holder.GetJavaDexFile(cnt),
+                                         holder.GetNewDexFileCookie(cnt));
     // TODO Rewrite so we don't do a stack walk for each and every class.
     redef.FindAndAllocateObsoleteMethods(klass);
     redef.UpdateClass(klass, holder.GetNewDexCache(cnt), holder.GetOriginalDexFileBytes(cnt));
@@ -1090,24 +985,6 @@
   ext->SetOriginalDexFileBytes(original_dex_file);
 }
 
-void Redefiner::ClassRedefinition::UpdateJavaDexFile(
-    art::ObjPtr<art::mirror::Object> java_dex_file,
-    art::ObjPtr<art::mirror::LongArray> new_cookie) {
-  art::ArtField* internal_cookie_field = java_dex_file->GetClass()->FindDeclaredInstanceField(
-      "mInternalCookie", "Ljava/lang/Object;");
-  art::ArtField* cookie_field = java_dex_file->GetClass()->FindDeclaredInstanceField(
-      "mCookie", "Ljava/lang/Object;");
-  CHECK(internal_cookie_field != nullptr);
-  art::ObjPtr<art::mirror::LongArray> orig_internal_cookie(
-      internal_cookie_field->GetObject(java_dex_file)->AsLongArray());
-  art::ObjPtr<art::mirror::LongArray> orig_cookie(
-      cookie_field->GetObject(java_dex_file)->AsLongArray());
-  internal_cookie_field->SetObject<false>(java_dex_file, new_cookie);
-  if (!orig_cookie.IsNull()) {
-    cookie_field->SetObject<false>(java_dex_file, new_cookie);
-  }
-}
-
 // This function does all (java) allocations we need to do for the Class being redefined.
 // TODO Change this name maybe?
 bool Redefiner::ClassRedefinition::EnsureClassAllocationsFinished() {
diff --git a/runtime/openjdkjvmti/ti_redefine.h b/runtime/openjdkjvmti/ti_redefine.h
index fdc13ee..85df6e1 100644
--- a/runtime/openjdkjvmti/ti_redefine.h
+++ b/runtime/openjdkjvmti/ti_redefine.h
@@ -127,18 +127,9 @@
     art::mirror::Class* GetMirrorClass() REQUIRES_SHARED(art::Locks::mutator_lock_);
     art::mirror::ClassLoader* GetClassLoader() REQUIRES_SHARED(art::Locks::mutator_lock_);
 
-    // This finds the java.lang.DexFile we will add the native DexFile to as part of the classpath.
-    // TODO Make sure the DexFile object returned is the one that the klass_ actually comes from.
-    art::mirror::Object* FindSourceDexFileObject(art::Handle<art::mirror::ClassLoader> loader)
-        REQUIRES_SHARED(art::Locks::mutator_lock_);
-
     art::mirror::DexCache* CreateNewDexCache(art::Handle<art::mirror::ClassLoader> loader)
         REQUIRES_SHARED(art::Locks::mutator_lock_);
 
-    // Allocates and fills the new DexFileCookie
-    art::mirror::LongArray* AllocateDexFileCookie(art::Handle<art::mirror::Object> j_dex_file_obj)
-        REQUIRES_SHARED(art::Locks::mutator_lock_);
-
     // This may return nullptr with a OOME pending if allocation fails.
     art::mirror::ByteArray* AllocateOrGetOriginalDexFileBytes()
         REQUIRES_SHARED(art::Locks::mutator_lock_);
diff --git a/runtime/openjdkjvmti/ti_search.cc b/runtime/openjdkjvmti/ti_search.cc
index 913d2b6..df80f85 100644
--- a/runtime/openjdkjvmti/ti_search.cc
+++ b/runtime/openjdkjvmti/ti_search.cc
@@ -34,15 +34,177 @@
 #include "jni.h"
 
 #include "art_jvmti.h"
+#include "base/enums.h"
 #include "base/macros.h"
 #include "class_linker.h"
 #include "dex_file.h"
+#include "jni_internal.h"
+#include "mirror/class-inl.h"
+#include "mirror/object.h"
+#include "mirror/string.h"
+#include "obj_ptr-inl.h"
 #include "runtime.h"
+#include "runtime_callbacks.h"
 #include "scoped_thread_state_change-inl.h"
 #include "ScopedLocalRef.h"
+#include "ti_phase.h"
+#include "thread-inl.h"
+#include "thread_list.h"
 
 namespace openjdkjvmti {
 
+static std::vector<std::string> gSystemOnloadSegments;
+
+static art::ObjPtr<art::mirror::Object> GetSystemProperties(art::Thread* self,
+                                                            art::ClassLinker* class_linker)
+    REQUIRES_SHARED(art::Locks::mutator_lock_) {
+  art::ObjPtr<art::mirror::Class> system_class =
+      class_linker->LookupClass(self, "Ljava/lang/System;", nullptr);
+  DCHECK(system_class != nullptr);
+  DCHECK(system_class->IsInitialized());
+
+  art::ArtField* props_field =
+      system_class->FindDeclaredStaticField("props", "Ljava/util/Properties;");
+  DCHECK(props_field != nullptr);
+
+  art::ObjPtr<art::mirror::Object> props_obj = props_field->GetObject(system_class);
+  DCHECK(props_obj != nullptr);
+
+  return props_obj;
+}
+
+static void Update() REQUIRES_SHARED(art::Locks::mutator_lock_) {
+  if (gSystemOnloadSegments.empty()) {
+    return;
+  }
+
+  // In the on-load phase we have to modify java.class.path to influence the system classloader.
+  // As this is an unmodifiable system property, we have to access the "defaults" field.
+  art::ClassLinker* class_linker = art::Runtime::Current()->GetClassLinker();
+  DCHECK(class_linker != nullptr);
+  art::Thread* self = art::Thread::Current();
+
+  // Prepare: collect classes, fields and methods.
+  art::ObjPtr<art::mirror::Class> properties_class =
+      class_linker->LookupClass(self, "Ljava/util/Properties;", nullptr);
+  DCHECK(properties_class != nullptr);
+
+  ScopedLocalRef<jobject> defaults_jobj(self->GetJniEnv(), nullptr);
+  {
+    art::ObjPtr<art::mirror::Object> props_obj = GetSystemProperties(self, class_linker);
+
+    art::ArtField* defaults_field =
+        properties_class->FindDeclaredInstanceField("defaults", "Ljava/util/Properties;");
+    DCHECK(defaults_field != nullptr);
+
+    art::ObjPtr<art::mirror::Object> defaults_obj = defaults_field->GetObject(props_obj);
+    DCHECK(defaults_obj != nullptr);
+    defaults_jobj.reset(self->GetJniEnv()->AddLocalReference<jobject>(defaults_obj));
+  }
+
+  art::ArtMethod* get_property =
+      properties_class->FindDeclaredVirtualMethod(
+          "getProperty",
+          "(Ljava/lang/String;)Ljava/lang/String;",
+          art::kRuntimePointerSize);
+  DCHECK(get_property != nullptr);
+  art::ArtMethod* set_property =
+      properties_class->FindDeclaredVirtualMethod(
+          "setProperty",
+          "(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/Object;",
+          art::kRuntimePointerSize);
+  DCHECK(set_property != nullptr);
+
+  // This is an allocation. Do this late to avoid the need for handles.
+  ScopedLocalRef<jobject> cp_jobj(self->GetJniEnv(), nullptr);
+  {
+    art::ObjPtr<art::mirror::Object> cp_key =
+        art::mirror::String::AllocFromModifiedUtf8(self, "java.class.path");
+    if (cp_key == nullptr) {
+      self->AssertPendingOOMException();
+      self->ClearException();
+      return;
+    }
+    cp_jobj.reset(self->GetJniEnv()->AddLocalReference<jobject>(cp_key));
+  }
+
+  // OK, now get the current value.
+  std::string str_value;
+  {
+    ScopedLocalRef<jobject> old_value(self->GetJniEnv(),
+                                      self->GetJniEnv()->CallObjectMethod(
+                                          defaults_jobj.get(),
+                                          art::jni::EncodeArtMethod(get_property),
+                                          cp_jobj.get()));
+    DCHECK(old_value.get() != nullptr);
+
+    str_value = self->DecodeJObject(old_value.get())->AsString()->ToModifiedUtf8();
+    self->GetJniEnv()->DeleteLocalRef(old_value.release());
+  }
+
+  // Update the value by appending the new segments.
+  for (const std::string& segment : gSystemOnloadSegments) {
+    if (!str_value.empty()) {
+      str_value += ":";
+    }
+    str_value += segment;
+  }
+  gSystemOnloadSegments.clear();
+
+  // Create the new value object.
+  ScopedLocalRef<jobject> new_val_jobj(self->GetJniEnv(), nullptr);
+  {
+    art::ObjPtr<art::mirror::Object> new_value =
+        art::mirror::String::AllocFromModifiedUtf8(self, str_value.c_str());
+    if (new_value == nullptr) {
+      self->AssertPendingOOMException();
+      self->ClearException();
+      return;
+    }
+
+    new_val_jobj.reset(self->GetJniEnv()->AddLocalReference<jobject>(new_value));
+  }
+
+  // Write to the defaults.
+  ScopedLocalRef<jobject> res_obj(self->GetJniEnv(),
+                                  self->GetJniEnv()->CallObjectMethod(defaults_jobj.get(),
+                                      art::jni::EncodeArtMethod(set_property),
+                                      cp_jobj.get(),
+                                      new_val_jobj.get()));
+  if (self->IsExceptionPending()) {
+    self->ClearException();
+    return;
+  }
+}
+
+struct SearchCallback : public art::RuntimePhaseCallback {
+  void NextRuntimePhase(RuntimePhase phase) OVERRIDE REQUIRES_SHARED(art::Locks::mutator_lock_) {
+    if (phase == RuntimePhase::kStart) {
+      // It's time to update the system properties.
+      Update();
+    }
+  }
+};
+
+static SearchCallback gSearchCallback;
+
+void SearchUtil::Register() {
+  art::Runtime* runtime = art::Runtime::Current();
+
+  art::ScopedThreadStateChange stsc(art::Thread::Current(),
+                                    art::ThreadState::kWaitingForDebuggerToAttach);
+  art::ScopedSuspendAll ssa("Add search callback");
+  runtime->GetRuntimeCallbacks()->AddRuntimePhaseCallback(&gSearchCallback);
+}
+
+void SearchUtil::Unregister() {
+  art::ScopedThreadStateChange stsc(art::Thread::Current(),
+                                    art::ThreadState::kWaitingForDebuggerToAttach);
+  art::ScopedSuspendAll ssa("Remove search callback");
+  art::Runtime* runtime = art::Runtime::Current();
+  runtime->GetRuntimeCallbacks()->RemoveRuntimePhaseCallback(&gSearchCallback);
+}
+
 jvmtiError SearchUtil::AddToBootstrapClassLoaderSearch(jvmtiEnv* env ATTRIBUTE_UNUSED,
                                                        const char* segment) {
   art::Runtime* current = art::Runtime::Current();
@@ -78,14 +240,21 @@
     return ERR(NULL_POINTER);
   }
 
-  art::Runtime* current = art::Runtime::Current();
-  if (current == nullptr) {
+  jvmtiPhase phase = PhaseUtil::GetPhaseUnchecked();
+
+  if (phase == jvmtiPhase::JVMTI_PHASE_ONLOAD) {
+    // We could try and see whether it is a valid path. We could also try to allocate Java
+    // objects to avoid later OOME.
+    gSystemOnloadSegments.push_back(segment);
+    return ERR(NONE);
+  } else if (phase != jvmtiPhase::JVMTI_PHASE_LIVE) {
     return ERR(WRONG_PHASE);
   }
-  jobject sys_class_loader = current->GetSystemClassLoader();
+
+  jobject sys_class_loader = art::Runtime::Current()->GetSystemClassLoader();
   if (sys_class_loader == nullptr) {
-    // TODO: Support classpath change in OnLoad.
-    return ERR(WRONG_PHASE);
+    // This is unexpected.
+    return ERR(INTERNAL);
   }
 
   // We'll use BaseDexClassLoader.addDexPath, as it takes care of array resizing etc. As a downside,
diff --git a/runtime/openjdkjvmti/ti_search.h b/runtime/openjdkjvmti/ti_search.h
index 6a52e80..cd7b4be 100644
--- a/runtime/openjdkjvmti/ti_search.h
+++ b/runtime/openjdkjvmti/ti_search.h
@@ -32,12 +32,17 @@
 #ifndef ART_RUNTIME_OPENJDKJVMTI_TI_SEARCH_H_
 #define ART_RUNTIME_OPENJDKJVMTI_TI_SEARCH_H_
 
+#include <vector>
+
 #include "jvmti.h"
 
 namespace openjdkjvmti {
 
 class SearchUtil {
  public:
+  static void Register();
+  static void Unregister();
+
   static jvmtiError AddToBootstrapClassLoaderSearch(jvmtiEnv* env, const char* segment);
 
   static jvmtiError AddToSystemClassLoaderSearch(jvmtiEnv* env, const char* segment);
diff --git a/test/936-search-onload/build b/test/936-search-onload/build
new file mode 100755
index 0000000..898e2e5
--- /dev/null
+++ b/test/936-search-onload/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/936-search-onload/expected.txt b/test/936-search-onload/expected.txt
new file mode 100644
index 0000000..2eec8e1
--- /dev/null
+++ b/test/936-search-onload/expected.txt
@@ -0,0 +1,3 @@
+B was loaded with boot classloader
+A was loaded with system classloader
+Done
diff --git a/test/936-search-onload/info.txt b/test/936-search-onload/info.txt
new file mode 100644
index 0000000..875a5f6
--- /dev/null
+++ b/test/936-search-onload/info.txt
@@ -0,0 +1 @@
+Tests basic functions in the jvmti plugin.
diff --git a/test/936-search-onload/run b/test/936-search-onload/run
new file mode 100755
index 0000000..67923a7
--- /dev/null
+++ b/test/936-search-onload/run
@@ -0,0 +1,21 @@
+#!/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.
+
+# This test checks whether dex files can be injected into parent classloaders. App images preload
+# classes, which will make the injection moot. Turn off app images to avoid the issue.
+
+./default-run "$@" --jvmti \
+                   --no-app-image
diff --git a/test/936-search-onload/search_onload.cc b/test/936-search-onload/search_onload.cc
new file mode 100644
index 0000000..2286a46
--- /dev/null
+++ b/test/936-search-onload/search_onload.cc
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "search_onload.h"
+
+#include <inttypes.h>
+
+#include "android-base/stringprintf.h"
+#include "base/logging.h"
+#include "base/macros.h"
+#include "jni.h"
+#include "openjdkjvmti/jvmti.h"
+#include "ScopedUtfChars.h"
+
+#include "ti-agent/common_helper.h"
+#include "ti-agent/common_load.h"
+
+namespace art {
+namespace Test936SearchOnload {
+
+jint OnLoad(JavaVM* vm,
+            char* options ATTRIBUTE_UNUSED,
+            void* reserved ATTRIBUTE_UNUSED) {
+  if (vm->GetEnv(reinterpret_cast<void**>(&jvmti_env), JVMTI_VERSION_1_0)) {
+    printf("Unable to get jvmti env!\n");
+    return 1;
+  }
+  SetAllCapabilities(jvmti_env);
+
+  char* dex_loc = getenv("DEX_LOCATION");
+  std::string dex1 = android::base::StringPrintf("%s/936-search-onload.jar", dex_loc);
+  std::string dex2 = android::base::StringPrintf("%s/936-search-onload-ex.jar", dex_loc);
+
+  jvmtiError result = jvmti_env->AddToBootstrapClassLoaderSearch(dex1.c_str());
+  if (result != JVMTI_ERROR_NONE) {
+    printf("Could not add to bootstrap classloader.\n");
+    return 1;
+  }
+
+  result = jvmti_env->AddToSystemClassLoaderSearch(dex2.c_str());
+  if (result != JVMTI_ERROR_NONE) {
+    printf("Could not add to system classloader.\n");
+    return 1;
+  }
+
+  return JNI_OK;
+}
+
+}  // namespace Test936SearchOnload
+}  // namespace art
diff --git a/test/936-search-onload/search_onload.h b/test/936-search-onload/search_onload.h
new file mode 100644
index 0000000..e556892
--- /dev/null
+++ b/test/936-search-onload/search_onload.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_936_SEARCH_ONLOAD_SEARCH_ONLOAD_H_
+#define ART_TEST_936_SEARCH_ONLOAD_SEARCH_ONLOAD_H_
+
+#include <jni.h>
+
+namespace art {
+namespace Test936SearchOnload {
+
+jint OnLoad(JavaVM* vm, char* options, void* reserved);
+
+}  // namespace Test936SearchOnload
+}  // namespace art
+
+#endif  // ART_TEST_936_SEARCH_ONLOAD_SEARCH_ONLOAD_H_
diff --git a/test/936-search-onload/src-ex/A.java b/test/936-search-onload/src-ex/A.java
new file mode 100644
index 0000000..64acb2f
--- /dev/null
+++ b/test/936-search-onload/src-ex/A.java
@@ -0,0 +1,18 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+public class A {
+}
\ No newline at end of file
diff --git a/test/936-search-onload/src/B.java b/test/936-search-onload/src/B.java
new file mode 100644
index 0000000..f1458c3
--- /dev/null
+++ b/test/936-search-onload/src/B.java
@@ -0,0 +1,18 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+public class B {
+}
\ No newline at end of file
diff --git a/test/936-search-onload/src/Main.java b/test/936-search-onload/src/Main.java
new file mode 100644
index 0000000..2e7a871
--- /dev/null
+++ b/test/936-search-onload/src/Main.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import java.util.Arrays;
+
+public class Main {
+  public static void main(String[] args) throws Exception {
+    doTest();
+  }
+
+  private static void doTest() throws Exception {
+    doTest(true, "B");
+    doTest(false, "A");
+    System.out.println("Done");
+  }
+
+  private static void doTest(boolean boot, String className) throws Exception {
+    ClassLoader expectedClassLoader;
+    if (boot) {
+      expectedClassLoader = Object.class.getClassLoader();
+    } else {
+      expectedClassLoader = ClassLoader.getSystemClassLoader();
+    }
+
+    Class<?> c = Class.forName(className, false, ClassLoader.getSystemClassLoader());
+    if (c.getClassLoader() != expectedClassLoader) {
+      throw new RuntimeException(className + "(" + boot + "): " +
+          c.getClassLoader() + " vs " + expectedClassLoader);
+    } else {
+      System.out.println(className + " was loaded with " + (boot ? "boot" : "system") +
+          " classloader");
+    }
+  }
+}
diff --git a/test/Android.bp b/test/Android.bp
index 287df13..1070645 100644
--- a/test/Android.bp
+++ b/test/Android.bp
@@ -272,6 +272,7 @@
         "929-search/search.cc",
         "931-agent-thread/agent_thread.cc",
         "933-misc-events/misc_events.cc",
+        "936-search-onload/search_onload.cc",
     ],
     shared_libs: [
         "libbase",
diff --git a/test/ti-agent/common_load.cc b/test/ti-agent/common_load.cc
index c30c2b1..621d45a 100644
--- a/test/ti-agent/common_load.cc
+++ b/test/ti-agent/common_load.cc
@@ -28,6 +28,7 @@
 
 #include "901-hello-ti-agent/basics.h"
 #include "909-attach-agent/attach.h"
+#include "936-search-onload/search_onload.h"
 
 namespace art {
 
@@ -112,6 +113,7 @@
   { "932-transform-saves", common_retransform::OnLoad, nullptr },
   { "934-load-transform", common_retransform::OnLoad, nullptr },
   { "935-non-retransformable", common_transform::OnLoad, nullptr },
+  { "936-search-onload", Test936SearchOnload::OnLoad, nullptr },
   { "937-hello-retransform-package", common_retransform::OnLoad, nullptr },
 };