Add chain-agents agent

This agent is able to chain-load other agents listed in a given file.
This is useful for testing and using persistent JVMTI agents, which
might need to have specific arguments passed to them.

Test: Install debuggable apk (here com.antonioleiva.bandhookkotlin)
  walleye:/ $ run-as com.antonioleiva.bandhookkotlin sh
  walleye:/data/data/com.antonioleiva.bandhookkotlin $ mkdir code_cache/startup_agents
  walleye:/data/data/com.antonioleiva.bandhookkotlin $ cp /data/local/tmp/libtifasts32.so code_cache
  walleye:/data/data/com.antonioleiva.bandhookkotlin $ cp /data/local/tmp/libtifasts64.so code_cache
  walleye:/data/data/com.antonioleiva.bandhookkotlin $ cp /data/local/tmp/libchainagentss32.so code_cache/startup_agents/
  walleye:/data/data/com.antonioleiva.bandhookkotlin $ cp /data/local/tmp/libchainagentss64.so code_cache/startup_agents/
  walleye:/data/data/com.antonioleiva.bandhookkotlin $ echo $PWD/code_cache/libtifasts32.so=log,ClassLoad > chain_agents.txt
  walleye:/data/data/com.antonioleiva.bandhookkotlin $ echo $PWD/code_cache/libtifasts64.so=log,ClassLoad >> chain_agents.txt
  Start bandhookkotlin
  Examine logcat

