ART: Take classloader into account for AttachAgent

Take a classloader into account for attaching an agent, if given.
This will use the library path from the classloader to find the
agent library.

Switch to libnativeloader to do the actual loading.

Bug: 70901841
Test: m test-art-host
Change-Id: I59ec55178792bc738fb06e0299522e207dd4a784
diff --git a/adbconnection/adbconnection.cc b/adbconnection/adbconnection.cc
index 2a9982a..a5c885a 100644
--- a/adbconnection/adbconnection.cc
+++ b/adbconnection/adbconnection.cc
@@ -583,7 +583,9 @@
           DCHECK(!agent_listening_);
           // Load the agent now!
           self->AssertNoPendingException();
-          art::Runtime::Current()->AttachAgent(MakeAgentArg());
+          art::Runtime::Current()->AttachAgent(/* JNIEnv* */ nullptr,
+                                               MakeAgentArg(),
+                                               /* classloader */ nullptr);
           if (self->IsExceptionPending()) {
             LOG(ERROR) << "Failed to load agent " << agent_name_;
             art::ScopedObjectAccess soa(self);
diff --git a/runtime/native/dalvik_system_VMDebug.cc b/runtime/native/dalvik_system_VMDebug.cc
index 787646d..6da34bc 100644
--- a/runtime/native/dalvik_system_VMDebug.cc
+++ b/runtime/native/dalvik_system_VMDebug.cc
@@ -548,7 +548,7 @@
   return result;
 }
 
