ART: Refactor TI tests

Add a helper to explicitly bind native methods in a given class,
using dlsym to look up C functions in the local environment.

Add a callback helper that hooks VmInit and calls the above function
on the Main class. Use the callback helper before calling the test-
defined or shared minimal OnLoad function.

Add a binder helper that immediately binds the Main functions. Use
the helper before calling the test-defined OnAttach function.

Remove System.loadLibrary from tests. Instead rely on the explicit
binding.

In preparation for making the tests functional on device.

Test: m test-art-host
Change-Id: I12e68f070e8c6331e51d3a1fa4b9ebd8f28dfce6
diff --git a/test/ti-agent/common_helper.cc b/test/ti-agent/common_helper.cc
index 4bceef5..fd9fc38 100644
--- a/test/ti-agent/common_helper.cc
+++ b/test/ti-agent/common_helper.cc
@@ -16,14 +16,18 @@
 
 #include "ti-agent/common_helper.h"
 
+#include <dlfcn.h>
 #include <stdio.h>
 #include <sstream>
 #include <deque>
 
+#include "android-base/stringprintf.h"
 #include "art_method.h"
 #include "jni.h"
+#include "jni_internal.h"
 #include "openjdkjvmti/jvmti.h"
 #include "scoped_thread_state_change-inl.h"
+#include "ScopedLocalRef.h"
 #include "stack.h"
 #include "ti-agent/common_load.h"
 #include "utils.h"
@@ -325,4 +329,123 @@
 
 }  // namespace common_retransform
 
+static void BindMethod(jvmtiEnv* jenv,
+                       JNIEnv* env,
+                       jclass klass,
+                       jmethodID method) {
+  char* name;
+  char* signature;
+  jvmtiError name_result = jenv->GetMethodName(method, &name, &signature, nullptr);
+  if (name_result != JVMTI_ERROR_NONE) {
+    LOG(FATAL) << "Could not get methods";
+  }
+
+  ArtMethod* m = jni::DecodeArtMethod(method);
+
+  std::string names[2];
+  {
+    ScopedObjectAccess soa(Thread::Current());
+    names[0] = m->JniShortName();
+    names[1] = m->JniLongName();
+  }
+  for (const std::string& mangled_name : names) {
+    void* sym = dlsym(nullptr, mangled_name.c_str());
+    if (sym == nullptr) {
+      continue;
+    }
+
+    JNINativeMethod native_method;
+    native_method.fnPtr = sym;
+    native_method.name = name;
+    native_method.signature = signature;
+
+    env->RegisterNatives(klass, &native_method, 1);
+
+    jenv->Deallocate(reinterpret_cast<unsigned char*>(name));
+    jenv->Deallocate(reinterpret_cast<unsigned char*>(signature));
+    return;
+  }
+
+  LOG(FATAL) << "Could not find " << names[0];
+}
+
+static jclass FindClassWithSystemClassLoader(JNIEnv* env, const char* class_name) {
+  // Find the system classloader.
+  ScopedLocalRef<jclass> cl_klass(env, env->FindClass("java/lang/ClassLoader"));
+  if (cl_klass.get() == nullptr) {
+    return nullptr;
+  }
+  jmethodID getsystemclassloader_method = env->GetStaticMethodID(cl_klass.get(),
+                                                                 "getSystemClassLoader",
+                                                                 "()Ljava/lang/ClassLoader;");
+  if (getsystemclassloader_method == nullptr) {
+    return nullptr;
+  }
+  ScopedLocalRef<jobject> cl(env, env->CallStaticObjectMethod(cl_klass.get(),
+                                                              getsystemclassloader_method));
+  if (cl.get() == nullptr) {
+    return nullptr;
+  }
+
+  // Create a String of the name.
+  std::string descriptor = android::base::StringPrintf("L%s;", class_name);
+  std::string dot_name = DescriptorToDot(descriptor.c_str());
+  ScopedLocalRef<jstring> name_str(env, env->NewStringUTF(dot_name.c_str()));
+
+  // Call Class.forName with it.
+  ScopedLocalRef<jclass> c_klass(env, env->FindClass("java/lang/Class"));
+  if (c_klass.get() == nullptr) {
+    return nullptr;
+  }
+  jmethodID forname_method = env->GetStaticMethodID(
+      c_klass.get(),
+      "forName",
+      "(Ljava/lang/String;ZLjava/lang/ClassLoader;)Ljava/lang/Class;");
+  if (forname_method == nullptr) {
+    return nullptr;
+  }
+
+  return reinterpret_cast<jclass>(env->CallStaticObjectMethod(c_klass.get(),
+                                                              forname_method,
+                                                              name_str.get(),
+                                                              JNI_FALSE,
+                                                              cl.get()));
+}
+
+void BindFunctions(jvmtiEnv* jenv, JNIEnv* env, const char* class_name) {
+  // Use JNI to load the class.
+  ScopedLocalRef<jclass> klass(env, env->FindClass(class_name));
+  if (klass.get() == nullptr) {
+    // We may be called with the wrong classloader. Try explicitly using the system classloader.
+    env->ExceptionClear();
+    klass.reset(FindClassWithSystemClassLoader(env, class_name));
+    if (klass.get() == nullptr) {
+      LOG(FATAL) << "Could not load " << class_name;
+    }
+  }
+
+  // Use JVMTI to get the methods.
+  jint method_count;
+  jmethodID* methods;
+  jvmtiError methods_result = jenv->GetClassMethods(klass.get(), &method_count, &methods);
+  if (methods_result != JVMTI_ERROR_NONE) {
+    LOG(FATAL) << "Could not get methods";
+  }
+
+  // Check each method.
+  for (jint i = 0; i < method_count; ++i) {
+    jint modifiers;
+    jvmtiError mod_result = jenv->GetMethodModifiers(methods[i], &modifiers);
+    if (mod_result != JVMTI_ERROR_NONE) {
+      LOG(FATAL) << "Could not get methods";
+    }
+    constexpr jint kNative = static_cast<jint>(kAccNative);
+    if ((modifiers & kNative) != 0) {
+      BindMethod(jenv, env, klass.get(), methods[i]);
+    }
+  }
+
+  jenv->Deallocate(reinterpret_cast<unsigned char*>(methods));
+}
+
 }  // namespace art