Bug: 135627501
Change-Id: Icd97e8cce6c540b8d1ee5e5efd7b2587707786d3
diff --git a/tools/jvmti-agents/chain-agents/Android.bp b/tools/jvmti-agents/chain-agents/Android.bp
new file mode 100644
index 0000000..148082d
--- /dev/null
+++ b/tools/jvmti-agents/chain-agents/Android.bp
@@ -0,0 +1,84 @@
+//
+// Copyright (C) 2019 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.
+//
+
+// Build variants {target,host} x {debug,ndebug} x {32,64}
+
+cc_defaults {
+    name: "chainagents-base-defaults",
+    srcs: ["chainagents.cc"],
+    defaults: ["art_defaults"],
+
+    // Note that this tool needs to be built for both 32-bit and 64-bit since it requires
+    // to be same ISA as what it is attached to.
+    compile_multilib: "both",
+
+    header_libs: [
+        "libopenjdkjvmti_headers",
+        "libnativehelper_header_only",
+        "jni_headers",
+    ],
+    multilib: {
+        lib32: {
+            suffix: "32",
+        },
+        lib64: {
+            suffix: "64",
+        },
+    },
+    symlink_preferred_arch: true,
+}
+
+cc_defaults {
+    name: "chainagents-defaults",
+    host_supported: true,
+    shared_libs: [
+        "libbase",
+    ],
+    defaults: ["chainagents-base-defaults"],
+}
+
+art_cc_library {
+    name: "libchainagents",
+    defaults: ["chainagents-defaults"],
+}
+
+art_cc_library {
+    name: "libchainagentsd",
+    defaults: [
+        "art_debug_defaults",
+        "chainagents-defaults",
+    ],
+}
+
+cc_defaults {
+    name: "chainagents-static-defaults",
+    host_supported: false,
+    defaults: ["chainagents-base-defaults"],
+
+    shared_libs: [
+        "liblog",
+    ],
+    static_libs: [
+        "libbase_ndk",
+    ],
+    sdk_version: "current",
+    stl: "c++_static",
+}
+
+cc_library {
+    name: "libchainagentss",
+    defaults: ["chainagents-static-defaults"],
+}
diff --git a/tools/jvmti-agents/chain-agents/README.md b/tools/jvmti-agents/chain-agents/README.md
new file mode 100644
index 0000000..c76ac28
--- /dev/null
+++ b/tools/jvmti-agents/chain-agents/README.md
@@ -0,0 +1,36 @@
+# chainagent
+
+The chainagents agent is a JVMTI agent that chain loads other agents from a file found at a
+location relative to a passed in path. It can be used in combination with android startup_agents
+in order to implement more complicated agent-loading rules.
+
+It will open the file `chain_agents.txt` from the directory passed in as an argument and read it
+line-by-line loading the agents (with the arguments) listed in the file.
+
+Errors in loading are logged then ignored.
+
+# Usage
+### Build
+>    `m libchainagents`
+
+The libraries will be built for 32-bit, 64-bit, host and target. Below examples
+assume you want to use the 64-bit version.
+
+### Command Line
+#### ART
+>    `art -Xplugin:$ANDROID_HOST_OUT/lib64/libopenjdkjvmti.so -agentpath:$ANDROID_HOST_OUT/lib64/libchainagents.so=/some/path/here -Xint helloworld`
+
+* `-Xplugin` and `-agentpath` need to be used, otherwise libtitrace agent will fail during init.
+* If using `libartd.so`, make sure to use the debug version of jvmti.
+
+### chain_agents.txt file format.
+
+The chain-agents file is a list of agent files and arguments to load in the same format as the
+`-agentpath` argument.
+
+#### Example chain_agents.txt file
+
+```
+/data/data/com.android.launcher3/code_cache/libtifast32.so=ClassLoad
+/data/data/com.android.launcher3/code_cache/libtifast64.so=ClassLoad
+```
diff --git a/tools/jvmti-agents/chain-agents/chainagents.cc b/tools/jvmti-agents/chain-agents/chainagents.cc
new file mode 100644
index 0000000..1242409
--- /dev/null
+++ b/tools/jvmti-agents/chain-agents/chainagents.cc
@@ -0,0 +1,136 @@
+// 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 <android-base/logging.h>
+#include <dlfcn.h>
+#include <jni.h>
+#include <jvmti.h>
+
+#include <atomic>
+#include <fstream>
+#include <iomanip>
+#include <iostream>
+#include <memory>
+#include <mutex>
+#include <sstream>
+#include <string>
+#include <unordered_map>
+#include <unordered_set>
+#include <vector>
+
+namespace chainagents {
+
+static constexpr const char* kChainFile = "chain_agents.txt";
+static constexpr const char* kOnLoad = "Agent_OnLoad";
+static constexpr const char* kOnAttach = "Agent_OnAttach";
+static constexpr const char* kOnUnload = "Agent_OnUnload";
+using AgentLoadFunction = jint (*)(JavaVM*, const char*, void*);
+using AgentUnloadFunction = jint (*)(JavaVM*);
+
+// Global namespace. Shared by every usage of this wrapper unfortunately.
+// We need to keep track of them to call Agent_OnUnload.
+static std::mutex unload_mutex;
+
+struct Unloader {
+  AgentUnloadFunction unload;
+};
+static std::vector<Unloader> unload_functions;
+
+enum class StartType {
+  OnAttach,
+  OnLoad,
+};
+
+static std::pair<std::string, std::string> Split(std::string source, char delim) {
+  std::string first(source.substr(0, source.find(delim)));
+  if (source.find(delim) == std::string::npos) {
+    return std::pair(first, "");
+  } else {
+    return std::pair(first, source.substr(source.find(delim) + 1));
+  }
+}
+
+static jint Load(StartType start,
+                 JavaVM* vm,
+                 void* reserved,
+                 const std::pair<std::string, std::string>& lib_and_args,
+                 /*out*/ std::string* err) {
+  void* handle = dlopen(lib_and_args.first.c_str(), RTLD_LAZY);
+  std::ostringstream oss;
+  if (handle == nullptr) {
+    oss << "Failed to dlopen due to " << dlerror();
+    *err = oss.str();
+    return JNI_ERR;
+  }
+  AgentLoadFunction alf = reinterpret_cast<AgentLoadFunction>(
+      dlsym(handle, start == StartType::OnLoad ? kOnLoad : kOnAttach));
+  if (alf == nullptr) {
+    oss << "Failed to dlsym " << (start == StartType::OnLoad ? kOnLoad : kOnAttach) << " due to "
+        << dlerror();
+    *err = oss.str();
+    return JNI_ERR;
+  }
+  jint res = alf(vm, lib_and_args.second.c_str(), reserved);
+  if (res != JNI_OK) {
+    *err = "load function failed!";
+    return res;
+  }
+  AgentUnloadFunction auf = reinterpret_cast<AgentUnloadFunction>(dlsym(handle, kOnUnload));
+  if (auf != nullptr) {
+    unload_functions.push_back({ auf });
+  }
+  return JNI_OK;
+}
+
+static jint AgentStart(StartType start, JavaVM* vm, char* options, void* reserved) {
+  std::string input_file(options);
+  input_file = input_file + "/" + kChainFile;
+  std::ifstream input(input_file);
+  std::string line;
+  std::lock_guard<std::mutex> mu(unload_mutex);
+  while (std::getline(input, line)) {
+    std::pair<std::string, std::string> lib_and_args(Split(line, '='));
+    std::string err;
+    jint new_res = Load(start, vm, reserved, lib_and_args, &err);
+    if (new_res != JNI_OK) {
+      PLOG(WARNING) << "Failed to load library " << lib_and_args.first
+                    << " (arguments: " << lib_and_args.second << ") due to " << err;
+    }
+  }
+  return JNI_OK;
+}
+
+// Late attachment (e.g. 'am attach-agent').
+extern "C" JNIEXPORT jint JNICALL Agent_OnAttach(JavaVM* vm, char* options, void* reserved) {
+  return AgentStart(StartType::OnAttach, vm, options, reserved);
+}
+
+// Early attachment
+// (e.g. 'java
+// -agentpath:/path/to/libwrapagentproperties.so=/path/to/propfile,/path/to/wrapped.so=[ops]').
+extern "C" JNIEXPORT jint JNICALL Agent_OnLoad(JavaVM* jvm, char* options, void* reserved) {
+  return AgentStart(StartType::OnLoad, jvm, options, reserved);
+}
+
+extern "C" JNIEXPORT void JNICALL Agent_OnUnload(JavaVM* jvm) {
+  std::lock_guard<std::mutex> lk(unload_mutex);
+  for (const Unloader& u : unload_functions) {
+    u.unload(jvm);
+    // Don't dlclose since some agents expect to still have code loaded after this.
+  }
+  unload_functions.clear();
+}
+
+}  // namespace chainagents