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