Implement can_generate_native_method_bind capability

This capability lets one observe and even replace the implementations
of native methods when they are bound.

Test: ./test.py --host -j40

Bug: 37432636
Change-Id: I2432a8e4da1a677e8011ce495296f4ab9f42eb3e
diff --git a/runtime/art_method.cc b/runtime/art_method.cc
index 5a71be6..76fdd43 100644
--- a/runtime/art_method.cc
+++ b/runtime/art_method.cc
@@ -43,6 +43,7 @@
 #include "mirror/object-inl.h"
 #include "mirror/string.h"
 #include "oat_file-inl.h"
+#include "runtime_callbacks.h"
 #include "scoped_thread_state_change-inl.h"
 #include "well_known_classes.h"
 
@@ -372,20 +373,25 @@
   self->PopManagedStackFragment(fragment);
 }
 
-void ArtMethod::RegisterNative(const void* native_method, bool is_fast) {
+const void* ArtMethod::RegisterNative(const void* native_method, bool is_fast) {
   CHECK(IsNative()) << PrettyMethod();
   CHECK(!IsFastNative()) << PrettyMethod();
   CHECK(native_method != nullptr) << PrettyMethod();
   if (is_fast) {
     AddAccessFlags(kAccFastNative);
   }
-  SetEntryPointFromJni(native_method);
+  void* new_native_method = nullptr;
+  Runtime::Current()->GetRuntimeCallbacks()->RegisterNativeMethod(this,
+                                                                  native_method,
+                                                                  /*out*/&new_native_method);
+  SetEntryPointFromJni(new_native_method);
+  return new_native_method;
 }
 
 void ArtMethod::UnregisterNative() {
   CHECK(IsNative() && !IsFastNative()) << PrettyMethod();
   // restore stub to lookup native pointer via dlsym
-  RegisterNative(GetJniDlsymLookupStub(), false);
+  SetEntryPointFromJni(GetJniDlsymLookupStub());
 }
 
 bool ArtMethod::IsOverridableByDefaultMethod() {
diff --git a/runtime/art_method.h b/runtime/art_method.h
index 51b6576..b01b344 100644
--- a/runtime/art_method.h
+++ b/runtime/art_method.h
@@ -398,8 +398,10 @@
                      pointer_size);
   }
 
-  void RegisterNative(const void* native_method, bool is_fast)
-      REQUIRES_SHARED(Locks::mutator_lock_);
+  // Registers the native method and returns the new entry point. NB The returned entry point might
+  // be different from the native_method argument if some MethodCallback modifies it.
+  const void* RegisterNative(const void* native_method, bool is_fast)
+      REQUIRES_SHARED(Locks::mutator_lock_) WARN_UNUSED;
 
   void UnregisterNative() REQUIRES_SHARED(Locks::mutator_lock_);
 
@@ -744,6 +746,16 @@
   DISALLOW_COPY_AND_ASSIGN(ArtMethod);  // Need to use CopyFrom to deal with 32 vs 64 bits.
 };
 
+class MethodCallback {
+ public:
+  virtual ~MethodCallback() {}
+
+  virtual void RegisterNativeMethod(ArtMethod* method,
+                                    const void* original_implementation,
+                                    /*out*/void** new_implementation)
+      REQUIRES_SHARED(Locks::mutator_lock_) = 0;
+};
+
 }  // namespace art
 
 #endif  // ART_RUNTIME_ART_METHOD_H_
diff --git a/runtime/entrypoints/jni/jni_entrypoints.cc b/runtime/entrypoints/jni/jni_entrypoints.cc
index fd23ced..546e59d 100644
--- a/runtime/entrypoints/jni/jni_entrypoints.cc
+++ b/runtime/entrypoints/jni/jni_entrypoints.cc
@@ -25,10 +25,10 @@
 
 // Used by the JNI dlsym stub to find the native method to invoke if none is registered.
 #if defined(__arm__) || defined(__aarch64__)
