Add JVMTI Breakpoint and SingleStep functionality
Adds support for can_generate_single_step_events and
can_generate_breakpoint_events capabilities.
Tests follow in next CL
Bug: 62821960
Test: ./test.py --host -j40
Change-Id: I5e71b85d7a293784d08aea727dac22236d6fab14
diff --git a/runtime/openjdkjvmti/Android.bp b/runtime/openjdkjvmti/Android.bp
index e38f265..619a49a 100644
--- a/runtime/openjdkjvmti/Android.bp
+++ b/runtime/openjdkjvmti/Android.bp
@@ -27,6 +27,7 @@
"fixed_up_dex_file.cc",
"object_tagging.cc",
"OpenjdkJvmTi.cc",
+ "ti_breakpoint.cc",
"ti_class.cc",
"ti_class_definition.cc",
"ti_class_loader.cc",
diff --git a/runtime/openjdkjvmti/OpenjdkJvmTi.cc b/runtime/openjdkjvmti/OpenjdkJvmTi.cc
index 0896210..e3768b3 100644
--- a/runtime/openjdkjvmti/OpenjdkJvmTi.cc
+++ b/runtime/openjdkjvmti/OpenjdkJvmTi.cc
@@ -48,6 +48,7 @@
#include "scoped_thread_state_change-inl.h"
#include "thread-current-inl.h"
#include "thread_list.h"
+#include "ti_breakpoint.h"
#include "ti_class.h"
#include "ti_dump.h"
#include "ti_field.h"
@@ -619,20 +620,17 @@
return ERR(NOT_IMPLEMENTED);
}
- static jvmtiError SetBreakpoint(jvmtiEnv* env,
- jmethodID method ATTRIBUTE_UNUSED,
- jlocation location ATTRIBUTE_UNUSED) {
+
+ static jvmtiError SetBreakpoint(jvmtiEnv* env, jmethodID method, jlocation location) {
ENSURE_VALID_ENV(env);
ENSURE_HAS_CAP(env, can_generate_breakpoint_events);
- return ERR(NOT_IMPLEMENTED);
+ return BreakpointUtil::SetBreakpoint(env, method, location);
}
- static jvmtiError ClearBreakpoint(jvmtiEnv* env,
- jmethodID method ATTRIBUTE_UNUSED,
- jlocation location ATTRIBUTE_UNUSED) {
+ static jvmtiError ClearBreakpoint(jvmtiEnv* env, jmethodID method, jlocation location) {
ENSURE_VALID_ENV(env);
ENSURE_HAS_CAP(env, can_generate_breakpoint_events);
- return ERR(NOT_IMPLEMENTED);
+ return BreakpointUtil::ClearBreakpoint(env, method, location);
}
static jvmtiError SetFieldAccessWatch(jvmtiEnv* env, jclass klass, jfieldID field) {
diff --git a/runtime/openjdkjvmti/art_jvmti.h b/runtime/openjdkjvmti/art_jvmti.h
index b5f1219..2d5d527 100644
--- a/runtime/openjdkjvmti/art_jvmti.h
+++ b/runtime/openjdkjvmti/art_jvmti.h
@@ -34,6 +34,7 @@
#include <memory>
#include <type_traits>
+#include <unordered_map>
#include <unordered_set>
#include <jni.h>
@@ -46,10 +47,12 @@
#include "java_vm_ext.h"
#include "jni_env_ext.h"
#include "jvmti.h"
+#include "ti_breakpoint.h"
namespace art {
class ArtField;
-}
+class ArtMethod;
+} // namespace art
namespace openjdkjvmti {
@@ -76,6 +79,9 @@
std::unordered_set<art::ArtField*> access_watched_fields;
std::unordered_set<art::ArtField*> modify_watched_fields;
+ // Set of breakpoints is unique to each jvmtiEnv.
+ std::unordered_set<Breakpoint> breakpoints;
+
ArtJvmTiEnv(art::JavaVMExt* runtime, EventHandler* event_handler);
static ArtJvmTiEnv* AsArtJvmTiEnv(jvmtiEnv* env) {
@@ -223,10 +229,10 @@
.can_get_source_debug_extension = 1,
.can_access_local_variables = 0,
.can_maintain_original_method_order = 0,
- .can_generate_single_step_events = 0,
+ .can_generate_single_step_events = 1,
.can_generate_exception_events = 0,
.can_generate_frame_pop_events = 0,
- .can_generate_breakpoint_events = 0,
+ .can_generate_breakpoint_events = 1,
.can_suspend = 0,
.can_redefine_any_class = 0,
.can_get_current_thread_cpu_time = 0,
diff --git a/runtime/openjdkjvmti/events-inl.h b/runtime/openjdkjvmti/events-inl.h
index af99233..f30d7ce 100644
--- a/runtime/openjdkjvmti/events-inl.h
+++ b/runtime/openjdkjvmti/events-inl.h
@@ -22,6 +22,7 @@
#include "events.h"
#include "jni_internal.h"
#include "ScopedLocalRef.h"
+#include "ti_breakpoint.h"
#include "art_jvmti.h"
@@ -217,6 +218,32 @@
}
}
+// Need to give custom specializations for Breakpoint since it needs to filter out which particular
+// methods/dex_pcs agents get notified on.
+template <>
+inline void EventHandler::DispatchEvent<ArtJvmtiEvent::kBreakpoint>(art::Thread* thread,
+ JNIEnv* jnienv,
+ jthread jni_thread,
+ jmethodID jmethod,
+ jlocation location) const {
+ art::ArtMethod* method = art::jni::DecodeArtMethod(jmethod);
+ for (ArtJvmTiEnv* env : envs) {
+ // Search for a breakpoint on this particular method and location.
+ if (env != nullptr &&
+ ShouldDispatch<ArtJvmtiEvent::kBreakpoint>(env, thread) &&
+ env->breakpoints.find({method, location}) != env->breakpoints.end()) {
+ // We temporarily clear any pending exceptions so the event can call back into java code.
+ ScopedLocalRef<jthrowable> thr(jnienv, jnienv->ExceptionOccurred());
+ jnienv->ExceptionClear();
+ auto callback = impl::GetCallback<ArtJvmtiEvent::kBreakpoint>(env);
+ (*callback)(env, jnienv, jni_thread, jmethod, location);
+ if (thr.get() != nullptr && !jnienv->ExceptionCheck()) {
+ jnienv->Throw(thr.get());
+ }
+ }
+ }
+}
+
// Need to give custom specializations for FieldAccess and FieldModification since they need to
// filter out which particular fields agents want to get notified on.
// TODO The spec allows us to do shortcuts like only allow one agent to ever set these watches. This
diff --git a/runtime/openjdkjvmti/events.cc b/runtime/openjdkjvmti/events.cc
index 989b9af..f749daa 100644
--- a/runtime/openjdkjvmti/events.cc
+++ b/runtime/openjdkjvmti/events.cc
@@ -423,14 +423,30 @@
}
}
- // Call-back for when the dex pc moves in a method. We don't currently have any events associated
- // with this.
- void DexPcMoved(art::Thread* self ATTRIBUTE_UNUSED,
+ // Call-back for when the dex pc moves in a method.
+ void DexPcMoved(art::Thread* self,
art::Handle<art::mirror::Object> this_object ATTRIBUTE_UNUSED,
- art::ArtMethod* method ATTRIBUTE_UNUSED,
- uint32_t new_dex_pc ATTRIBUTE_UNUSED)
+ art::ArtMethod* method,
+ uint32_t new_dex_pc)
REQUIRES_SHARED(art::Locks::mutator_lock_) OVERRIDE {
- return;
+ DCHECK(!method->IsRuntimeMethod());
+ // Default methods might be copied to multiple classes. We need to get the canonical version of
+ // this method so that we can check for breakpoints correctly.
+ // TODO We should maybe do this on other events to ensure that we are consistent WRT default
+ // methods. This could interact with obsolete methods if we ever let interface redefinition
+ // happen though.
+ method = method->GetCanonicalMethod();
+ art::JNIEnvExt* jnienv = self->GetJniEnv();
+ jmethodID jmethod = art::jni::EncodeArtMethod(method);
+ jlocation location = static_cast<jlocation>(new_dex_pc);
+ // Step event is reported first according to the spec.
+ if (event_handler_->IsEventEnabledAnywhere(ArtJvmtiEvent::kSingleStep)) {
+ RunEventCallback<ArtJvmtiEvent::kSingleStep>(self, jnienv, jmethod, location);
+ }
+ // Next we do the Breakpoint events. The Dispatch code will filter the individual
+ if (event_handler_->IsEventEnabledAnywhere(ArtJvmtiEvent::kBreakpoint)) {
+ RunEventCallback<ArtJvmtiEvent::kBreakpoint>(self, jnienv, jmethod, location);
+ }
}
// Call-back for when we read from a field.
@@ -563,6 +579,9 @@
return art::instrumentation::Instrumentation::kFieldWritten;
case ArtJvmtiEvent::kFieldAccess:
return art::instrumentation::Instrumentation::kFieldRead;
+ case ArtJvmtiEvent::kBreakpoint:
+ case ArtJvmtiEvent::kSingleStep:
+ return art::instrumentation::Instrumentation::kDexPcMoved;
default:
LOG(FATAL) << "Unknown event ";
return 0;
@@ -580,6 +599,8 @@
art::gc::kCollectorTypeInstrumentation);
art::ScopedSuspendAll ssa("jvmti method tracing installation");
if (enable) {
+ // TODO Depending on the features being used we should be able to avoid deoptimizing everything
+ // like we do here.
if (!instr->AreAllMethodsDeoptimized()) {
instr->EnableMethodTracing("jvmti-tracing", /*needs_interpreter*/true);
}
@@ -601,6 +622,17 @@
SetupGcPauseTracking(gc_pause_listener_.get(), event, enable);
return;
+ case ArtJvmtiEvent::kBreakpoint:
+ case ArtJvmtiEvent::kSingleStep: {
+ ArtJvmtiEvent other = (event == ArtJvmtiEvent::kBreakpoint) ? ArtJvmtiEvent::kSingleStep
+ : ArtJvmtiEvent::kBreakpoint;
+ // We only need to do anything if there isn't already a listener installed/held-on by the
+ // other jvmti event that uses DexPcMoved.
+ if (!IsEventEnabledAnywhere(other)) {
+ SetupTraceListener(method_trace_listener_.get(), event, enable);
+ }
+ return;
+ }
case ArtJvmtiEvent::kMethodEntry:
case ArtJvmtiEvent::kMethodExit:
case ArtJvmtiEvent::kFieldAccess:
diff --git a/runtime/openjdkjvmti/ti_breakpoint.cc b/runtime/openjdkjvmti/ti_breakpoint.cc
new file mode 100644
index 0000000..6d0e2c6
--- /dev/null
+++ b/runtime/openjdkjvmti/ti_breakpoint.cc
@@ -0,0 +1,114 @@
+/* Copyright (C) 2017 The Android Open Source Project
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This file implements interfaces from the file jvmti.h. This implementation
+ * is licensed under the same terms as the file jvmti.h. The
+ * copyright and license information for the file jvmti.h follows.
+ *
+ * Copyright (c) 2003, 2011, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#include <functional>
+
+#include "ti_breakpoint.h"
+
+#include "art_jvmti.h"
+#include "art_method-inl.h"
+#include "base/enums.h"
+#include "dex_file_annotations.h"
+#include "events-inl.h"
+#include "jni_internal.h"
+#include "mirror/class-inl.h"
+#include "mirror/object_array-inl.h"
+#include "modifiers.h"
+#include "runtime_callbacks.h"
+#include "scoped_thread_state_change-inl.h"
+#include "ScopedLocalRef.h"
+#include "thread-current-inl.h"
+#include "thread_list.h"
+#include "ti_phase.h"
+
+namespace openjdkjvmti {
+
+size_t Breakpoint::hash() const {
+ return std::hash<uintptr_t> {}(reinterpret_cast<uintptr_t>(method_))
+ ^ std::hash<jlocation> {}(location_);
+}
+
+Breakpoint::Breakpoint(art::ArtMethod* m, jlocation loc) : method_(m), location_(loc) {
+ DCHECK(!m->IsDefault() || !m->IsCopied() || !m->IsInvokable())
+ << "Flags are: 0x" << std::hex << m->GetAccessFlags();
+}
+
+void BreakpointUtil::RemoveBreakpointsInClass(ArtJvmTiEnv* env, art::mirror::Class* klass) {
+ std::vector<Breakpoint> to_remove;
+ for (const Breakpoint& b : env->breakpoints) {
+ if (b.GetMethod()->GetDeclaringClass() == klass) {
+ to_remove.push_back(b);
+ }
+ }
+ for (const Breakpoint& b : to_remove) {
+ auto it = env->breakpoints.find(b);
+ DCHECK(it != env->breakpoints.end());
+ env->breakpoints.erase(it);
+ }
+}
+
+jvmtiError BreakpointUtil::SetBreakpoint(jvmtiEnv* jenv, jmethodID method, jlocation location) {
+ ArtJvmTiEnv* env = ArtJvmTiEnv::AsArtJvmTiEnv(jenv);
+ if (method == nullptr) {
+ return ERR(INVALID_METHODID);
+ }
+ // Need to get mutator_lock_ so we can find the interface version of any default methods.
+ art::ScopedObjectAccess soa(art::Thread::Current());
+ art::ArtMethod* art_method = art::jni::DecodeArtMethod(method)->GetCanonicalMethod();
+ if (location < 0 || static_cast<uint32_t>(location) >=
+ art_method->GetCodeItem()->insns_size_in_code_units_) {
+ return ERR(INVALID_LOCATION);
+ }
+ auto res_pair = env->breakpoints.insert(/* Breakpoint */ {art_method, location});
+ if (!res_pair.second) {
+ // Didn't get inserted because it's already present!
+ return ERR(DUPLICATE);
+ }
+ return OK;
+}
+
+jvmtiError BreakpointUtil::ClearBreakpoint(jvmtiEnv* jenv, jmethodID method, jlocation location) {
+ ArtJvmTiEnv* env = ArtJvmTiEnv::AsArtJvmTiEnv(jenv);
+ if (method == nullptr) {
+ return ERR(INVALID_METHODID);
+ }
+ // Need to get mutator_lock_ so we can find the interface version of any default methods.
+ art::ScopedObjectAccess soa(art::Thread::Current());
+ auto pos = env->breakpoints.find(
+ /* Breakpoint */ {art::jni::DecodeArtMethod(method)->GetCanonicalMethod(), location});
+ if (pos == env->breakpoints.end()) {
+ return ERR(NOT_FOUND);
+ }
+ env->breakpoints.erase(pos);
+ return OK;
+}
+
+} // namespace openjdkjvmti
diff --git a/runtime/openjdkjvmti/ti_breakpoint.h b/runtime/openjdkjvmti/ti_breakpoint.h
new file mode 100644
index 0000000..c3dbef7
--- /dev/null
+++ b/runtime/openjdkjvmti/ti_breakpoint.h
@@ -0,0 +1,94 @@
+/* Copyright (C) 2017 The Android Open Source Project
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This file implements interfaces from the file jvmti.h. This implementation
+ * is licensed under the same terms as the file jvmti.h. The
+ * copyright and license information for the file jvmti.h follows.
+ *
+ * Copyright (c) 2003, 2011, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#ifndef ART_RUNTIME_OPENJDKJVMTI_TI_BREAKPOINT_H_
+#define ART_RUNTIME_OPENJDKJVMTI_TI_BREAKPOINT_H_
+
+#include "jni.h"
+#include "jvmti.h"
+
+#include "base/mutex.h"
+
+namespace art {
+class ArtMethod;
+namespace mirror {
+class Class;
+} // namespace mirror
+} // namespace art
+
+namespace openjdkjvmti {
+
+struct ArtJvmTiEnv;
+
+class Breakpoint {
+ public:
+ Breakpoint(art::ArtMethod* m, jlocation loc);
+
+ // Get the hash code of this breakpoint.
+ size_t hash() const;
+
+ bool operator==(const Breakpoint& other) const {
+ return method_ == other.method_ && location_ == other.location_;
+ }
+
+ art::ArtMethod* GetMethod() const {
+ return method_;
+ }
+
+ jlocation GetLocation() const {
+ return location_;
+ }
+
+ private:
+ art::ArtMethod* method_;
+ jlocation location_;
+};
+
+class BreakpointUtil {
+ public:
+ static jvmtiError SetBreakpoint(jvmtiEnv* env, jmethodID method, jlocation location);
+ static jvmtiError ClearBreakpoint(jvmtiEnv* env, jmethodID method, jlocation location);
+ // Used by class redefinition to remove breakpoints on redefined classes.
+ static void RemoveBreakpointsInClass(ArtJvmTiEnv* env, art::mirror::Class* klass)
+ REQUIRES(art::Locks::mutator_lock_);
+};
+
+} // namespace openjdkjvmti
+
+namespace std {
+template<> struct hash<openjdkjvmti::Breakpoint> {
+ size_t operator()(const openjdkjvmti::Breakpoint& b) const {
+ return b.hash();
+ }
+};
+
+} // namespace std
+#endif // ART_RUNTIME_OPENJDKJVMTI_TI_BREAKPOINT_H_
diff --git a/runtime/openjdkjvmti/ti_redefine.cc b/runtime/openjdkjvmti/ti_redefine.cc
index 5422f48..debee91 100644
--- a/runtime/openjdkjvmti/ti_redefine.cc
+++ b/runtime/openjdkjvmti/ti_redefine.cc
@@ -64,6 +64,7 @@
#include "object_lock.h"
#include "runtime.h"
#include "ScopedLocalRef.h"
+#include "ti_breakpoint.h"
#include "ti_class_loader.h"
#include "transform.h"
#include "verifier/method_verifier.h"
@@ -380,7 +381,7 @@
art::jit::ScopedJitSuspend suspend_jit;
// Get shared mutator lock so we can lock all the classes.
art::ScopedObjectAccess soa(self);
- Redefiner r(runtime, self, error_msg);
+ Redefiner r(env, runtime, self, error_msg);
for (const ArtClassDefinition& def : definitions) {
// Only try to transform classes that have been modified.
if (def.IsModified()) {
@@ -1200,6 +1201,10 @@
return true;
}
+void Redefiner::ClassRedefinition::UnregisterJvmtiBreakpoints() {
+ BreakpointUtil::RemoveBreakpointsInClass(driver_->env_, GetMirrorClass());
+}
+
void Redefiner::ClassRedefinition::UnregisterBreakpoints() {
DCHECK(art::Dbg::IsDebuggerActive());
art::JDWP::JdwpState* state = art::Dbg::GetJdwpState();
@@ -1342,6 +1347,7 @@
// TODO Rewrite so we don't do a stack walk for each and every class.
redef.FindAndAllocateObsoleteMethods(klass);
redef.UpdateClass(klass, data.GetNewDexCache(), data.GetOriginalDexFile());
+ redef.UnregisterJvmtiBreakpoints();
}
RestoreObsoleteMethodMapsIfUnneeded(holder);
// TODO We should check for if any of the redefined methods are intrinsic methods here and, if any
diff --git a/runtime/openjdkjvmti/ti_redefine.h b/runtime/openjdkjvmti/ti_redefine.h
index ec4a8b2..27d7c3d 100644
--- a/runtime/openjdkjvmti/ti_redefine.h
+++ b/runtime/openjdkjvmti/ti_redefine.h
@@ -199,6 +199,8 @@
void ReleaseDexFile() REQUIRES_SHARED(art::Locks::mutator_lock_);
void UnregisterBreakpoints() REQUIRES_SHARED(art::Locks::mutator_lock_);
+ // This should be done with all threads suspended.
+ void UnregisterJvmtiBreakpoints() REQUIRES(art::Locks::mutator_lock_);
private:
Redefiner* driver_;
@@ -208,6 +210,7 @@
art::ArrayRef<const unsigned char> original_dex_file_;
};
+ ArtJvmTiEnv* env_;
jvmtiError result_;
art::Runtime* runtime_;
art::Thread* self_;
@@ -216,10 +219,12 @@
// mirror::Class difficult and confusing.
std::string* error_msg_;
- Redefiner(art::Runtime* runtime,
+ Redefiner(ArtJvmTiEnv* env,
+ art::Runtime* runtime,
art::Thread* self,
std::string* error_msg)
- : result_(ERR(INTERNAL)),
+ : env_(env),
+ result_(ERR(INTERNAL)),
runtime_(runtime),
self_(self),
redefinitions_(),