summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Alex Light <allight@google.com> 2019-03-05 13:32:49 -0800
committer Treehugger Robot <treehugger-gerrit@google.com> 2019-03-08 04:07:45 +0000
commit60ee778ea7cff778f95fafe63138f336def9fb18 (patch)
tree4b7222ad056b0856c73a4fd65fe6f49d571062fa
parent334630ee9dffdd1932c1ee641d938f25362a4c1a (diff)
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
-rw-r--r--openjdkjvmti/deopt_manager.cc52
-rw-r--r--openjdkjvmti/deopt_manager.h3
-rw-r--r--openjdkjvmti/ti_dump.cc20
-rw-r--r--openjdkjvmti/ti_dump.h2
-rw-r--r--openjdkjvmti/ti_extension.cc15
-rw-r--r--tools/dump-jvmti-state/Android.bp56
-rw-r--r--tools/dump-jvmti-state/README.md27
-rw-r--r--tools/dump-jvmti-state/dump-jvmti.cc115
8 files changed, 288 insertions, 2 deletions
diff --git a/openjdkjvmti/deopt_manager.cc b/openjdkjvmti/deopt_manager.cc
index ee77b7bb77..ec29f2cdda 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 @@ void DeoptManager::Shutdown() {
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 @@ jvmtiError DeoptManager::AddDeoptimizeThreadMethods(art::ScopedObjectAccessUnche
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 4c4a77412e..73a64be7a4 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 @@ class DeoptManager {
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 c9abb71e4c..caf24fa21c 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 @@ void DumpUtil::Unregister() {
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 323bf56aef..c382b36736 100644
--- a/openjdkjvmti/ti_dump.h
+++ b/openjdkjvmti/ti_dump.h
@@ -43,6 +43,8 @@ class DumpUtil {
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 5d398844b2..f12cb0a380 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 @@ jvmtiError ExtensionUtil::GetExtensionFunctions(jvmtiEnv* env,
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 0000000000..5c78965b40
--- /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 0000000000..4aabc082ac
--- /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 0000000000..71a0115999
--- /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