-extern "C" void* artFindNativeMethod() {
+extern "C" const void* artFindNativeMethod() {
   Thread* self = Thread::Current();
 #else
-extern "C" void* artFindNativeMethod(Thread* self) {
+extern "C" const void* artFindNativeMethod(Thread* self) {
   DCHECK_EQ(self, Thread::Current());
 #endif
   Locks::mutator_lock_->AssertNotHeld(self);  // We come here as Native.
@@ -45,8 +45,7 @@
     return nullptr;
   } else {
     // Register so that future calls don't come here
-    method->RegisterNative(native_code, false);
-    return native_code;
+    return method->RegisterNative(native_code, false);
   }
 }
 
diff --git a/runtime/entrypoints/quick/quick_trampoline_entrypoints.cc b/runtime/entrypoints/quick/quick_trampoline_entrypoints.cc
index 354ae20..2b349e3 100644
--- a/runtime/entrypoints/quick/quick_trampoline_entrypoints.cc
+++ b/runtime/entrypoints/quick/quick_trampoline_entrypoints.cc
@@ -2025,9 +2025,9 @@
 }
 
 #if defined(__arm__) || defined(__aarch64__)
-extern "C" void* artFindNativeMethod();
+extern "C" const void* artFindNativeMethod();
 #else
-extern "C" void* artFindNativeMethod(Thread* self);
+extern "C" const void* artFindNativeMethod(Thread* self);
 #endif
 
 static uint64_t artQuickGenericJniEndJNIRef(Thread* self,
@@ -2126,7 +2126,7 @@
   }
 
   // Retrieve the stored native code.
-  void* nativeCode = called->GetEntryPointFromJni();
+  void const* nativeCode = called->GetEntryPointFromJni();
 
   // There are two cases for the content of nativeCode:
   // 1) Pointer to the native function.
diff --git a/runtime/jni_internal.cc b/runtime/jni_internal.cc
index 5418d35..b146b51 100644
--- a/runtime/jni_internal.cc
+++ b/runtime/jni_internal.cc
@@ -2277,7 +2277,8 @@
         // TODO: make this a hard register error in the future.
       }
 
-      m->RegisterNative(fnPtr, is_fast);
+      const void* final_function_ptr = m->RegisterNative(fnPtr, is_fast);
+      UNUSED(final_function_ptr);
     }
     return JNI_OK;
   }
diff --git a/runtime/openjdkjvmti/OpenjdkJvmTi.cc b/runtime/openjdkjvmti/OpenjdkJvmTi.cc
index 39e603e..c3a94b9 100644
--- a/runtime/openjdkjvmti/OpenjdkJvmTi.cc
+++ b/runtime/openjdkjvmti/OpenjdkJvmTi.cc
@@ -1556,6 +1556,7 @@
   ThreadUtil::Register(&gEventHandler);
   ClassUtil::Register(&gEventHandler);
   DumpUtil::Register(&gEventHandler);
+  MethodUtil::Register(&gEventHandler);
   SearchUtil::Register();
   HeapUtil::Register();
 
@@ -1569,6 +1570,7 @@
   ThreadUtil::Unregister();
   ClassUtil::Unregister();
   DumpUtil::Unregister();
+  MethodUtil::Unregister();
   SearchUtil::Unregister();
   HeapUtil::Unregister();
 
diff --git a/runtime/openjdkjvmti/art_jvmti.h b/runtime/openjdkjvmti/art_jvmti.h
index 2ff3a47..2a2aa4c 100644
--- a/runtime/openjdkjvmti/art_jvmti.h
+++ b/runtime/openjdkjvmti/art_jvmti.h
@@ -223,7 +223,7 @@
     .can_generate_compiled_method_load_events        = 0,
     .can_generate_monitor_events                     = 0,
     .can_generate_vm_object_alloc_events             = 1,
