Add list extensions agent

Add a small agent that just lists jvmti extensions.

Test: ./test/run-test --host --with-agent liblistextensions.so --dev 001-HelloWorld

Change-Id: I1335206c0a1dfc7ad311ec7ab5d75ee07df63957
diff --git a/tools/jvmti-agents/README.md b/tools/jvmti-agents/README.md
index 6f3e6dc..35dfc68 100644
--- a/tools/jvmti-agents/README.md
+++ b/tools/jvmti-agents/README.md
@@ -9,6 +9,7 @@
 * [libdumpjvmti](./dump-jvmti-state)
 * [libfieldnull](./field-null-percent)
 * [libjitload](./jit-load)
+* [liblistextensions](./list-extensions)
 * [libforceredefine](./simple-force-redefine)
 * [litifast](./ti-fast)
 * [libtitrace](./titrace)
diff --git a/tools/jvmti-agents/list-extensions/Android.bp b/tools/jvmti-agents/list-extensions/Android.bp
new file mode 100644
index 0000000..fcf883c
--- /dev/null
+++ b/tools/jvmti-agents/list-extensions/Android.bp
@@ -0,0 +1,56 @@
+//
+// 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: "listextensions-defaults",
+    host_supported: true,
+    srcs: ["list-extensions.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",
+
+    shared_libs: [
+        "libbase",
+    ],
+    header_libs: [
+        "libopenjdkjvmti_headers",
+    ],
+    multilib: {
+        lib32: {
+            suffix: "32",
+        },
+        lib64: {
+            suffix: "64",
+        },
+    },
+    symlink_preferred_arch: true,
+}
+
+art_cc_library {
+    name: "liblistextensions",
+    defaults: ["listextensions-defaults"],
+}
+
+art_cc_library {
+    name: "liblistextensionsd",
+    defaults: [
+        "art_debug_defaults",
+        "listextensions-defaults",
+    ],
+}
diff --git a/tools/jvmti-agents/list-extensions/README.md b/tools/jvmti-agents/list-extensions/README.md
new file mode 100644
index 0000000..dffac95
--- /dev/null
+++ b/tools/jvmti-agents/list-extensions/README.md
@@ -0,0 +1,56 @@
+# listextensions
+
+listextensions is a jvmti agent that will print the details of all available jvmti extension
+functions and events.
+
+# Usage
+### Build
+>    `make liblistextensions`
+
+The libraries will be built for 32-bit, 64-bit, host and target. Below examples
+assume you want to use the 64-bit version.
+
+#### ART
+>    `art -Xplugin:$ANDROID_HOST_OUT/lib64/libopenjdkjvmti.so '-agentpath:liblistextensions.so' -cp tmp/java/helloworld.dex -Xint helloworld`
+
+This will print something similar to:
+```
+dalvikvm64 I 07-30 10:47:37 154719 154719 list-extensions.cc:104] Found 13 extension functions
+dalvikvm64 I 07-30 10:47:37 154719 154719 list-extensions.cc:107] com.android.art.heap.get_object_heap_id
+dalvikvm64 I 07-30 10:47:37 154719 154719 list-extensions.cc:108]       desc: Retrieve the heap id of the object tagged with the given argument. An arbitrary object is chosen if multiple objects exist with the same tag.
+dalvikvm64 I 07-30 10:47:37 154719 154719 list-extensions.cc:109]       arguments: (count: 2)
+dalvikvm64 I 07-30 10:47:37 154719 154719 list-extensions.cc:112]               tag (IN, JLONG)
+dalvikvm64 I 07-30 10:47:37 154719 154719 list-extensions.cc:112]               heap_id (OUT, JINT)
+dalvikvm64 I 07-30 10:47:37 154719 154719 list-extensions.cc:114]       Errors: (count: 1)
+dalvikvm64 I 07-30 10:47:37 154719 154719 list-extensions.cc:118]               JVMTI_ERROR_NOT_FOUND
+dalvikvm64 I 07-30 10:47:37 154719 154719 list-extensions.cc:107] com.android.art.heap.get_heap_name
+dalvikvm64 I 07-30 10:47:37 154719 154719 list-extensions.cc:108]       desc: Retrieve the name of the heap with the given id.
+dalvikvm64 I 07-30 10:47:37 154719 154719 list-extensions.cc:109]       arguments: (count: 2)
+dalvikvm64 I 07-30 10:47:37 154719 154719 list-extensions.cc:112]               heap_id (IN, JINT)
+dalvikvm64 I 07-30 10:47:37 154719 154719 list-extensions.cc:112]               heap_name (ALLOC_BUF, CCHAR)
+dalvikvm64 I 07-30 10:47:37 154719 154719 list-extensions.cc:114]       Errors: (count: 1)
+dalvikvm64 I 07-30 10:47:37 154719 154719 list-extensions.cc:118]               JVMTI_ERROR_ILLEGAL_ARGUMENT
+...
+dalvikvm64 I 07-30 10:47:37 154719 154719 list-extensions.cc:130] Found 2 extension events
+dalvikvm64 I 07-30 10:47:37 154719 154719 list-extensions.cc:133] com.android.art.internal.ddm.publish_chunk
+dalvikvm64 I 07-30 10:47:37 154719 154719 list-extensions.cc:134]       index: 86
+dalvikvm64 I 07-30 10:47:37 154719 154719 list-extensions.cc:135]       desc: Called when there is new ddms information that the agent or other clients can use. The agent is given the 'type' of the ddms chunk and a 'data_size' byte-buffer in 'data'. The 'data' pointer is only valid for the duration of the publish_chunk event. The agent is responsible for interpreting the information present in the 'data' buffer. This is provided for backwards-compatibility support only. Agents should prefer to use relevant JVMTI events and functions above listening for this event.
+dalvikvm64 I 07-30 10:47:37 154719 154719 list-extensions.cc:136]       event arguments: (count: 4)
+dalvikvm64 I 07-30 10:47:37 154719 154719 list-extensions.cc:139]               jni_env (IN_PTR, JNIENV)
+dalvikvm64 I 07-30 10:47:37 154719 154719 list-extensions.cc:139]               type (IN, JINT)
+dalvikvm64 I 07-30 10:47:37 154719 154719 list-extensions.cc:139]               data_size (IN, JINT)
+dalvikvm64 I 07-30 10:47:37 154719 154719 list-extensions.cc:139]               data (IN_BUF, JBYTE)
+...
+```
+
+* `-Xplugin` and `-agentpath` need to be used, otherwise the agent will fail during init.
+* If using `libartd.so`, make sure to use the debug version of jvmti.
+
+>    `adb shell setenforce 0`
+>
+>    `adb push $ANDROID_PRODUCT_OUT/system/lib64/liblistextensions.so /data/local/tmp/`
+>
+>    `adb shell am start-activity --attach-agent /data/local/tmp/liblistextensions.so some.debuggable.apps/.the.app.MainActivity`
+
+#### RI
+>    `java -agentpath:liblistextensions.so -cp tmp/helloworld/classes helloworld`
\ No newline at end of file
diff --git a/tools/jvmti-agents/list-extensions/list-extensions.cc b/tools/jvmti-agents/list-extensions/list-extensions.cc
new file mode 100644
index 0000000..6d8237a
--- /dev/null
+++ b/tools/jvmti-agents/list-extensions/list-extensions.cc
@@ -0,0 +1,170 @@
+// 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.
+//
+
+#include <android-base/logging.h>
+
+#include <jni.h>
+#include <jvmti.h>
+#include <string>
+
+namespace listextensions {
+
+namespace {
+
+// Special art ti-version number. We will use this as a fallback if we cannot get a regular JVMTI
+// env.
+constexpr jint kArtTiVersion = JVMTI_VERSION_1_2 | 0x40000000;
+
+template <typename T> void Dealloc(jvmtiEnv* env, T* t) {
+  env->Deallocate(reinterpret_cast<unsigned char*>(t));
+}
+
+template <typename T, typename... Rest> void Dealloc(jvmtiEnv* env, T* t, Rest... rs) {
+  Dealloc(env, t);
+  Dealloc(env, rs...);
+}
+
+void DeallocParams(jvmtiEnv* env, jvmtiParamInfo* params, jint n_params) {
+  for (jint i = 0; i < n_params; i++) {
+    Dealloc(env, params[i].name);
+  }
+}
+
+std::ostream& operator<<(std::ostream& os, const jvmtiParamInfo& param) {
+  os << param.name << " (";
+#define CASE(type, name)       \
+  case JVMTI_##type##_##name:  \
+    os << #name;               \
+    break
+  switch (param.kind) {
+    CASE(KIND, IN);
+    CASE(KIND, IN_PTR);
+    CASE(KIND, IN_BUF);
+    CASE(KIND, ALLOC_BUF);
+    CASE(KIND, ALLOC_ALLOC_BUF);
+    CASE(KIND, OUT);
+    CASE(KIND, OUT_BUF);
+  }
+  os << ", ";
+  switch (param.base_type) {
+    CASE(TYPE, JBYTE);
+    CASE(TYPE, JCHAR);
+    CASE(TYPE, JSHORT);
+    CASE(TYPE, JINT);
+    CASE(TYPE, JLONG);
+    CASE(TYPE, JFLOAT);
+    CASE(TYPE, JDOUBLE);
+    CASE(TYPE, JBOOLEAN);
+    CASE(TYPE, JOBJECT);
+    CASE(TYPE, JTHREAD);
+    CASE(TYPE, JCLASS);
+    CASE(TYPE, JVALUE);
+    CASE(TYPE, JFIELDID);
+    CASE(TYPE, JMETHODID);
+    CASE(TYPE, CCHAR);
+    CASE(TYPE, CVOID);
+    CASE(TYPE, JNIENV);
+  }
+#undef CASE
+  os << ")";
+  return os;
+}
+
+jint SetupJvmtiEnv(JavaVM* vm) {
+  jint res = 0;
+  jvmtiEnv* env = nullptr;
+  res = vm->GetEnv(reinterpret_cast<void**>(&env), JVMTI_VERSION_1_1);
+
+  if (res != JNI_OK || env == nullptr) {
+    LOG(ERROR) << "Unable to access JVMTI, error code " << res;
+    res = vm->GetEnv(reinterpret_cast<void**>(&env), kArtTiVersion);
+    if (res != JNI_OK) {
+      return res;
+    }
+  }
+
+  // Get the extensions.
+  jint n_ext = 0;
+  jvmtiExtensionFunctionInfo* infos = nullptr;
+  if (env->GetExtensionFunctions(&n_ext, &infos) != JVMTI_ERROR_NONE) {
+    return JNI_ERR;
+  }
+  LOG(INFO) << "Found " << n_ext << " extension functions";
+  for (jint i = 0; i < n_ext; i++) {
+    const jvmtiExtensionFunctionInfo& info = infos[i];
+    LOG(INFO) << info.id;
+    LOG(INFO) << "\tdesc: " << info.short_description;
+    LOG(INFO) << "\targuments: (count: " << info.param_count << ")";
+    for (jint j = 0; j < info.param_count; j++) {
+      const jvmtiParamInfo& param = info.params[j];
+      LOG(INFO) << "\t\t" << param;
+    }
+    LOG(INFO) << "\tErrors: (count: " << info.error_count << ")";
+    for (jint j = 0; j < info.error_count; j++) {
+      char* name;
+      CHECK_EQ(JVMTI_ERROR_NONE, env->GetErrorName(info.errors[j], &name));
+      LOG(INFO) << "\t\t" << name;
+      Dealloc(env, name);
+    }
+    DeallocParams(env, info.params, info.param_count);
+    Dealloc(env, info.short_description, info.id, info.errors, info.params);
+  }
+  // Cleanup the array.
+  Dealloc(env, infos);
+  jvmtiExtensionEventInfo* events = nullptr;
+  if (env->GetExtensionEvents(&n_ext, &events) != JVMTI_ERROR_NONE) {
+    return JNI_ERR;
+  }
+  LOG(INFO) << "Found " << n_ext << " extension events";
+  for (jint i = 0; i < n_ext; i++) {
+    const jvmtiExtensionEventInfo& info = events[i];
+    LOG(INFO) << info.id;
+    LOG(INFO) << "\tindex: " << info.extension_event_index;
+    LOG(INFO) << "\tdesc: " << info.short_description;
+    LOG(INFO) << "\tevent arguments: (count: " << info.param_count << ")";
+    for (jint j = 0; j < info.param_count; j++) {
+      const jvmtiParamInfo& param = info.params[j];
+      LOG(INFO) << "\t\t" << param;
+    }
+    DeallocParams(env, info.params, info.param_count);
+    Dealloc(env, info.short_description, info.id, info.params);
+  }
+  // Cleanup the array.
+  Dealloc(env, events);
+  env->DisposeEnvironment();
+  return JNI_OK;
+}
+
+jint AgentStart(JavaVM* vm, char* options ATTRIBUTE_UNUSED, void* reserved ATTRIBUTE_UNUSED) {
+  if (SetupJvmtiEnv(vm) != JNI_OK) {
+    LOG(ERROR) << "Could not get JVMTI env or ArtTiEnv!";
+    return JNI_ERR;
+  }
+  return JNI_OK;
+}
+
+}  // namespace
+
+// Late attachment (e.g. 'am attach-agent').
+extern "C" JNIEXPORT jint JNICALL Agent_OnAttach(JavaVM* vm, char* options, void* reserved) {
+  return AgentStart(vm, options, reserved);
+}
+
+// Early attachment
+extern "C" JNIEXPORT jint JNICALL Agent_OnLoad(JavaVM* jvm, char* options, void* reserved) {
+  return AgentStart(jvm, options, reserved);
+}
+
+}  // namespace listextensions