-static void VMDebug_attachAgent(JNIEnv* env, jclass, jstring agent) {
+static void VMDebug_nativeAttachAgent(JNIEnv* env, jclass, jstring agent, jobject classloader) {
   if (agent == nullptr) {
     ScopedObjectAccess soa(env);
     ThrowNullPointerException("agent is null");
@@ -570,7 +570,7 @@
     filename = chars.c_str();
   }
 
-  Runtime::Current()->AttachAgent(filename);
+  Runtime::Current()->AttachAgent(env, filename, classloader);
 }
 
 static JNINativeMethod gMethods[] = {
@@ -607,7 +607,7 @@
   FAST_NATIVE_METHOD(VMDebug, threadCpuTimeNanos, "()J"),
   NATIVE_METHOD(VMDebug, getRuntimeStatInternal, "(I)Ljava/lang/String;"),
   NATIVE_METHOD(VMDebug, getRuntimeStatsInternal, "()[Ljava/lang/String;"),
-  NATIVE_METHOD(VMDebug, attachAgent, "(Ljava/lang/String;)V"),
+  NATIVE_METHOD(VMDebug, nativeAttachAgent, "(Ljava/lang/String;Ljava/lang/ClassLoader;)V"),
 };
 
 void register_dalvik_system_VMDebug(JNIEnv* env) {
diff --git a/runtime/runtime.cc b/runtime/runtime.cc
index e1610ab..c61d7ef 100644
--- a/runtime/runtime.cc
+++ b/runtime/runtime.cc
@@ -1575,7 +1575,7 @@
 //   revisit this and make sure we're doing this on the right thread
 //   (and we synchronize access to any shared data structures like "agents_")
 //
-void Runtime::AttachAgent(const std::string& agent_arg) {
+void Runtime::AttachAgent(JNIEnv* env, const std::string& agent_arg, jobject class_loader) {
   std::string error_msg;
   if (!EnsureJvmtiPlugin(this, &plugins_, &error_msg)) {
     LOG(WARNING) << "Could not load plugin: " << error_msg;
@@ -1588,7 +1588,7 @@
 
   int res = 0;
   ti::LoadError error;
-  std::unique_ptr<ti::Agent> agent = agent_spec.Attach(&res, &error, &error_msg);
+  std::unique_ptr<ti::Agent> agent = agent_spec.Attach(env, class_loader, &res, &error, &error_msg);
 
   if (agent != nullptr) {
     agents_.push_back(std::move(agent));
diff --git a/runtime/runtime.h b/runtime/runtime.h
index ac29ed4..4da2cd4 100644
--- a/runtime/runtime.h
+++ b/runtime/runtime.h
@@ -661,7 +661,7 @@
   void AddSystemWeakHolder(gc::AbstractSystemWeakHolder* holder);
   void RemoveSystemWeakHolder(gc::AbstractSystemWeakHolder* holder);
 
-  void AttachAgent(const std::string& agent_arg);
+  void AttachAgent(JNIEnv* env, const std::string& agent_arg, jobject class_loader);
 
   const std::list<std::unique_ptr<ti::Agent>>& GetAgents() const {
     return agents_;
diff --git a/runtime/ti/agent.cc b/runtime/ti/agent.cc
index aa09d84..62bdde6 100644
--- a/runtime/ti/agent.cc
+++ b/runtime/ti/agent.cc
@@ -17,6 +17,8 @@
 #include "agent.h"
 
 #include "android-base/stringprintf.h"
+#include "nativehelper/scoped_local_ref.h"
+#include "nativeloader/native_loader.h"
 
 #include "base/strlcpy.h"
 #include "java_vm_ext.h"
@@ -47,20 +49,24 @@
                                        /*out*/LoadError* error,
                                        /*out*/std::string* error_msg) {
   VLOG(agents) << "Loading agent: " << name_ << " " << args_;
-  return DoLoadHelper(false, call_res, error, error_msg);
+  return DoLoadHelper(nullptr, false, nullptr, call_res, error, error_msg);
 }
 
 // Tries to attach the agent using its OnAttach method. Returns true on success.
-std::unique_ptr<Agent> AgentSpec::Attach(/*out*/jint* call_res,
+std::unique_ptr<Agent> AgentSpec::Attach(JNIEnv* env,
+                                         jobject class_loader,
+                                         /*out*/jint* call_res,
                                          /*out*/LoadError* error,
                                          /*out*/std::string* error_msg) {
   VLOG(agents) << "Attaching agent: " << name_ << " " << args_;
-  return DoLoadHelper(true, call_res, error, error_msg);
+  return DoLoadHelper(env, true, class_loader, call_res, error, error_msg);
 }
 
 
 // TODO We need to acquire some locks probably.
-std::unique_ptr<Agent> AgentSpec::DoLoadHelper(bool attaching,
+std::unique_ptr<Agent> AgentSpec::DoLoadHelper(JNIEnv* env,
+                                               bool attaching,
+                                               jobject class_loader,
                                                /*out*/jint* call_res,
                                                /*out*/LoadError* error,
                                                /*out*/std::string* error_msg) {
@@ -68,7 +74,7 @@
   DCHECK(call_res != nullptr);
   DCHECK(error_msg != nullptr);
 
-  std::unique_ptr<Agent> agent = DoDlOpen(error, error_msg);
+  std::unique_ptr<Agent> agent = DoDlOpen(env, class_loader, error, error_msg);
   if (agent == nullptr) {
     VLOG(agents) << "err: " << *error_msg;
     return nullptr;
@@ -99,15 +105,37 @@
   return agent;
 }
 
-std::unique_ptr<Agent> AgentSpec::DoDlOpen(/*out*/LoadError* error, /*out*/std::string* error_msg) {
+std::unique_ptr<Agent> AgentSpec::DoDlOpen(JNIEnv* env,
+                                           jobject class_loader,
+                                           /*out*/LoadError* error,
+                                           /*out*/std::string* error_msg) {
   DCHECK(error_msg != nullptr);
 
-  void* dlopen_handle = dlopen(name_.c_str(), RTLD_LAZY);
+  ScopedLocalRef<jstring> library_path(env,
+                                       class_loader == nullptr
+                                           ? nullptr
+                                           : JavaVMExt::GetLibrarySearchPath(env, class_loader));
+
+  bool needs_native_bridge = false;
+  void* dlopen_handle = android::OpenNativeLibrary(env,
+                                                   Runtime::Current()->GetTargetSdkVersion(),
+                                                   name_.c_str(),
+                                                   class_loader,
+                                                   library_path.get(),
+                                                   &needs_native_bridge,
+                                                   error_msg);
   if (dlopen_handle == nullptr) {
     *error_msg = StringPrintf("Unable to dlopen %s: %s", name_.c_str(), dlerror());
     *error = kLoadingError;
     return nullptr;
   }
+  if (needs_native_bridge) {
+    // TODO: Consider support?
+    android::CloseNativeLibrary(dlopen_handle, needs_native_bridge);
+    *error_msg = StringPrintf("Native-bridge agents unsupported: %s", name_.c_str());
+    *error = kLoadingError;
+    return nullptr;
+  }
 
   std::unique_ptr<Agent> agent(new Agent(name_, dlopen_handle));
   agent->PopulateFunctions();
@@ -131,8 +159,9 @@
     if (onunload_ != nullptr) {
       onunload_(Runtime::Current()->GetJavaVM());
     }
-    // Don't actually dlclose since some agents assume they will never get unloaded. Since this only
-    // happens when the runtime is shutting down anyway this isn't a big deal.
+    // Don't actually android::CloseNativeLibrary since some agents assume they will never get
+    // unloaded. Since this only happens when the runtime is shutting down anyway this isn't a big
+    // deal.
     dlopen_handle_ = nullptr;
     onload_ = nullptr;
     onattach_ = nullptr;
diff --git a/runtime/ti/agent.h b/runtime/ti/agent.h
index 68b4948..24a6f1c 100644
--- a/runtime/ti/agent.h
+++ b/runtime/ti/agent.h
@@ -56,14 +56,21 @@
                               /*out*/std::string* error_msg);
 
   // Tries to attach the agent using its OnAttach method. Returns true on success.
-  std::unique_ptr<Agent> Attach(/*out*/jint* call_res,
+  std::unique_ptr<Agent> Attach(JNIEnv* env,
+                                jobject class_loader,
+                                /*out*/jint* call_res,
                                 /*out*/LoadError* error,
                                 /*out*/std::string* error_msg);
 
  private:
-  std::unique_ptr<Agent> DoDlOpen(/*out*/LoadError* error, /*out*/std::string* error_msg);
+  std::unique_ptr<Agent> DoDlOpen(JNIEnv* env,
+                                  jobject class_loader,
+                                  /*out*/LoadError* error,
+                                  /*out*/std::string* error_msg);
 
-  std::unique_ptr<Agent> DoLoadHelper(bool attaching,
+  std::unique_ptr<Agent> DoLoadHelper(JNIEnv* env,
+                                      bool attaching,
+                                      jobject class_loader,
                                       /*out*/jint* call_res,
                                       /*out*/LoadError* error,
                                       /*out*/std::string* error_msg);
diff --git a/test/909-attach-agent/expected.txt b/test/909-attach-agent/expected.txt
index c0bccd6..4d687f5 100644
--- a/test/909-attach-agent/expected.txt
+++ b/test/909-attach-agent/expected.txt
@@ -1,11 +1,12 @@
 Hello, world!
 Attached Agent for test 909-attach-agent
-Goodbye!
-Hello, world!
 Attached Agent for test 909-attach-agent
 Goodbye!
 Hello, world!
-java.io.IOException: Process is not debuggable.
-	at dalvik.system.VMDebug.attachAgent(Native Method)
-	at Main.main(Main.java:27)
+Attached Agent for test 909-attach-agent
+Attached Agent for test 909-attach-agent
+Goodbye!
+Hello, world!
+Process is not debuggable.
+Process is not debuggable.
 Goodbye!
diff --git a/test/909-attach-agent/src-art/Main.java b/test/909-attach-agent/src-art/Main.java
index 25ebd57..705e61e 100644
--- a/test/909-attach-agent/src-art/Main.java
+++ b/test/909-attach-agent/src-art/Main.java
@@ -14,7 +14,13 @@
  * limitations under the License.
  */
 
+import dalvik.system.PathClassLoader;
 import dalvik.system.VMDebug;
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.File;
 import java.io.IOException;
 
 public class Main {
@@ -26,10 +32,76 @@
         try {
           VMDebug.attachAgent(agent);
         } catch(IOException e) {
-          e.printStackTrace(System.out);
+          System.out.println(e.getMessage());
         }
       }
     }
+    attachWithClassLoader(args);
     System.out.println("Goodbye!");
   }
+
+  private static void attachWithClassLoader(String[] args) {
+    for(String a : args) {
+      if(a.startsWith("agent:")) {
+        String agentName = a.substring(6, a.indexOf('='));
+        File tmp = null;
+        try {
+          tmp = File.createTempFile("lib", ".so");
+          prepare(agentName, tmp);
+
+          String newAgentName = tmp.getName();
+          String agent = a.substring(6).replace(agentName, newAgentName);
+
+          ClassLoader cl = new PathClassLoader("", tmp.getParentFile().getAbsolutePath(),
+              Main.class.getClassLoader());
+          try {
+            VMDebug.attachAgent(agent, cl);
+          } catch(IOException e) {
+            System.out.println(e.getMessage());
+          }
+        } catch (Exception e) {
+          e.printStackTrace(System.out);
+        } finally {
+          if (tmp != null) {
+            tmp.delete();
+          }
+        }
+      }
+    }
+  }
+
+  private static void prepare(String in, File tmp) throws Exception {
+    // Find the original.
+    File orig = find(in);
+    if (orig == null) {
+      throw new RuntimeException("Could not find " + in);
+    }
+    // Copy the original.
+    {
+      BufferedInputStream bis = new BufferedInputStream(new FileInputStream(orig));
+      BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(tmp));
+      byte[] buf = new byte[16 * 1024];
+      for (;;) {
+        int r = bis.read(buf, 0, buf.length);
+        if (r < 0) {
+          break;
+        } else if (r > 0) {
+          bos.write(buf, 0, r);
+        }
+      }
+      bos.close();
+      bis.close();
+    }
+  }
+
+  private static File find(String in) {
+    String libraryPath = System.getProperty("java.library.path");
+    for (String path : libraryPath.split(":")) {
+      File f = new File(path + "/" + in);
+      if (f.exists()) {
+        return f;
+      }
+    }
+    return null;
+  }
 }