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