Add extension and agent for dumping internal jvmti plugin data.

When debugging openjdkjvmti plugin issues it can be useful to dump
internal state somewhere it can be examined. This adds a new extension
method that will let agents get a view of the deopt state of the
plugin and an agent that prints this information to LOG(INFO) on
SIGQUIT.

Test: ./test.py --host
Change-Id: Ia265a5bcca31a2df5ac930ddc2ecffb57d3db911
diff --git a/openjdkjvmti/deopt_manager.cc b/openjdkjvmti/deopt_manager.cc
index ee77b7b..ec29f2c 100644
--- a/openjdkjvmti/deopt_manager.cc
+++ b/openjdkjvmti/deopt_manager.cc
@@ -30,6 +30,8 @@
  */
 
 #include <functional>
+#include <iosfwd>
+#include <mutex>
 
 #include "deopt_manager.h"
 
@@ -109,6 +111,53 @@
   callbacks->RemoveMethodInspectionCallback(&inspection_callback_);
 }
 
+void DeoptManager::DumpDeoptInfo(art::Thread* self, std::ostream& stream) {
+  art::ScopedObjectAccess soa(self);
+  art::MutexLock mutll(self, *art::Locks::thread_list_lock_);
+  art::MutexLock mudsl(self, deoptimization_status_lock_);
+  art::MutexLock mubsl(self, breakpoint_status_lock_);
+  stream << "Deoptimizer count: " << deopter_count_ << "\n";
+  stream << "Global deopt count: " << global_deopt_count_ << "\n";
+  stream << "Can perform OSR: " << !set_local_variable_called_.load() << "\n";
+  for (const auto& [bp, loc] : this->breakpoint_status_) {
+    stream << "Breakpoint: " << bp->PrettyMethod() << " @ 0x" << std::hex << loc << "\n";
+  }
+  struct DumpThreadDeoptCount : public art::Closure {
+   public:
+    DumpThreadDeoptCount(std::ostream& stream, std::mutex& mu)
+        : cnt_(0), stream_(stream), mu_(mu) {}
+    void Run(art::Thread* self) override {
+      {
+        std::lock_guard<std::mutex> lg(mu_);
+        std::string name;
+        self->GetThreadName(name);
+        stream_ << "Thread " << name << " (id: " << std::dec << self->GetThreadId()
+                << ") force interpreter count " << self->ForceInterpreterCount() << "\n";
+      }
+      // Increment this after unlocking the mutex so we won't race its destructor.
+      cnt_++;
+    }
+
+    void WaitForCount(size_t threads) {
+      while (cnt_.load() != threads) {
+        sched_yield();
+      }
+    }
+
+   private:
+    std::atomic<size_t> cnt_;
+    std::ostream& stream_;
+    std::mutex& mu_;
+  };
+
+  std::mutex mu;
+  DumpThreadDeoptCount dtdc(stream, mu);
+  auto func = [](art::Thread* thread, void* ctx) {
+    reinterpret_cast<DumpThreadDeoptCount*>(ctx)->Run(thread);
+  };
+  art::Runtime::Current()->GetThreadList()->ForEach(func, &dtdc);
+}
+
 void DeoptManager::FinishSetup() {
   art::Thread* self = art::Thread::Current();
   art::MutexLock mu(self, deoptimization_status_lock_);
@@ -366,8 +415,7 @@
     return err;
   }
   // We don't need additional locking here because we hold the Thread_list_lock_.