-    .can_generate_native_method_bind_events          = 0,
+    .can_generate_native_method_bind_events          = 1,
     .can_generate_garbage_collection_events          = 1,
     .can_generate_object_free_events                 = 1,
     .can_force_early_return                          = 0,
diff --git a/runtime/openjdkjvmti/events-inl.h b/runtime/openjdkjvmti/events-inl.h
index 233b45c..57abf31 100644
--- a/runtime/openjdkjvmti/events-inl.h
+++ b/runtime/openjdkjvmti/events-inl.h
@@ -191,6 +191,27 @@
   }
 }
 
+// Need to give a custom specialization for NativeMethodBind since it has to deal with an out
+// variable.
+template <>
+inline void EventHandler::DispatchEvent<ArtJvmtiEvent::kNativeMethodBind>(art::Thread* thread,
+                                                                          JNIEnv* jnienv,
+                                                                          jthread jni_thread,
+                                                                          jmethodID method,
+                                                                          void* cur_method,
+                                                                          void** new_method) const {
+  *new_method = cur_method;
+  for (ArtJvmTiEnv* env : envs) {
+    if (env != nullptr && ShouldDispatch<ArtJvmtiEvent::kNativeMethodBind>(env, thread)) {
+      auto callback = impl::GetCallback<ArtJvmtiEvent::kNativeMethodBind>(env);
+      (*callback)(env, jnienv, jni_thread, method, cur_method, new_method);
+      if (*new_method != nullptr) {
+        cur_method = *new_method;
+      }
+    }
+  }
+}
+
 // C++ does not allow partial template function specialization. The dispatch for our separated
 // ClassFileLoadHook event types is the same, and in the DispatchClassFileLoadHookEvent helper.
 // The following two DispatchEvent specializations dispatch to it.
diff --git a/runtime/openjdkjvmti/ti_method.cc b/runtime/openjdkjvmti/ti_method.cc
index 01bf21d..2adabba 100644
--- a/runtime/openjdkjvmti/ti_method.cc
+++ b/runtime/openjdkjvmti/ti_method.cc
@@ -35,14 +35,59 @@
 #include "art_method-inl.h"
 #include "base/enums.h"
 #include "dex_file_annotations.h"
+#include "events-inl.h"
 #include "jni_internal.h"
 #include "mirror/object_array-inl.h"
 #include "modifiers.h"
+#include "runtime_callbacks.h"
 #include "scoped_thread_state_change-inl.h"
+#include "ScopedLocalRef.h"
 #include "thread-inl.h"
+#include "thread_list.h"
 
 namespace openjdkjvmti {
 
+struct TiMethodCallback : public art::MethodCallback {
+  void RegisterNativeMethod(art::ArtMethod* method,
+                            const void* cur_method,
+                            /*out*/void** new_method)
+      OVERRIDE REQUIRES_SHARED(art::Locks::mutator_lock_) {
+    if (event_handler->IsEventEnabledAnywhere(ArtJvmtiEvent::kNativeMethodBind)) {
+      art::Thread* thread = art::Thread::Current();
+      ScopedLocalRef<jthread> thread_jni(
+          thread->GetJniEnv(), thread->GetJniEnv()->AddLocalReference<jthread>(thread->GetPeer()));
+      art::ScopedThreadSuspension sts(thread, art::ThreadState::kNative);
+      event_handler->DispatchEvent<ArtJvmtiEvent::kNativeMethodBind>(
+          thread,
+          static_cast<JNIEnv*>(thread->GetJniEnv()),
+          thread_jni.get(),
+          art::jni::EncodeArtMethod(method),
+          const_cast<void*>(cur_method),
+          new_method);
+    }
+  }
+
+  EventHandler* event_handler = nullptr;
+};
+
+TiMethodCallback gMethodCallback;
+
+void MethodUtil::Register(EventHandler* handler) {
+  gMethodCallback.event_handler = handler;
+  art::ScopedThreadStateChange stsc(art::Thread::Current(),
+                                    art::ThreadState::kWaitingForDebuggerToAttach);
+  art::ScopedSuspendAll ssa("Add method callback");
+  art::Runtime::Current()->GetRuntimeCallbacks()->AddMethodCallback(&gMethodCallback);
+}
+
+void MethodUtil::Unregister() {
+  art::ScopedThreadStateChange stsc(art::Thread::Current(),
+                                    art::ThreadState::kWaitingForDebuggerToAttach);
+  art::ScopedSuspendAll ssa("Remove method callback");
+  art::Runtime* runtime = art::Runtime::Current();
+  runtime->GetRuntimeCallbacks()->RemoveMethodCallback(&gMethodCallback);
+}
+
 jvmtiError MethodUtil::GetArgumentsSize(jvmtiEnv* env ATTRIBUTE_UNUSED,
                                         jmethodID method,
                                         jint* size_ptr) {
diff --git a/runtime/openjdkjvmti/ti_method.h b/runtime/openjdkjvmti/ti_method.h
index e5c1705..cc161c8 100644
--- a/runtime/openjdkjvmti/ti_method.h
+++ b/runtime/openjdkjvmti/ti_method.h
@@ -37,8 +37,13 @@
 
 namespace openjdkjvmti {
 
+class EventHandler;
+
 class MethodUtil {
  public:
+  static void Register(EventHandler* event_handler);
+  static void Unregister();
+
   static jvmtiError GetArgumentsSize(jvmtiEnv* env, jmethodID method, jint* size_ptr);
 
   static jvmtiError GetMaxLocals(jvmtiEnv* env, jmethodID method, jint* max_ptr);
diff --git a/runtime/runtime_callbacks.cc b/runtime/runtime_callbacks.cc
index 25324b5..16d6c13 100644
--- a/runtime/runtime_callbacks.cc
+++ b/runtime/runtime_callbacks.cc
@@ -18,6 +18,7 @@
 
 #include <algorithm>
 
+#include "art_method.h"
 #include "base/macros.h"
 #include "class_linker.h"
 #include "thread.h"
@@ -131,4 +132,25 @@
   }
 }
 
+void RuntimeCallbacks::AddMethodCallback(MethodCallback* cb) {
+  method_callbacks_.push_back(cb);
+}
+
+void RuntimeCallbacks::RemoveMethodCallback(MethodCallback* cb) {
+  Remove(cb, &method_callbacks_);
+}
+
+void RuntimeCallbacks::RegisterNativeMethod(ArtMethod* method,
+                                            const void* in_cur_method,
+                                            /*out*/void** new_method) {
+  void* cur_method = const_cast<void*>(in_cur_method);
+  *new_method = cur_method;
+  for (MethodCallback* cb : method_callbacks_) {
+    cb->RegisterNativeMethod(method, cur_method, new_method);
+    if (*new_method != nullptr) {
+      cur_method = *new_method;
+    }
+  }
+}
+
 }  // namespace art
diff --git a/runtime/runtime_callbacks.h b/runtime/runtime_callbacks.h
index d321254..e8f1824 100644
--- a/runtime/runtime_callbacks.h
+++ b/runtime/runtime_callbacks.h
@@ -31,8 +31,10 @@
 class ClassLoader;
 }  // namespace mirror
 
+class ArtMethod;
 class ClassLoadCallback;
 class Thread;
+class MethodCallback;
 class ThreadLifecycleCallback;
 
 // Note: RuntimeCallbacks uses the mutator lock to synchronize the callback lists. A thread must
@@ -110,6 +112,14 @@
                       /*out*/DexFile::ClassDef const** final_class_def)
       REQUIRES_SHARED(Locks::mutator_lock_);
 