-  target->SetForceInterpreterCount(target->ForceInterpreterCount() + 1);
-  if (target->ForceInterpreterCount() == 1) {
+  if (target->IncrementForceInterpreterCount() == 1) {
     struct DeoptClosure : public art::Closure {
      public:
       explicit DeoptClosure(DeoptManager* man) : man_(man) {}
diff --git a/openjdkjvmti/deopt_manager.h b/openjdkjvmti/deopt_manager.h
index 4c4a774..73a64be 100644
--- a/openjdkjvmti/deopt_manager.h
+++ b/openjdkjvmti/deopt_manager.h
@@ -33,6 +33,7 @@
 #define ART_OPENJDKJVMTI_DEOPT_MANAGER_H_
 
 #include <atomic>
+#include <iosfwd>
 #include <unordered_map>
 
 #include "base/mutex.h"
@@ -78,6 +79,8 @@
   void Setup();
   void Shutdown();
 
+  void DumpDeoptInfo(art::Thread* self, std::ostream& stream);
+
   void RemoveDeoptimizationRequester() REQUIRES(!deoptimization_status_lock_,
                                                 !art::Roles::uninterruptible_);
   void AddDeoptimizationRequester() REQUIRES(!deoptimization_status_lock_,
diff --git a/openjdkjvmti/ti_dump.cc b/openjdkjvmti/ti_dump.cc
index c9abb71..caf24fa 100644
--- a/openjdkjvmti/ti_dump.cc
+++ b/openjdkjvmti/ti_dump.cc
@@ -32,9 +32,11 @@
 #include "ti_dump.h"
 
 #include <limits>
+#include <sstream>
 
 #include "art_jvmti.h"
 #include "base/mutex.h"
+#include "deopt_manager.h"
 #include "events-inl.h"
 #include "runtime_callbacks.h"
 #include "scoped_thread_state_change-inl.h"
@@ -70,4 +72,22 @@
   art::Runtime::Current()->GetRuntimeCallbacks()->RemoveRuntimeSigQuitCallback(&gDumpCallback);
 }
 
+jvmtiError DumpUtil::DumpInternalState(jvmtiEnv *jvmti, char **data) {
+  art::Thread* self = art::Thread::Current();
+  if (jvmti == nullptr || self == nullptr) {
+    return ERR(INVALID_ENVIRONMENT);
+  } else if (data == nullptr) {
+    return ERR(NULL_POINTER);
+  }
+
+  std::stringstream ss;
+  // TODO Add more stuff on here.
+  DeoptManager::Get()->DumpDeoptInfo(self, ss);
+
+  jvmtiError err = OK;
+  JvmtiUniquePtr<char[]> res = CopyString(jvmti, ss.str().c_str(), &err);
+  *data = res.release();
+  return err;
+}
+
 }  // namespace openjdkjvmti
diff --git a/openjdkjvmti/ti_dump.h b/openjdkjvmti/ti_dump.h
index 323bf56..c382b36 100644
--- a/openjdkjvmti/ti_dump.h
+++ b/openjdkjvmti/ti_dump.h
@@ -43,6 +43,8 @@
  public:
   static void Register(EventHandler* event_handler);
   static void Unregister();
+
+  static jvmtiError DumpInternalState(jvmtiEnv* jvmti, char** data);
 };
 
 }  // namespace openjdkjvmti
diff --git a/openjdkjvmti/ti_extension.cc b/openjdkjvmti/ti_extension.cc
index 5d39884..f12cb0a 100644
--- a/openjdkjvmti/ti_extension.cc
+++ b/openjdkjvmti/ti_extension.cc
@@ -38,6 +38,7 @@
 #include "ti_allocator.h"
 #include "ti_class.h"
 #include "ti_ddms.h"
+#include "ti_dump.h"
 #include "ti_heap.h"
 #include "ti_logging.h"
 #include "ti_monitor.h"
@@ -312,6 +313,20 @@
     return error;
   }
 
+  // DumpInternalState
+  error = add_extension(
+      reinterpret_cast<jvmtiExtensionFunction>(DumpUtil::DumpInternalState),
+      "com.android.art.misc.get_plugin_internal_state",
+      "Gets internal state about the plugin and serializes it to the given msg. "
+      "There is no particular format to this message beyond being human readable.",
+      {
+          { "msg", JVMTI_KIND_ALLOC_BUF, JVMTI_TYPE_CCHAR, false },
+      },
+      { ERR(NULL_POINTER) });
+  if (error != ERR(NONE)) {
+    return error;
+  }
+
   // Copy into output buffer.
 
   *extension_count_ptr = ext_vector.size();