+  void AddMethodCallback(MethodCallback* cb) REQUIRES(Locks::mutator_lock_);
+  void RemoveMethodCallback(MethodCallback* cb) REQUIRES(Locks::mutator_lock_);
+
+  void RegisterNativeMethod(ArtMethod* method,
+                            const void* original_implementation,
+                            /*out*/void** new_implementation)
+      REQUIRES_SHARED(Locks::mutator_lock_);
+
  private:
   std::vector<ThreadLifecycleCallback*> thread_callbacks_
       GUARDED_BY(Locks::mutator_lock_);
@@ -118,7 +128,9 @@
   std::vector<RuntimeSigQuitCallback*> sigquit_callbacks_
       GUARDED_BY(Locks::mutator_lock_);
   std::vector<RuntimePhaseCallback*> phase_callbacks_
-        GUARDED_BY(Locks::mutator_lock_);
+      GUARDED_BY(Locks::mutator_lock_);
+  std::vector<MethodCallback*> method_callbacks_
+      GUARDED_BY(Locks::mutator_lock_);
 };
 
 }  // namespace art
diff --git a/test/986-native-method-bind/expected.txt b/test/986-native-method-bind/expected.txt
new file mode 100644
index 0000000..189217d
--- /dev/null
+++ b/test/986-native-method-bind/expected.txt
@@ -0,0 +1,8 @@
+private static native void art.Test986$Transform.sayHi() = Java_art_Test986_00024Transform_sayHi -> Java_art_Test986_00024Transform_sayHi
+Hello
+private static native void art.Test986$Transform.sayHi() = Java_art_Test986_00024Transform_sayHi -> NoReallySayGoodbye
+Bye
+public static native void art.Main.bindAgentJNI(java.lang.String,java.lang.ClassLoader) = Java_art_Main_bindAgentJNI -> Java_art_Main_bindAgentJNI
+public static native void art.Main.bindAgentJNIForClass(java.lang.Class) = Java_art_Main_bindAgentJNIForClass -> Java_art_Main_bindAgentJNIForClass
+private static native void art.Test986.setNativeBindNotify(boolean) = Java_art_Test986_setNativeBindNotify -> Java_art_Test986_setNativeBindNotify
+private static native void art.Test986.setupNativeBindNotify() = Java_art_Test986_setupNativeBindNotify -> Java_art_Test986_setupNativeBindNotify
diff --git a/test/986-native-method-bind/info.txt b/test/986-native-method-bind/info.txt
new file mode 100644
index 0000000..1939936
--- /dev/null
+++ b/test/986-native-method-bind/info.txt
@@ -0,0 +1 @@
+Tests native-method-bind callback and native method replacement.
diff --git a/test/986-native-method-bind/native_bind.cc b/test/986-native-method-bind/native_bind.cc
new file mode 100644
index 0000000..4f93f87
--- /dev/null
+++ b/test/986-native-method-bind/native_bind.cc
@@ -0,0 +1,110 @@
+/*
+ * 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 <inttypes.h>
+#include <memory>
+#include <stdio.h>
+#include <dlfcn.h>
+
+#include "android-base/stringprintf.h"
+#include "jni.h"
+#include "jvmti.h"
+
+// Test infrastructure
+#include "jni_binder.h"
+#include "jvmti_helper.h"
+#include "test_env.h"
+#include "scoped_local_ref.h"
+
+namespace art {
+namespace Test986NativeBind {
+
+static void doUpPrintCall(JNIEnv* env, const char* function) {
+  ScopedLocalRef<jclass> klass(env, env->FindClass("art/Test986"));
+  jmethodID targetMethod = env->GetStaticMethodID(klass.get(), function, "()V");
+  env->CallStaticVoidMethod(klass.get(), targetMethod);
+}
+
+extern "C" JNIEXPORT void JNICALL Java_art_Test986_00024Transform_sayHi(
+    JNIEnv* env, jclass klass ATTRIBUTE_UNUSED) {
+  doUpPrintCall(env, "doSayHi");
+}
+
+extern "C" JNIEXPORT void JNICALL NoReallySayGoodbye(JNIEnv* env, jclass klass ATTRIBUTE_UNUSED) {
+  doUpPrintCall(env, "doSayBye");
+}
+
+static void doJvmtiMethodBind(jvmtiEnv* jvmtienv ATTRIBUTE_UNUSED,
+                              JNIEnv* env,
+                              jthread thread ATTRIBUTE_UNUSED,
+                              jmethodID m,
+                              void* address,
+                              /*out*/void** out_address) {
+  ScopedLocalRef<jclass> method_class(env, env->FindClass("java/lang/reflect/Method"));
+  ScopedLocalRef<jobject> method_obj(env, env->ToReflectedMethod(method_class.get(), m, false));
+  Dl_info addr_info;
+  if (dladdr(address, &addr_info) == 0 || addr_info.dli_sname == nullptr) {
+    ScopedLocalRef<jclass> exception_class(env, env->FindClass("java/lang/Exception"));
+    env->ThrowNew(exception_class.get(), "dladdr failure!");
+    return;
+  }
+  ScopedLocalRef<jstring> sym_name(env, env->NewStringUTF(addr_info.dli_sname));
+  ScopedLocalRef<jclass> klass(env, env->FindClass("art/Test986"));
+  jmethodID upcallMethod = env->GetStaticMethodID(
+      klass.get(),
+      "doNativeMethodBind",
+      "(Ljava/lang/reflect/Method;Ljava/lang/String;)Ljava/lang/String;");
+  if (env->ExceptionCheck()) {
+    return;
+  }
+  ScopedLocalRef<jstring> new_symbol(env,
+                                     reinterpret_cast<jstring>(
+                                         env->CallStaticObjectMethod(klass.get(),
+                                                                 upcallMethod,
+                                                                 method_obj.get(),
+                                                                 sym_name.get())));
+  const char* new_symbol_chars = env->GetStringUTFChars(new_symbol.get(), nullptr);
+  if (strcmp(new_symbol_chars, addr_info.dli_sname) != 0) {
+    *out_address = dlsym(RTLD_DEFAULT, new_symbol_chars);
+    if (*out_address == nullptr) {
+      ScopedLocalRef<jclass> exception_class(env, env->FindClass("java/lang/Exception"));
+      env->ThrowNew(exception_class.get(), "dlsym failure!");
+      return;
+    }
+  }
+  env->ReleaseStringUTFChars(new_symbol.get(), new_symbol_chars);
+}
+
+extern "C" JNIEXPORT void JNICALL Java_art_Test986_setupNativeBindNotify(
+    JNIEnv* env ATTRIBUTE_UNUSED, jclass klass ATTRIBUTE_UNUSED) {
+  jvmtiEventCallbacks cb;
+  memset(&cb, 0, sizeof(cb));
+  cb.NativeMethodBind = doJvmtiMethodBind;
+  jvmti_env->SetEventCallbacks(&cb, sizeof(cb));
+}
+
+extern "C" JNIEXPORT void JNICALL Java_art_Test986_setNativeBindNotify(
+    JNIEnv* env, jclass klass ATTRIBUTE_UNUSED, jboolean enable) {
+  jvmtiError res = jvmti_env->SetEventNotificationMode(enable ? JVMTI_ENABLE : JVMTI_DISABLE,
+                                                       JVMTI_EVENT_NATIVE_METHOD_BIND,
+                                                       nullptr);
+  if (res != JVMTI_ERROR_NONE) {
+    JvmtiErrorToException(env, jvmti_env, res);
+  }
+}
+
+}  // namespace Test986NativeBind
+}  // namespace art
diff --git a/test/986-native-method-bind/run b/test/986-native-method-bind/run
new file mode 100755
index 0000000..e92b873
--- /dev/null
+++ b/test/986-native-method-bind/run
@@ -0,0 +1,17 @@
+#!/bin/bash
+#
+# Copyright 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.
+
+./default-run "$@" --jvmti
diff --git a/test/986-native-method-bind/src/Main.java b/test/986-native-method-bind/src/Main.java
new file mode 100644
index 0000000..fac9d8e
--- /dev/null
+++ b/test/986-native-method-bind/src/Main.java
@@ -0,0 +1,21 @@
+/*
+ * 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 Main {
+  public static void main(String[] args) throws Exception {
+    art.Test986.run();
+  }
+}
diff --git a/test/986-native-method-bind/src/art/Main.java b/test/986-native-method-bind/src/art/Main.java
new file mode 100644
index 0000000..8b01920
--- /dev/null
+++ b/test/986-native-method-bind/src/art/Main.java
@@ -0,0 +1,28 @@
+/*
+ * 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.
+ */
+
+package art;
+
+// Binder class so the agent's C code has something that can be bound and exposed to tests.
+// In a package to separate cleanly and work around CTS reference issues (though this class
+// should be replaced in the CTS version).
+public class Main {
+  // Load the given class with the given classloader, and bind all native methods to corresponding
+  // C methods in the agent. Will abort if any of the steps fail.
+  public static native void bindAgentJNI(String className, ClassLoader classLoader);
+  // Same as above, giving the class directly.
+  public static native void bindAgentJNIForClass(Class<?> klass);
+}
diff --git a/test/986-native-method-bind/src/art/Redefinition.java b/test/986-native-method-bind/src/art/Redefinition.java
new file mode 100644
index 0000000..0350ab4
--- /dev/null
+++ b/test/986-native-method-bind/src/art/Redefinition.java
@@ -0,0 +1,96 @@
+/*
+ * 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.
+ */
+
+package art;
+
+import java.util.ArrayList;
+// Common Redefinition functions. Placed here for use by CTS
+public class Redefinition {
+  // Bind native functions.
+  static {
+    Main.bindAgentJNIForClass(Redefinition.class);
+  }
+
+  public static final class CommonClassDefinition {
+    public final Class<?> target;
+    public final byte[] class_file_bytes;
+    public final byte[] dex_file_bytes;
+
+    public CommonClassDefinition(Class<?> target, byte[] class_file_bytes, byte[] dex_file_bytes) {
+      this.target = target;
+      this.class_file_bytes = class_file_bytes;
+      this.dex_file_bytes = dex_file_bytes;
+    }
+  }
+
+  // A set of possible test configurations. Test should set this if they need to.
+  // This must be kept in sync with the defines in ti-agent/common_helper.cc
+  public static enum Config {
+    COMMON_REDEFINE(0),
+    COMMON_RETRANSFORM(1),
+    COMMON_TRANSFORM(2);
+
+    private final int val;
+    private Config(int val) {
+      this.val = val;
+    }
+  }
+
+  public static void setTestConfiguration(Config type) {
+    nativeSetTestConfiguration(type.val);
+  }
+
+  private static native void nativeSetTestConfiguration(int type);
+
+  // Transforms the class
+  public static native void doCommonClassRedefinition(Class<?> target,
+                                                      byte[] classfile,
+                                                      byte[] dexfile);
+
+  public static void doMultiClassRedefinition(CommonClassDefinition... defs) {
+    ArrayList<Class<?>> classes = new ArrayList<>();
+    ArrayList<byte[]> class_files = new ArrayList<>();
+    ArrayList<byte[]> dex_files = new ArrayList<>();
+
+    for (CommonClassDefinition d : defs) {
+      classes.add(d.target);
+      class_files.add(d.class_file_bytes);
+      dex_files.add(d.dex_file_bytes);
+    }
+    doCommonMultiClassRedefinition(classes.toArray(new Class<?>[0]),
+                                   class_files.toArray(new byte[0][]),
+                                   dex_files.toArray(new byte[0][]));
+  }
+
+  public static void addMultiTransformationResults(CommonClassDefinition... defs) {
+    for (CommonClassDefinition d : defs) {
+      addCommonTransformationResult(d.target.getCanonicalName(),
+                                    d.class_file_bytes,
+                                    d.dex_file_bytes);
+    }
+  }
+
+  public static native void doCommonMultiClassRedefinition(Class<?>[] targets,
+                                                           byte[][] classfiles,
+                                                           byte[][] dexfiles);
+  public static native void doCommonClassRetransformation(Class<?>... target);
+  public static native void setPopRetransformations(boolean pop);
+  public static native void popTransformationFor(String name);
+  public static native void enableCommonRetransformation(boolean enable);
+  public static native void addCommonTransformationResult(String target_name,
+                                                          byte[] class_bytes,
+                                                          byte[] dex_bytes);
+}
diff --git a/test/986-native-method-bind/src/art/Test986.java b/test/986-native-method-bind/src/art/Test986.java
new file mode 100644
index 0000000..edd2e9d
--- /dev/null
+++ b/test/986-native-method-bind/src/art/Test986.java
@@ -0,0 +1,82 @@
+/*
+ * 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.
+ */
+
+package art;
+
+import java.lang.reflect.Method;
+import java.util.HashMap;
+
+public class Test986 {
+  static {
+    // NB This is called before any setup is done so we don't need to worry about getting bind
+    // events.
+    Main.bindAgentJNIForClass(Test986.class);
+  }
+
+
+  private static final HashMap<Method, String> SymbolMap = new HashMap<>();
+
+  // A class with a native method we can play with.
+  static class Transform {
+    private static native void sayHi();
+  }
+
+  public static void run() throws Exception {
+    setupNativeBindNotify();
+    setNativeBindNotify(true);
+    doTest();
+  }
+
+  private static void setNativeTransform(Method method, String dest) {
+    SymbolMap.put(method, dest);
+  }
+
+  /**
+   * Notifies java that a native method bind has occurred and requests the new symbol to bind to.
+   */
+  public static String doNativeMethodBind(Method method, String nativeSym) {
+    // Disable native bind notify for now to avoid infinite loops.
+    setNativeBindNotify(false);
+    String transSym = SymbolMap.getOrDefault(method, nativeSym);
+    System.out.println(method + " = " + nativeSym + " -> " + transSym);
+    setNativeBindNotify(true);
+    return transSym;
+  }
+
+  public static void doTest() throws Exception {
+    Method say_hi_method = Transform.class.getDeclaredMethod("sayHi");
+    // TODO We should test auto-binding but due to the way this is run that will be annoying.
+    Main.bindAgentJNIForClass(Transform.class);
+    Transform.sayHi();
+    setNativeTransform(say_hi_method, "NoReallySayGoodbye");
+    Main.bindAgentJNIForClass(Transform.class);
+    Transform.sayHi();
+    Main.bindAgentJNIForClass(Main.class);
+    Main.bindAgentJNIForClass(Test986.class);
+  }
+
+  // Functions called from native code.
+  public static void doSayHi() {
+    System.out.println("Hello");
+  }
+
+  public static void doSayBye() {
+    System.out.println("Bye");
+  }
+
+  private static native void setNativeBindNotify(boolean enable);
+  private static native void setupNativeBindNotify();
+}
diff --git a/test/Android.bp b/test/Android.bp
index c5d96da..15b3f8a 100644
--- a/test/Android.bp
+++ b/test/Android.bp
@@ -275,6 +275,7 @@
         "933-misc-events/misc_events.cc",
         "945-obsolete-native/obsolete_native.cc",
         "984-obsolete-invoke/obsolete_invoke.cc",
+        "986-native-method-bind/native_bind.cc",
     ],
     shared_libs: [
         "libbase",