diff --git a/tools/dump-jvmti-state/Android.bp b/tools/dump-jvmti-state/Android.bp
new file mode 100644
index 0000000..5c78965
--- /dev/null
+++ b/tools/dump-jvmti-state/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: "dumpjvmti-defaults",
+    host_supported: true,
+    srcs: ["dump-jvmti.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: "libdumpjvmti",
+    defaults: ["dumpjvmti-defaults"],
+}
+
+art_cc_library {
+    name: "libdumpjvmtid",
+    defaults: [
+        "art_debug_defaults",
+        "dumpjvmti-defaults",
+    ],
+}
diff --git a/tools/dump-jvmti-state/README.md b/tools/dump-jvmti-state/README.md
new file mode 100644
index 0000000..4aabc08
--- /dev/null
+++ b/tools/dump-jvmti-state/README.md
@@ -0,0 +1,27 @@
+# dumpjvmti
+
+dumpjvmti is a JVMTI agent designed for helping debug the working of the openjdkjvmti plugin. It
+allows one to use SIGQUIT to dump information about the current JVMTI state to logcat. It does
+this by calling the com.android.art.misc.get_plugin_internal_state extension function.
+
+# Usage
+### Build
+>    `make libdumpjvmti`
+
+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:libdumpjvmti.so' -cp tmp/java/helloworld.dex -Xint helloworld`
+>    `kill -3 <pid>`
+
+* `-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/libdumpjvmti.so /data/local/tmp/`
+>
+>    `adb shell am start-activity --attach-agent /data/local/tmp/libdumpjvmti.so some.debuggable.apps/.the.app.MainActivity`
+>
+>    `adb shell kill -3 $(adb shell pidof some.debuggable.apps)`
\ No newline at end of file
diff --git a/tools/dump-jvmti-state/dump-jvmti.cc b/tools/dump-jvmti-state/dump-jvmti.cc
new file mode 100644
index 0000000..71a0115
--- /dev/null
+++ b/tools/dump-jvmti-state/dump-jvmti.cc
@@ -0,0 +1,115 @@
+// 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>
+
+namespace dumpjvmti {
+
+namespace {
+
+// Special art ti-version number. We will use this as a fallback if we cannot get a regular JVMTI
+// env.
+static constexpr jint kArtTiVersion = JVMTI_VERSION_1_2 | 0x40000000;
+
+template <typename T> static void Dealloc(jvmtiEnv* env, T* t) {
+  env->Deallocate(reinterpret_cast<unsigned char*>(t));
+}
+
+template <typename T, typename... Rest> static void Dealloc(jvmtiEnv* env, T* t, Rest... rs) {
+  Dealloc(env, t);
+  Dealloc(env, rs...);
+}
+
+static void DeallocParams(jvmtiEnv* env, jvmtiParamInfo* params, jint n_params) {
+  for (jint i = 0; i < n_params; i++) {
+    Dealloc(env, params[i].name);
+  }
+}
+
+// The extension function to get the internal data
+static jvmtiError (*GetInternalData)(jvmtiEnv* env, unsigned char** data) = nullptr;
+
+static jint SetupJvmtiEnv(JavaVM* vm, jvmtiEnv** jvmti) {
+  jint res = 0;
+  res = vm->GetEnv(reinterpret_cast<void**>(jvmti), JVMTI_VERSION_1_1);
+
+  if (res != JNI_OK || *jvmti == nullptr) {
+    LOG(ERROR) << "Unable to access JVMTI, error code " << res;
+    res = vm->GetEnv(reinterpret_cast<void**>(jvmti), kArtTiVersion);
+    if (res != JNI_OK) {
+      return res;
+    }
+  }
+
+  jvmtiEnv* env = *jvmti;
+
+  // Get the extensions.
+  jint n_ext = 0;
+  jvmtiExtensionFunctionInfo* infos = nullptr;
+  if (env->GetExtensionFunctions(&n_ext, &infos) != JVMTI_ERROR_NONE) {
+    return JNI_ERR;
+  }
+  for (jint i = 0; i < n_ext; i++) {
+    jvmtiExtensionFunctionInfo* cur_info = &infos[i];
+    if (strcmp("com.android.art.misc.get_plugin_internal_state", cur_info->id) == 0) {
+      GetInternalData = reinterpret_cast<decltype(GetInternalData)>(cur_info->func);
+    }
+    // Cleanup the cur_info
+    DeallocParams(env, cur_info->params, cur_info->param_count);
+    Dealloc(env, cur_info->id, cur_info->short_description, cur_info->params, cur_info->errors);
+  }
+  // Cleanup the array.
+  Dealloc(env, infos);
+  return GetInternalData != nullptr ? JNI_OK : JNI_ERR;
+}
+
+static void CbDataDump(jvmtiEnv* jvmti) {
+  unsigned char* data = nullptr;
+  if (JVMTI_ERROR_NONE == GetInternalData(jvmti, &data)) {
+    LOG(INFO) << data;
+    Dealloc(jvmti, data);
+  }
+}
+
+}  // namespace
+
+static jint AgentStart(JavaVM* vm, char* options ATTRIBUTE_UNUSED, void* reserved ATTRIBUTE_UNUSED) {
+  jvmtiEnv* jvmti = nullptr;
+  if (SetupJvmtiEnv(vm, &jvmti) != JNI_OK) {
+    LOG(ERROR) << "Could not get JVMTI env or ArtTiEnv!";
+    return JNI_ERR;
+  }
+  jvmtiEventCallbacks cb{
+    .DataDumpRequest = CbDataDump,
+  };
+  jvmti->SetEventCallbacks(&cb, sizeof(cb));
+  jvmti->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_DATA_DUMP_REQUEST, nullptr);
+  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(vm, options, reserved);
+}
+
+// Early attachment
+extern "C" JNIEXPORT jint JNICALL Agent_OnLoad(JavaVM* jvm, char* options, void* reserved) {
+  return AgentStart(jvm, options, reserved);
+}
+
+}  // namespace dumpjvmti