Merge changes Id2a4db5a,I5e71b85d,I70f76ba7,I4d19cc10,I231178e7
am: f88e73f246

Change-Id: I25c5be0129b9071e7bb976cc872cf0dc8b7d4093
diff --git a/runtime/art_method.cc b/runtime/art_method.cc
index 1554986..3e7ed97 100644
--- a/runtime/art_method.cc
+++ b/runtime/art_method.cc
@@ -61,6 +61,19 @@
 static_assert(ArtMethod::kRuntimeMethodDexMethodIndex == DexFile::kDexNoIndex,
               "Wrong runtime-method dex method index");
 
+ArtMethod* ArtMethod::GetCanonicalMethod(PointerSize pointer_size) {
+  if (LIKELY(!IsDefault())) {
+    return this;
+  } else {
+    mirror::Class* declaring_class = GetDeclaringClass();
+    ArtMethod* ret = declaring_class->FindDeclaredVirtualMethod(declaring_class->GetDexCache(),
+                                                                GetDexMethodIndex(),
+                                                                pointer_size);
+    DCHECK(ret != nullptr);
+    return ret;
+  }
+}
+
 ArtMethod* ArtMethod::GetNonObsoleteMethod() {
   DCHECK_EQ(kRuntimePointerSize, Runtime::Current()->GetClassLinker()->GetImagePointerSize());
   if (LIKELY(!IsObsolete())) {
diff --git a/runtime/art_method.h b/runtime/art_method.h
index 96306af..4b3e8ef 100644
--- a/runtime/art_method.h
+++ b/runtime/art_method.h
@@ -483,6 +483,12 @@
     }
   }
 
+  // Takes a method and returns a 'canonical' one if the method is default (and therefore
+  // potentially copied from some other class). For example, this ensures that the debugger does not
+  // get confused as to which method we are in.
+  ArtMethod* GetCanonicalMethod(PointerSize pointer_size = kRuntimePointerSize)
+      REQUIRES_SHARED(Locks::mutator_lock_);
+
   ArtMethod* GetSingleImplementation(PointerSize pointer_size)
       REQUIRES_SHARED(Locks::mutator_lock_);
 
diff --git a/runtime/debugger.cc b/runtime/debugger.cc
index 12bdb32..cc12439 100644
--- a/runtime/debugger.cc
+++ b/runtime/debugger.cc
@@ -77,25 +77,10 @@
   return alloc_record_count;
 }
 
-// Takes a method and returns a 'canonical' one if the method is default (and therefore potentially
-// copied from some other class). This ensures that the debugger does not get confused as to which
-// method we are in.
-static ArtMethod* GetCanonicalMethod(ArtMethod* m)
-    REQUIRES_SHARED(Locks::mutator_lock_) {
-  if (LIKELY(!m->IsDefault())) {
-    return m;
-  } else {
-    mirror::Class* declaring_class = m->GetDeclaringClass();
-    return declaring_class->FindDeclaredVirtualMethod(declaring_class->GetDexCache(),
-                                                      m->GetDexMethodIndex(),
-                                                      kRuntimePointerSize);
-  }
-}
-
 class Breakpoint : public ValueObject {
  public:
   Breakpoint(ArtMethod* method, uint32_t dex_pc, DeoptimizationRequest::Kind deoptimization_kind)
-    : method_(GetCanonicalMethod(method)),
+    : method_(method->GetCanonicalMethod(kRuntimePointerSize)),
       dex_pc_(dex_pc),
       deoptimization_kind_(deoptimization_kind) {
     CHECK(deoptimization_kind_ == DeoptimizationRequest::kNothing ||
@@ -125,7 +110,7 @@
   // Returns true if the method of this breakpoint and the passed in method should be considered the
   // same. That is, they are either the same method or they are copied from the same method.
   bool IsInMethod(ArtMethod* m) const REQUIRES_SHARED(Locks::mutator_lock_) {
-    return method_ == GetCanonicalMethod(m);
+    return method_ == m->GetCanonicalMethod(kRuntimePointerSize);
   }
 
  private:
@@ -1367,7 +1352,8 @@
 
 static JDWP::MethodId ToMethodId(ArtMethod* m)
     REQUIRES_SHARED(Locks::mutator_lock_) {
-  return static_cast<JDWP::MethodId>(reinterpret_cast<uintptr_t>(GetCanonicalMethod(m)));
+  return static_cast<JDWP::MethodId>(
+      reinterpret_cast<uintptr_t>(m->GetCanonicalMethod(kRuntimePointerSize)));
 }
 
 static ArtField* FromFieldId(JDWP::FieldId fid)
@@ -2887,7 +2873,7 @@
   if (m == nullptr) {
     memset(location, 0, sizeof(*location));
   } else {
-    location->method = GetCanonicalMethod(m);
+    location->method = m->GetCanonicalMethod(kRuntimePointerSize);
     location->dex_pc = (m->IsNative() || m->IsProxyMethod()) ? static_cast<uint32_t>(-1) : dex_pc;
   }
 }
diff --git a/runtime/interpreter/interpreter_switch_impl.cc b/runtime/interpreter/interpreter_switch_impl.cc
index 45788e7..a7a2d12 100644
--- a/runtime/interpreter/interpreter_switch_impl.cc
+++ b/runtime/interpreter/interpreter_switch_impl.cc
@@ -64,13 +64,22 @@
   }
 
 // Code to run before each dex instruction.
-#define PREAMBLE()                                                                              \
-  do {                                                                                          \
-    if (UNLIKELY(instrumentation->HasDexPcListeners())) {                                       \
-      instrumentation->DexPcMovedEvent(self, shadow_frame.GetThisObject(code_item->ins_size_),  \
-                                       shadow_frame.GetMethod(), dex_pc);                       \
+#define PREAMBLE_SAVE(save_ref)                                                                      \
+  {                                                                                             \
+    if (UNLIKELY(instrumentation->HasDexPcListeners()) &&                                       \
+        UNLIKELY(!DoDexPcMoveEvent(self,                                                        \
+                                   code_item,                                                   \
+                                   shadow_frame,                                                \
+                                   dex_pc,                                                      \
+                                   instrumentation,                                             \
+                                   save_ref))) {                                                \
+      HANDLE_PENDING_EXCEPTION();                                                               \
+      break;                                                                                    \
     }                                                                                           \
-  } while (false)
+  }                                                                                             \
+  do {} while (false)
+
+#define PREAMBLE() PREAMBLE_SAVE(nullptr)
 
 #define BRANCH_INSTRUMENTATION(offset)                                                         \
   do {                                                                                         \
@@ -104,6 +113,43 @@
     }                                                                                          \
   } while (false)
 
+// Unlike most other events the DexPcMovedEvent can be sent when there is a pending exception (if
+// the next instruction is MOVE_EXCEPTION). This means it needs to be handled carefully to be able
+// to detect exceptions thrown by the DexPcMovedEvent itself. These exceptions could be thrown by
+// jvmti-agents while handling breakpoint or single step events. We had to move this into its own
+// function because it was making ExecuteSwitchImpl have too large a stack.
+static bool DoDexPcMoveEvent(Thread* self,
+                             const DexFile::CodeItem* code_item,
+                             const ShadowFrame& shadow_frame,
+                             uint32_t dex_pc,
+                             const instrumentation::Instrumentation* instrumentation,
+                             JValue* save_ref)
+    REQUIRES_SHARED(Locks::mutator_lock_) {
+  DCHECK(instrumentation->HasDexPcListeners());
+  StackHandleScope<2> hs(self);
+  Handle<mirror::Throwable> thr(hs.NewHandle(self->GetException()));
+  mirror::Object* null_obj = nullptr;
+  HandleWrapper<mirror::Object> h(
+      hs.NewHandleWrapper(LIKELY(save_ref == nullptr) ? &null_obj : save_ref->GetGCRoot()));
+  self->ClearException();
+  instrumentation->DexPcMovedEvent(self,
+                                   shadow_frame.GetThisObject(code_item->ins_size_),
+                                   shadow_frame.GetMethod(),
+                                   dex_pc);
+  if (UNLIKELY(self->IsExceptionPending())) {
+    // We got a new exception in the dex-pc-moved event. We just let this exception replace the old
+    // one.
+    // TODO It would be good to add the old exception to the suppressed exceptions of the new one if
+    // possible.
+    return false;
+  } else {
+    if (UNLIKELY(!thr.IsNull())) {
+      self->SetException(thr.Get());
+    }
+    return true;
+  }
+}
+
 template<bool do_access_check, bool transaction_active>
 JValue ExecuteSwitchImpl(Thread* self, const DexFile::CodeItem* code_item,
                          ShadowFrame& shadow_frame, JValue result_register,
@@ -198,7 +244,7 @@
         inst = inst->Next_1xx();
         break;
       case Instruction::MOVE_RESULT_OBJECT:
-        PREAMBLE();
+        PREAMBLE_SAVE(&result_register);
         shadow_frame.SetVRegReference(inst->VRegA_11x(inst_data), result_register.GetL());
         inst = inst->Next_1xx();
         break;
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_(),
diff --git a/test/988-method-trace/expected.txt b/test/988-method-trace/expected.txt
index 30ad532..7eec783 100644
--- a/test/988-method-trace/expected.txt
+++ b/test/988-method-trace/expected.txt
@@ -1,4 +1,4 @@
-.<= public static native void art.Trace.enableTracing(java.lang.Class,java.lang.reflect.Method,java.lang.reflect.Method,java.lang.reflect.Method,java.lang.reflect.Method,java.lang.Thread) -> <null: null>
+.<= public static native void art.Trace.enableTracing(java.lang.Class,java.lang.reflect.Method,java.lang.reflect.Method,java.lang.reflect.Method,java.lang.reflect.Method,java.lang.reflect.Method,java.lang.Thread) -> <null: null>
 <= public static void art.Trace.enableMethodTracing(java.lang.Class,java.lang.reflect.Method,java.lang.reflect.Method,java.lang.Thread) -> <null: null>
 => art.Test988$IterOp()
 .=> public java.lang.Object()
diff --git a/test/988-method-trace/src/art/Trace.java b/test/988-method-trace/src/art/Trace.java
index 9c27c9f..ba3d397 100644
--- a/test/988-method-trace/src/art/Trace.java
+++ b/test/988-method-trace/src/art/Trace.java
@@ -25,6 +25,7 @@
                                           Method exitMethod,
                                           Method fieldAccess,
                                           Method fieldModify,
+                                          Method singleStep,
                                           Thread thr);
   public static native void disableTracing(Thread thr);
 
@@ -32,14 +33,20 @@
                                         Method fieldAccess,
                                         Method fieldModify,
                                         Thread thr) {
-    enableTracing(methodClass, null, null, fieldAccess, fieldModify, thr);
+    enableTracing(methodClass, null, null, fieldAccess, fieldModify, null, thr);
   }
 
   public static void enableMethodTracing(Class<?> methodClass,
                                          Method entryMethod,
                                          Method exitMethod,
                                          Thread thr) {
-    enableTracing(methodClass, entryMethod, exitMethod, null, null, thr);
+    enableTracing(methodClass, entryMethod, exitMethod, null, null, null, thr);
+  }
+
+  public static void enableSingleStepTracing(Class<?> methodClass,
+                                             Method singleStep,
+                                             Thread thr) {
+    enableTracing(methodClass, null, null, null, null, singleStep, thr);
   }
 
   public static native void watchFieldAccess(Field f);
diff --git a/test/989-method-trace-throw/src/art/Trace.java b/test/989-method-trace-throw/src/art/Trace.java
index 9c27c9f..ba3d397 100644
--- a/test/989-method-trace-throw/src/art/Trace.java
+++ b/test/989-method-trace-throw/src/art/Trace.java
@@ -25,6 +25,7 @@
                                           Method exitMethod,
                                           Method fieldAccess,
                                           Method fieldModify,
+                                          Method singleStep,
                                           Thread thr);
   public static native void disableTracing(Thread thr);
 
@@ -32,14 +33,20 @@
                                         Method fieldAccess,
                                         Method fieldModify,
                                         Thread thr) {
-    enableTracing(methodClass, null, null, fieldAccess, fieldModify, thr);
+    enableTracing(methodClass, null, null, fieldAccess, fieldModify, null, thr);
   }
 
   public static void enableMethodTracing(Class<?> methodClass,
                                          Method entryMethod,
                                          Method exitMethod,
                                          Thread thr) {
-    enableTracing(methodClass, entryMethod, exitMethod, null, null, thr);
+    enableTracing(methodClass, entryMethod, exitMethod, null, null, null, thr);
+  }
+
+  public static void enableSingleStepTracing(Class<?> methodClass,
+                                             Method singleStep,
+                                             Thread thr) {
+    enableTracing(methodClass, null, null, null, null, singleStep, thr);
   }
 
   public static native void watchFieldAccess(Field f);
diff --git a/test/990-field-trace/src/art/Trace.java b/test/990-field-trace/src/art/Trace.java
index 9c27c9f..ba3d397 100644
--- a/test/990-field-trace/src/art/Trace.java
+++ b/test/990-field-trace/src/art/Trace.java
@@ -25,6 +25,7 @@
                                           Method exitMethod,
                                           Method fieldAccess,
                                           Method fieldModify,
+                                          Method singleStep,
                                           Thread thr);
   public static native void disableTracing(Thread thr);
 
@@ -32,14 +33,20 @@
                                         Method fieldAccess,
                                         Method fieldModify,
                                         Thread thr) {
-    enableTracing(methodClass, null, null, fieldAccess, fieldModify, thr);
+    enableTracing(methodClass, null, null, fieldAccess, fieldModify, null, thr);
   }
 
   public static void enableMethodTracing(Class<?> methodClass,
                                          Method entryMethod,
                                          Method exitMethod,
                                          Thread thr) {
-    enableTracing(methodClass, entryMethod, exitMethod, null, null, thr);
+    enableTracing(methodClass, entryMethod, exitMethod, null, null, null, thr);
+  }
+
+  public static void enableSingleStepTracing(Class<?> methodClass,
+                                             Method singleStep,
+                                             Thread thr) {
+    enableTracing(methodClass, null, null, null, null, singleStep, thr);
   }
 
   public static native void watchFieldAccess(Field f);
diff --git a/test/991-field-trace-2/src/art/Trace.java b/test/991-field-trace-2/src/art/Trace.java
index 9c27c9f..ba3d397 100644
--- a/test/991-field-trace-2/src/art/Trace.java
+++ b/test/991-field-trace-2/src/art/Trace.java
@@ -25,6 +25,7 @@
                                           Method exitMethod,
                                           Method fieldAccess,
                                           Method fieldModify,
+                                          Method singleStep,
                                           Thread thr);
   public static native void disableTracing(Thread thr);
 
@@ -32,14 +33,20 @@
                                         Method fieldAccess,
                                         Method fieldModify,
                                         Thread thr) {
-    enableTracing(methodClass, null, null, fieldAccess, fieldModify, thr);
+    enableTracing(methodClass, null, null, fieldAccess, fieldModify, null, thr);
   }
 
   public static void enableMethodTracing(Class<?> methodClass,
                                          Method entryMethod,
                                          Method exitMethod,
                                          Thread thr) {
-    enableTracing(methodClass, entryMethod, exitMethod, null, null, thr);
+    enableTracing(methodClass, entryMethod, exitMethod, null, null, null, thr);
+  }
+
+  public static void enableSingleStepTracing(Class<?> methodClass,
+                                             Method singleStep,
+                                             Thread thr) {
+    enableTracing(methodClass, null, null, null, null, singleStep, thr);
   }
 
   public static native void watchFieldAccess(Field f);
diff --git a/test/993-breakpoints/breakpoints.cc b/test/993-breakpoints/breakpoints.cc
new file mode 100644
index 0000000..1292070
--- /dev/null
+++ b/test/993-breakpoints/breakpoints.cc
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2013 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 <inttypes.h>
+#include <memory>
+#include <stdio.h>
+
+#include "android-base/logging.h"
+#include "android-base/stringprintf.h"
+
+#include "jni.h"
+#include "jvmti.h"
+#include "scoped_local_ref.h"
+
+// Test infrastructure
+#include "jni_binder.h"
+#include "jni_helper.h"
+#include "jvmti_helper.h"
+#include "test_env.h"
+#include "ti_macros.h"
+
+namespace art {
+namespace Test993Breakpoints {
+
+extern "C" JNIEXPORT
+jobject JNICALL Java_art_Test993_constructNative(JNIEnv* env,
+                                                 jclass klass ATTRIBUTE_UNUSED,
+                                                 jobject target,
+                                                 jclass clazz) {
+  jmethodID method = env->FromReflectedMethod(target);
+  if (env->ExceptionCheck()) {
+    return nullptr;
+  }
+  return env->NewObject(clazz, method);
+}
+
+extern "C" JNIEXPORT
+void JNICALL Java_art_Test993_invokeNative(JNIEnv* env,
+                                           jclass klass ATTRIBUTE_UNUSED,
+                                           jobject target,
+                                           jclass clazz,
+                                           jobject thizz) {
+  jmethodID method = env->FromReflectedMethod(target);
+  if (env->ExceptionCheck()) {
+    return;
+  }
+  if (thizz == nullptr) {
+    env->CallStaticVoidMethod(clazz, method);
+  } else {
+    env->CallVoidMethod(thizz, method);
+  }
+}
+
+}  // namespace Test993Breakpoints
+}  // namespace art
+
diff --git a/test/993-breakpoints/expected.txt b/test/993-breakpoints/expected.txt
new file mode 100644
index 0000000..9621547
--- /dev/null
+++ b/test/993-breakpoints/expected.txt
@@ -0,0 +1,613 @@
+Running static invoke
+	Breaking on []
+		Native invoking: public static void art.Test993.breakpoint() args: [this: null]
+		Reflective invoking: public static void art.Test993.breakpoint() args: [this: null]
+		Invoking "Test993::breakpoint"
+	Breaking on [public static void art.Test993.breakpoint() @ 41]
+		Native invoking: public static void art.Test993.breakpoint() args: [this: null]
+			Breakpoint: public static void art.Test993.breakpoint() @ line=41
+		Reflective invoking: public static void art.Test993.breakpoint() args: [this: null]
+			Breakpoint: public static void art.Test993.breakpoint() @ line=41
+		Invoking "Test993::breakpoint"
+			Breakpoint: public static void art.Test993.breakpoint() @ line=41
+Running private static invoke
+	Breaking on []
+		Native invoking: private static void art.Test993.privateBreakpoint() args: [this: null]
+		Invoking "Test993::privateBreakpoint"
+	Breaking on [private static void art.Test993.privateBreakpoint() @ 45]
+		Native invoking: private static void art.Test993.privateBreakpoint() args: [this: null]
+			Breakpoint: private static void art.Test993.privateBreakpoint() @ line=45
+		Invoking "Test993::privateBreakpoint"
+			Breakpoint: private static void art.Test993.privateBreakpoint() @ line=45
+Running interface static invoke
+	Breaking on []
+		Native invoking: public static void art.Test993$Breakable.iBreakpoint() args: [this: null]
+		Reflective invoking: public static void art.Test993$Breakable.iBreakpoint() args: [this: null]
+		Invoking "Breakable::iBreakpoint"
+	Breaking on [public static void art.Test993$Breakable.iBreakpoint() @ 51]
+		Native invoking: public static void art.Test993$Breakable.iBreakpoint() args: [this: null]
+			Breakpoint: public static void art.Test993$Breakable.iBreakpoint() @ line=51
+		Reflective invoking: public static void art.Test993$Breakable.iBreakpoint() args: [this: null]
+			Breakpoint: public static void art.Test993$Breakable.iBreakpoint() @ line=51
+		Invoking "Breakable::iBreakpoint"
+			Breakpoint: public static void art.Test993$Breakable.iBreakpoint() @ line=51
+Running TestClass1 invokes
+	Breaking on []
+		Native invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass1]
+		Reflective invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass1]
+		Invoking "((Breakable)new TestClass1()).breakit()"
+		Invoking "new TestClass1().breakit()"
+	Breaking on [public default void art.Test993$Breakable.breakit() @ 55]
+		Native invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass1]
+			Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55
+		Reflective invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass1]
+			Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55
+		Invoking "((Breakable)new TestClass1()).breakit()"
+			Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55
+		Invoking "new TestClass1().breakit()"
+			Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55
+Running TestClass1ext invokes
+	Breaking on []
+		Native invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass1Ext]
+		Native invoking: public void art.Test993$TestClass1ext.breakit() args: [this: TestClass1Ext]
+		Reflective invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass1Ext]
+		Reflective invoking: public void art.Test993$TestClass1ext.breakit() args: [this: TestClass1Ext]
+		Invoking "((Breakable)new TestClass1ext()).breakit()"
+		Invoking "((TestClass1)new TestClass1ext()).breakit()"
+		Invoking "new TestClass1ext().breakit()"
+	Breaking on [public default void art.Test993$Breakable.breakit() @ 55]
+		Native invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass1Ext]
+			Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55
+		Native invoking: public void art.Test993$TestClass1ext.breakit() args: [this: TestClass1Ext]
+			Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55
+		Reflective invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass1Ext]
+			Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55
+		Reflective invoking: public void art.Test993$TestClass1ext.breakit() args: [this: TestClass1Ext]
+			Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55
+		Invoking "((Breakable)new TestClass1ext()).breakit()"
+			Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55
+		Invoking "((TestClass1)new TestClass1ext()).breakit()"
+			Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55
+		Invoking "new TestClass1ext().breakit()"
+			Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55
+	Breaking on [public void art.Test993$TestClass1ext.breakit() @ 74]
+		Native invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass1Ext]
+			Breakpoint: public void art.Test993$TestClass1ext.breakit() @ line=74
+		Native invoking: public void art.Test993$TestClass1ext.breakit() args: [this: TestClass1Ext]
+			Breakpoint: public void art.Test993$TestClass1ext.breakit() @ line=74
+		Reflective invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass1Ext]
+			Breakpoint: public void art.Test993$TestClass1ext.breakit() @ line=74
+		Reflective invoking: public void art.Test993$TestClass1ext.breakit() args: [this: TestClass1Ext]
+			Breakpoint: public void art.Test993$TestClass1ext.breakit() @ line=74
+		Invoking "((Breakable)new TestClass1ext()).breakit()"
+			Breakpoint: public void art.Test993$TestClass1ext.breakit() @ line=74
+		Invoking "((TestClass1)new TestClass1ext()).breakit()"
+			Breakpoint: public void art.Test993$TestClass1ext.breakit() @ line=74
+		Invoking "new TestClass1ext().breakit()"
+			Breakpoint: public void art.Test993$TestClass1ext.breakit() @ line=74
+	Breaking on [public default void art.Test993$Breakable.breakit() @ 55, public void art.Test993$TestClass1ext.breakit() @ 74]
+		Native invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass1Ext]
+			Breakpoint: public void art.Test993$TestClass1ext.breakit() @ line=74
+			Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55
+		Native invoking: public void art.Test993$TestClass1ext.breakit() args: [this: TestClass1Ext]
+			Breakpoint: public void art.Test993$TestClass1ext.breakit() @ line=74
+			Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55
+		Reflective invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass1Ext]
+			Breakpoint: public void art.Test993$TestClass1ext.breakit() @ line=74
+			Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55
+		Reflective invoking: public void art.Test993$TestClass1ext.breakit() args: [this: TestClass1Ext]
+			Breakpoint: public void art.Test993$TestClass1ext.breakit() @ line=74
+			Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55
+		Invoking "((Breakable)new TestClass1ext()).breakit()"
+			Breakpoint: public void art.Test993$TestClass1ext.breakit() @ line=74
+			Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55
+		Invoking "((TestClass1)new TestClass1ext()).breakit()"
+			Breakpoint: public void art.Test993$TestClass1ext.breakit() @ line=74
+			Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55
+		Invoking "new TestClass1ext().breakit()"
+			Breakpoint: public void art.Test993$TestClass1ext.breakit() @ line=74
+			Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55
+Running TestClass2 invokes
+	Breaking on []
+		Native invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass2]
+		Native invoking: public void art.Test993$TestClass2.breakit() args: [this: TestClass2]
+		Reflective invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass2]
+		Reflective invoking: public void art.Test993$TestClass2.breakit() args: [this: TestClass2]
+		Invoking "((Breakable)new TestClass2()).breakit()"
+		Invoking "new TestClass2().breakit()"
+	Breaking on [public default void art.Test993$Breakable.breakit() @ 55]
+		Native invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass2]
+		Native invoking: public void art.Test993$TestClass2.breakit() args: [this: TestClass2]
+		Reflective invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass2]
+		Reflective invoking: public void art.Test993$TestClass2.breakit() args: [this: TestClass2]
+		Invoking "((Breakable)new TestClass2()).breakit()"
+		Invoking "new TestClass2().breakit()"
+	Breaking on [public void art.Test993$TestClass2.breakit() @ 83]
+		Native invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass2]
+			Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83
+		Native invoking: public void art.Test993$TestClass2.breakit() args: [this: TestClass2]
+			Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83
+		Reflective invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass2]
+			Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83
+		Reflective invoking: public void art.Test993$TestClass2.breakit() args: [this: TestClass2]
+			Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83
+		Invoking "((Breakable)new TestClass2()).breakit()"
+			Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83
+		Invoking "new TestClass2().breakit()"
+			Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83
+	Breaking on [public default void art.Test993$Breakable.breakit() @ 55, public void art.Test993$TestClass2.breakit() @ 83]
+		Native invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass2]
+			Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83
+		Native invoking: public void art.Test993$TestClass2.breakit() args: [this: TestClass2]
+			Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83
+		Reflective invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass2]
+			Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83
+		Reflective invoking: public void art.Test993$TestClass2.breakit() args: [this: TestClass2]
+			Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83
+		Invoking "((Breakable)new TestClass2()).breakit()"
+			Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83
+		Invoking "new TestClass2().breakit()"
+			Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83
+Running TestClass2ext invokes
+	Breaking on []
+		Native invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass2ext]
+		Native invoking: public void art.Test993$TestClass2.breakit() args: [this: TestClass2ext]
+		Native invoking: public void art.Test993$TestClass2ext.breakit() args: [this: TestClass2ext]
+		Reflective invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass2ext]
+		Reflective invoking: public void art.Test993$TestClass2.breakit() args: [this: TestClass2ext]
+		Reflective invoking: public void art.Test993$TestClass2ext.breakit() args: [this: TestClass2ext]
+		Invoking "((Breakable)new TestClass2ext()).breakit()"
+		Invoking "((TestClass2)new TestClass2ext()).breakit()"
+		Invoking "new TestClass2ext().breakit())"
+	Breaking on [public default void art.Test993$Breakable.breakit() @ 55]
+		Native invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass2ext]
+		Native invoking: public void art.Test993$TestClass2.breakit() args: [this: TestClass2ext]
+		Native invoking: public void art.Test993$TestClass2ext.breakit() args: [this: TestClass2ext]
+		Reflective invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass2ext]
+		Reflective invoking: public void art.Test993$TestClass2.breakit() args: [this: TestClass2ext]
+		Reflective invoking: public void art.Test993$TestClass2ext.breakit() args: [this: TestClass2ext]
+		Invoking "((Breakable)new TestClass2ext()).breakit()"
+		Invoking "((TestClass2)new TestClass2ext()).breakit()"
+		Invoking "new TestClass2ext().breakit())"
+	Breaking on [public void art.Test993$TestClass2.breakit() @ 83]
+		Native invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass2ext]
+			Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83
+		Native invoking: public void art.Test993$TestClass2.breakit() args: [this: TestClass2ext]
+			Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83
+		Native invoking: public void art.Test993$TestClass2ext.breakit() args: [this: TestClass2ext]
+			Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83
+		Reflective invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass2ext]
+			Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83
+		Reflective invoking: public void art.Test993$TestClass2.breakit() args: [this: TestClass2ext]
+			Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83
+		Reflective invoking: public void art.Test993$TestClass2ext.breakit() args: [this: TestClass2ext]
+			Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83
+		Invoking "((Breakable)new TestClass2ext()).breakit()"
+			Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83
+		Invoking "((TestClass2)new TestClass2ext()).breakit()"
+			Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83
+		Invoking "new TestClass2ext().breakit())"
+			Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83
+	Breaking on [public void art.Test993$TestClass2ext.breakit() @ 91]
+		Native invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass2ext]
+			Breakpoint: public void art.Test993$TestClass2ext.breakit() @ line=91
+		Native invoking: public void art.Test993$TestClass2.breakit() args: [this: TestClass2ext]
+			Breakpoint: public void art.Test993$TestClass2ext.breakit() @ line=91
+		Native invoking: public void art.Test993$TestClass2ext.breakit() args: [this: TestClass2ext]
+			Breakpoint: public void art.Test993$TestClass2ext.breakit() @ line=91
+		Reflective invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass2ext]
+			Breakpoint: public void art.Test993$TestClass2ext.breakit() @ line=91
+		Reflective invoking: public void art.Test993$TestClass2.breakit() args: [this: TestClass2ext]
+			Breakpoint: public void art.Test993$TestClass2ext.breakit() @ line=91
+		Reflective invoking: public void art.Test993$TestClass2ext.breakit() args: [this: TestClass2ext]
+			Breakpoint: public void art.Test993$TestClass2ext.breakit() @ line=91
+		Invoking "((Breakable)new TestClass2ext()).breakit()"
+			Breakpoint: public void art.Test993$TestClass2ext.breakit() @ line=91
+		Invoking "((TestClass2)new TestClass2ext()).breakit()"
+			Breakpoint: public void art.Test993$TestClass2ext.breakit() @ line=91
+		Invoking "new TestClass2ext().breakit())"
+			Breakpoint: public void art.Test993$TestClass2ext.breakit() @ line=91
+	Breaking on [public default void art.Test993$Breakable.breakit() @ 55, public void art.Test993$TestClass2.breakit() @ 83]
+		Native invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass2ext]
+			Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83
+		Native invoking: public void art.Test993$TestClass2.breakit() args: [this: TestClass2ext]
+			Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83
+		Native invoking: public void art.Test993$TestClass2ext.breakit() args: [this: TestClass2ext]
+			Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83
+		Reflective invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass2ext]
+			Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83
+		Reflective invoking: public void art.Test993$TestClass2.breakit() args: [this: TestClass2ext]
+			Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83
+		Reflective invoking: public void art.Test993$TestClass2ext.breakit() args: [this: TestClass2ext]
+			Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83
+		Invoking "((Breakable)new TestClass2ext()).breakit()"
+			Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83
+		Invoking "((TestClass2)new TestClass2ext()).breakit()"
+			Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83
+		Invoking "new TestClass2ext().breakit())"
+			Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83
+	Breaking on [public default void art.Test993$Breakable.breakit() @ 55, public void art.Test993$TestClass2ext.breakit() @ 91]
+		Native invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass2ext]
+			Breakpoint: public void art.Test993$TestClass2ext.breakit() @ line=91
+		Native invoking: public void art.Test993$TestClass2.breakit() args: [this: TestClass2ext]
+			Breakpoint: public void art.Test993$TestClass2ext.breakit() @ line=91
+		Native invoking: public void art.Test993$TestClass2ext.breakit() args: [this: TestClass2ext]
+			Breakpoint: public void art.Test993$TestClass2ext.breakit() @ line=91
+		Reflective invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass2ext]
+			Breakpoint: public void art.Test993$TestClass2ext.breakit() @ line=91
+		Reflective invoking: public void art.Test993$TestClass2.breakit() args: [this: TestClass2ext]
+			Breakpoint: public void art.Test993$TestClass2ext.breakit() @ line=91
+		Reflective invoking: public void art.Test993$TestClass2ext.breakit() args: [this: TestClass2ext]
+			Breakpoint: public void art.Test993$TestClass2ext.breakit() @ line=91
+		Invoking "((Breakable)new TestClass2ext()).breakit()"
+			Breakpoint: public void art.Test993$TestClass2ext.breakit() @ line=91
+		Invoking "((TestClass2)new TestClass2ext()).breakit()"
+			Breakpoint: public void art.Test993$TestClass2ext.breakit() @ line=91
+		Invoking "new TestClass2ext().breakit())"
+			Breakpoint: public void art.Test993$TestClass2ext.breakit() @ line=91
+	Breaking on [public void art.Test993$TestClass2.breakit() @ 83, public void art.Test993$TestClass2ext.breakit() @ 91]
+		Native invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass2ext]
+			Breakpoint: public void art.Test993$TestClass2ext.breakit() @ line=91
+			Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83
+		Native invoking: public void art.Test993$TestClass2.breakit() args: [this: TestClass2ext]
+			Breakpoint: public void art.Test993$TestClass2ext.breakit() @ line=91
+			Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83
+		Native invoking: public void art.Test993$TestClass2ext.breakit() args: [this: TestClass2ext]
+			Breakpoint: public void art.Test993$TestClass2ext.breakit() @ line=91
+			Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83
+		Reflective invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass2ext]
+			Breakpoint: public void art.Test993$TestClass2ext.breakit() @ line=91
+			Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83
+		Reflective invoking: public void art.Test993$TestClass2.breakit() args: [this: TestClass2ext]
+			Breakpoint: public void art.Test993$TestClass2ext.breakit() @ line=91
+			Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83
+		Reflective invoking: public void art.Test993$TestClass2ext.breakit() args: [this: TestClass2ext]
+			Breakpoint: public void art.Test993$TestClass2ext.breakit() @ line=91
+			Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83
+		Invoking "((Breakable)new TestClass2ext()).breakit()"
+			Breakpoint: public void art.Test993$TestClass2ext.breakit() @ line=91
+			Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83
+		Invoking "((TestClass2)new TestClass2ext()).breakit()"
+			Breakpoint: public void art.Test993$TestClass2ext.breakit() @ line=91
+			Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83
+		Invoking "new TestClass2ext().breakit())"
+			Breakpoint: public void art.Test993$TestClass2ext.breakit() @ line=91
+			Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83
+	Breaking on [public default void art.Test993$Breakable.breakit() @ 55, public void art.Test993$TestClass2.breakit() @ 83, public void art.Test993$TestClass2ext.breakit() @ 91]
+		Native invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass2ext]
+			Breakpoint: public void art.Test993$TestClass2ext.breakit() @ line=91
+			Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83
+		Native invoking: public void art.Test993$TestClass2.breakit() args: [this: TestClass2ext]
+			Breakpoint: public void art.Test993$TestClass2ext.breakit() @ line=91
+			Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83
+		Native invoking: public void art.Test993$TestClass2ext.breakit() args: [this: TestClass2ext]
+			Breakpoint: public void art.Test993$TestClass2ext.breakit() @ line=91
+			Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83
+		Reflective invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass2ext]
+			Breakpoint: public void art.Test993$TestClass2ext.breakit() @ line=91
+			Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83
+		Reflective invoking: public void art.Test993$TestClass2.breakit() args: [this: TestClass2ext]
+			Breakpoint: public void art.Test993$TestClass2ext.breakit() @ line=91
+			Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83
+		Reflective invoking: public void art.Test993$TestClass2ext.breakit() args: [this: TestClass2ext]
+			Breakpoint: public void art.Test993$TestClass2ext.breakit() @ line=91
+			Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83
+		Invoking "((Breakable)new TestClass2ext()).breakit()"
+			Breakpoint: public void art.Test993$TestClass2ext.breakit() @ line=91
+			Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83
+		Invoking "((TestClass2)new TestClass2ext()).breakit()"
+			Breakpoint: public void art.Test993$TestClass2ext.breakit() @ line=91
+			Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83
+		Invoking "new TestClass2ext().breakit())"
+			Breakpoint: public void art.Test993$TestClass2ext.breakit() @ line=91
+			Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83
+Running TestClass3 invokes
+	Breaking on []
+		Native invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass3]
+		Native invoking: public void art.Test993$TestClass3.breakit() args: [this: TestClass3]
+		Reflective invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass3]
+		Reflective invoking: public void art.Test993$TestClass3.breakit() args: [this: TestClass3]
+		Invoking "((Breakable)new TestClass3()).breakit()"
+		Invoking "new TestClass3().breakit())"
+	Breaking on [public default void art.Test993$Breakable.breakit() @ 55]
+		Native invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass3]
+			Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55
+		Native invoking: public void art.Test993$TestClass3.breakit() args: [this: TestClass3]
+			Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55
+		Reflective invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass3]
+			Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55
+		Reflective invoking: public void art.Test993$TestClass3.breakit() args: [this: TestClass3]
+			Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55
+		Invoking "((Breakable)new TestClass3()).breakit()"
+			Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55
+		Invoking "new TestClass3().breakit())"
+			Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55
+	Breaking on [public void art.Test993$TestClass3.breakit() @ 99]
+		Native invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass3]
+			Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99
+		Native invoking: public void art.Test993$TestClass3.breakit() args: [this: TestClass3]
+			Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99
+		Reflective invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass3]
+			Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99
+		Reflective invoking: public void art.Test993$TestClass3.breakit() args: [this: TestClass3]
+			Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99
+		Invoking "((Breakable)new TestClass3()).breakit()"
+			Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99
+		Invoking "new TestClass3().breakit())"
+			Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99
+	Breaking on [public default void art.Test993$Breakable.breakit() @ 55, public void art.Test993$TestClass3.breakit() @ 99]
+		Native invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass3]
+			Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99
+			Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55
+		Native invoking: public void art.Test993$TestClass3.breakit() args: [this: TestClass3]
+			Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99
+			Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55
+		Reflective invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass3]
+			Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99
+			Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55
+		Reflective invoking: public void art.Test993$TestClass3.breakit() args: [this: TestClass3]
+			Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99
+			Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55
+		Invoking "((Breakable)new TestClass3()).breakit()"
+			Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99
+			Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55
+		Invoking "new TestClass3().breakit())"
+			Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99
+			Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55
+Running TestClass3ext invokes
+	Breaking on []
+		Native invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass3ext]
+		Native invoking: public void art.Test993$TestClass3.breakit() args: [this: TestClass3ext]
+		Native invoking: public void art.Test993$TestClass3ext.breakit() args: [this: TestClass3ext]
+		Reflective invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass3ext]
+		Reflective invoking: public void art.Test993$TestClass3.breakit() args: [this: TestClass3ext]
+		Reflective invoking: public void art.Test993$TestClass3ext.breakit() args: [this: TestClass3ext]
+		Invoking "((Breakable)new TestClass3ext()).breakit()"
+		Invoking "((TestClass3)new TestClass3ext()).breakit()"
+		Invoking "new TestClass3ext().breakit())"
+	Breaking on [public default void art.Test993$Breakable.breakit() @ 55]
+		Native invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass3ext]
+			Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55
+		Native invoking: public void art.Test993$TestClass3.breakit() args: [this: TestClass3ext]
+			Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55
+		Native invoking: public void art.Test993$TestClass3ext.breakit() args: [this: TestClass3ext]
+			Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55
+		Reflective invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass3ext]
+			Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55
+		Reflective invoking: public void art.Test993$TestClass3.breakit() args: [this: TestClass3ext]
+			Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55
+		Reflective invoking: public void art.Test993$TestClass3ext.breakit() args: [this: TestClass3ext]
+			Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55
+		Invoking "((Breakable)new TestClass3ext()).breakit()"
+			Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55
+		Invoking "((TestClass3)new TestClass3ext()).breakit()"
+			Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55
+		Invoking "new TestClass3ext().breakit())"
+			Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55
+	Breaking on [public void art.Test993$TestClass3.breakit() @ 99]
+		Native invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass3ext]
+			Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99
+		Native invoking: public void art.Test993$TestClass3.breakit() args: [this: TestClass3ext]
+			Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99
+		Native invoking: public void art.Test993$TestClass3ext.breakit() args: [this: TestClass3ext]
+			Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99
+		Reflective invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass3ext]
+			Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99
+		Reflective invoking: public void art.Test993$TestClass3.breakit() args: [this: TestClass3ext]
+			Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99
+		Reflective invoking: public void art.Test993$TestClass3ext.breakit() args: [this: TestClass3ext]
+			Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99
+		Invoking "((Breakable)new TestClass3ext()).breakit()"
+			Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99
+		Invoking "((TestClass3)new TestClass3ext()).breakit()"
+			Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99
+		Invoking "new TestClass3ext().breakit())"
+			Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99
+	Breaking on [public void art.Test993$TestClass3ext.breakit() @ 108]
+		Native invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass3ext]
+			Breakpoint: public void art.Test993$TestClass3ext.breakit() @ line=108
+		Native invoking: public void art.Test993$TestClass3.breakit() args: [this: TestClass3ext]
+			Breakpoint: public void art.Test993$TestClass3ext.breakit() @ line=108
+		Native invoking: public void art.Test993$TestClass3ext.breakit() args: [this: TestClass3ext]
+			Breakpoint: public void art.Test993$TestClass3ext.breakit() @ line=108
+		Reflective invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass3ext]
+			Breakpoint: public void art.Test993$TestClass3ext.breakit() @ line=108
+		Reflective invoking: public void art.Test993$TestClass3.breakit() args: [this: TestClass3ext]
+			Breakpoint: public void art.Test993$TestClass3ext.breakit() @ line=108
+		Reflective invoking: public void art.Test993$TestClass3ext.breakit() args: [this: TestClass3ext]
+			Breakpoint: public void art.Test993$TestClass3ext.breakit() @ line=108
+		Invoking "((Breakable)new TestClass3ext()).breakit()"
+			Breakpoint: public void art.Test993$TestClass3ext.breakit() @ line=108
+		Invoking "((TestClass3)new TestClass3ext()).breakit()"
+			Breakpoint: public void art.Test993$TestClass3ext.breakit() @ line=108
+		Invoking "new TestClass3ext().breakit())"
+			Breakpoint: public void art.Test993$TestClass3ext.breakit() @ line=108
+	Breaking on [public default void art.Test993$Breakable.breakit() @ 55, public void art.Test993$TestClass3.breakit() @ 99]
+		Native invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass3ext]
+			Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99
+			Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55
+		Native invoking: public void art.Test993$TestClass3.breakit() args: [this: TestClass3ext]
+			Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99
+			Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55
+		Native invoking: public void art.Test993$TestClass3ext.breakit() args: [this: TestClass3ext]
+			Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99
+			Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55
+		Reflective invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass3ext]
+			Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99
+			Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55
+		Reflective invoking: public void art.Test993$TestClass3.breakit() args: [this: TestClass3ext]
+			Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99
+			Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55
+		Reflective invoking: public void art.Test993$TestClass3ext.breakit() args: [this: TestClass3ext]
+			Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99
+			Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55
+		Invoking "((Breakable)new TestClass3ext()).breakit()"
+			Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99
+			Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55
+		Invoking "((TestClass3)new TestClass3ext()).breakit()"
+			Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99
+			Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55
+		Invoking "new TestClass3ext().breakit())"
+			Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99
+			Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55
+	Breaking on [public default void art.Test993$Breakable.breakit() @ 55, public void art.Test993$TestClass3ext.breakit() @ 108]
+		Native invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass3ext]
+			Breakpoint: public void art.Test993$TestClass3ext.breakit() @ line=108
+			Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55
+		Native invoking: public void art.Test993$TestClass3.breakit() args: [this: TestClass3ext]
+			Breakpoint: public void art.Test993$TestClass3ext.breakit() @ line=108
+			Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55
+		Native invoking: public void art.Test993$TestClass3ext.breakit() args: [this: TestClass3ext]
+			Breakpoint: public void art.Test993$TestClass3ext.breakit() @ line=108
+			Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55
+		Reflective invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass3ext]
+			Breakpoint: public void art.Test993$TestClass3ext.breakit() @ line=108
+			Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55
+		Reflective invoking: public void art.Test993$TestClass3.breakit() args: [this: TestClass3ext]
+			Breakpoint: public void art.Test993$TestClass3ext.breakit() @ line=108
+			Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55
+		Reflective invoking: public void art.Test993$TestClass3ext.breakit() args: [this: TestClass3ext]
+			Breakpoint: public void art.Test993$TestClass3ext.breakit() @ line=108
+			Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55
+		Invoking "((Breakable)new TestClass3ext()).breakit()"
+			Breakpoint: public void art.Test993$TestClass3ext.breakit() @ line=108
+			Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55
+		Invoking "((TestClass3)new TestClass3ext()).breakit()"
+			Breakpoint: public void art.Test993$TestClass3ext.breakit() @ line=108
+			Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55
+		Invoking "new TestClass3ext().breakit())"
+			Breakpoint: public void art.Test993$TestClass3ext.breakit() @ line=108
+			Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55
+	Breaking on [public void art.Test993$TestClass3.breakit() @ 99, public void art.Test993$TestClass3ext.breakit() @ 108]
+		Native invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass3ext]
+			Breakpoint: public void art.Test993$TestClass3ext.breakit() @ line=108
+			Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99
+		Native invoking: public void art.Test993$TestClass3.breakit() args: [this: TestClass3ext]
+			Breakpoint: public void art.Test993$TestClass3ext.breakit() @ line=108
+			Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99
+		Native invoking: public void art.Test993$TestClass3ext.breakit() args: [this: TestClass3ext]
+			Breakpoint: public void art.Test993$TestClass3ext.breakit() @ line=108
+			Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99
+		Reflective invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass3ext]
+			Breakpoint: public void art.Test993$TestClass3ext.breakit() @ line=108
+			Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99
+		Reflective invoking: public void art.Test993$TestClass3.breakit() args: [this: TestClass3ext]
+			Breakpoint: public void art.Test993$TestClass3ext.breakit() @ line=108
+			Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99
+		Reflective invoking: public void art.Test993$TestClass3ext.breakit() args: [this: TestClass3ext]
+			Breakpoint: public void art.Test993$TestClass3ext.breakit() @ line=108
+			Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99
+		Invoking "((Breakable)new TestClass3ext()).breakit()"
+			Breakpoint: public void art.Test993$TestClass3ext.breakit() @ line=108
+			Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99
+		Invoking "((TestClass3)new TestClass3ext()).breakit()"
+			Breakpoint: public void art.Test993$TestClass3ext.breakit() @ line=108
+			Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99
+		Invoking "new TestClass3ext().breakit())"
+			Breakpoint: public void art.Test993$TestClass3ext.breakit() @ line=108
+			Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99
+	Breaking on [public default void art.Test993$Breakable.breakit() @ 55, public void art.Test993$TestClass3.breakit() @ 99, public void art.Test993$TestClass3ext.breakit() @ 108]
+		Native invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass3ext]
+			Breakpoint: public void art.Test993$TestClass3ext.breakit() @ line=108
+			Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99
+			Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55
+		Native invoking: public void art.Test993$TestClass3.breakit() args: [this: TestClass3ext]
+			Breakpoint: public void art.Test993$TestClass3ext.breakit() @ line=108
+			Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99
+			Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55
+		Native invoking: public void art.Test993$TestClass3ext.breakit() args: [this: TestClass3ext]
+			Breakpoint: public void art.Test993$TestClass3ext.breakit() @ line=108
+			Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99
+			Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55
+		Reflective invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass3ext]
+			Breakpoint: public void art.Test993$TestClass3ext.breakit() @ line=108
+			Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99
+			Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55
+		Reflective invoking: public void art.Test993$TestClass3.breakit() args: [this: TestClass3ext]
+			Breakpoint: public void art.Test993$TestClass3ext.breakit() @ line=108
+			Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99
+			Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55
+		Reflective invoking: public void art.Test993$TestClass3ext.breakit() args: [this: TestClass3ext]
+			Breakpoint: public void art.Test993$TestClass3ext.breakit() @ line=108
+			Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99
+			Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55
+		Invoking "((Breakable)new TestClass3ext()).breakit()"
+			Breakpoint: public void art.Test993$TestClass3ext.breakit() @ line=108
+			Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99
+			Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55
+		Invoking "((TestClass3)new TestClass3ext()).breakit()"
+			Breakpoint: public void art.Test993$TestClass3ext.breakit() @ line=108
+			Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99
+			Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55
+		Invoking "new TestClass3ext().breakit())"
+			Breakpoint: public void art.Test993$TestClass3ext.breakit() @ line=108
+			Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99
+			Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55
+Running private instance invoke
+	Breaking on []
+		Native invoking: private void art.Test993$TestClass4.privateMethod() args: [this: TestClass4]
+		Invoking "new TestClass4().callPrivateMethod()"
+	Breaking on [private void art.Test993$TestClass4.privateMethod() @ 118]
+		Native invoking: private void art.Test993$TestClass4.privateMethod() args: [this: TestClass4]
+			Breakpoint: private void art.Test993$TestClass4.privateMethod() @ line=118
+		Invoking "new TestClass4().callPrivateMethod()"
+			Breakpoint: private void art.Test993$TestClass4.privateMethod() @ line=118
+Running TestClass1 constructor
+	Breaking on []
+		Native constructor: public art.Test993$TestClass1(), type: class art.Test993$TestClass1
+			Created: TestClass1
+		Reflective constructor: public art.Test993$TestClass1()
+			Created: TestClass1
+		Constructing: new TestClass1()
+			Created: TestClass1
+	Breaking on [public art.Test993$TestClass1() @ 62]
+		Native constructor: public art.Test993$TestClass1(), type: class art.Test993$TestClass1
+			Breakpoint: public art.Test993$TestClass1() @ line=62
+			Created: TestClass1
+		Reflective constructor: public art.Test993$TestClass1()
+			Breakpoint: public art.Test993$TestClass1() @ line=62
+			Created: TestClass1
+		Constructing: new TestClass1()
+			Breakpoint: public art.Test993$TestClass1() @ line=62
+			Created: TestClass1
+Running TestClass1ext constructor
+	Breaking on []
+		Native constructor: public art.Test993$TestClass1ext(), type: class art.Test993$TestClass1ext
+			Created: TestClass1Ext
+		Reflective constructor: public art.Test993$TestClass1ext()
+			Created: TestClass1Ext
+		Constructing: new TestClass1ext()
+			Created: TestClass1Ext
+	Breaking on [public art.Test993$TestClass1() @ 62]
+		Native constructor: public art.Test993$TestClass1ext(), type: class art.Test993$TestClass1ext
+			Breakpoint: public art.Test993$TestClass1() @ line=62
+			Created: TestClass1Ext
+		Reflective constructor: public art.Test993$TestClass1ext()
+			Breakpoint: public art.Test993$TestClass1() @ line=62
+			Created: TestClass1Ext
+		Constructing: new TestClass1ext()
+			Breakpoint: public art.Test993$TestClass1() @ line=62
+			Created: TestClass1Ext
+	Breaking on [public art.Test993$TestClass1ext() @ 70]
+		Native constructor: public art.Test993$TestClass1ext(), type: class art.Test993$TestClass1ext
+			Breakpoint: public art.Test993$TestClass1ext() @ line=70
+			Created: TestClass1Ext
+		Reflective constructor: public art.Test993$TestClass1ext()
+			Breakpoint: public art.Test993$TestClass1ext() @ line=70
+			Created: TestClass1Ext
+		Constructing: new TestClass1ext()
+			Breakpoint: public art.Test993$TestClass1ext() @ line=70
+			Created: TestClass1Ext
+	Breaking on [public art.Test993$TestClass1() @ 62, public art.Test993$TestClass1ext() @ 70]
+		Native constructor: public art.Test993$TestClass1ext(), type: class art.Test993$TestClass1ext
+			Breakpoint: public art.Test993$TestClass1ext() @ line=70
+			Breakpoint: public art.Test993$TestClass1() @ line=62
+			Created: TestClass1Ext
+		Reflective constructor: public art.Test993$TestClass1ext()
+			Breakpoint: public art.Test993$TestClass1ext() @ line=70
+			Breakpoint: public art.Test993$TestClass1() @ line=62
+			Created: TestClass1Ext
+		Constructing: new TestClass1ext()
+			Breakpoint: public art.Test993$TestClass1ext() @ line=70
+			Breakpoint: public art.Test993$TestClass1() @ line=62
+			Created: TestClass1Ext
diff --git a/test/993-breakpoints/info.txt b/test/993-breakpoints/info.txt
new file mode 100644
index 0000000..b5eb546
--- /dev/null
+++ b/test/993-breakpoints/info.txt
@@ -0,0 +1,7 @@
+Test basic JVMTI breakpoint functionality.
+
+This test places a breakpoint on the first instruction of a number of functions
+that are entered in every way possible for the given class of method.
+
+It also tests that breakpoints don't interfere with each other by having
+multiple breakpoints be set at once.
diff --git a/test/993-breakpoints/run b/test/993-breakpoints/run
new file mode 100755
index 0000000..51875a7
--- /dev/null
+++ b/test/993-breakpoints/run
@@ -0,0 +1,18 @@
+#!/bin/bash
+#
+# Copyright 2017 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.
+
+# Ask for stack traces to be dumped to a file rather than to stdout.
+./default-run "$@" --jvmti
diff --git a/test/993-breakpoints/src/Main.java b/test/993-breakpoints/src/Main.java
new file mode 100644
index 0000000..b11f6f8
--- /dev/null
+++ b/test/993-breakpoints/src/Main.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+public class Main {
+  public static void main(String[] args) throws Exception {
+    art.Test993.run();
+  }
+}
diff --git a/test/993-breakpoints/src/art/Breakpoint.java b/test/993-breakpoints/src/art/Breakpoint.java
new file mode 100644
index 0000000..2a370eb
--- /dev/null
+++ b/test/993-breakpoints/src/art/Breakpoint.java
@@ -0,0 +1,202 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+package art;
+
+import java.lang.reflect.Executable;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.Objects;
+
+public class Breakpoint {
+  public static class Manager {
+    public static class BP {
+      public final Executable method;
+      public final long location;
+
+      public BP(Executable method) {
+        this(method, getStartLocation(method));
+      }
+
+      public BP(Executable method, long location) {
+        this.method = method;
+        this.location = location;
+      }
+
+      @Override
+      public boolean equals(Object other) {
+        return (other instanceof BP) &&
+            method.equals(((BP)other).method) &&
+            location == ((BP)other).location;
+      }
+
+      @Override
+      public String toString() {
+        return method.toString() + " @ " + getLine();
+      }
+
+      @Override
+      public int hashCode() {
+        return Objects.hash(method, location);
+      }
+
+      public int getLine() {
+        try {
+          LineNumber[] lines = getLineNumberTable(method);
+          int best = -1;
+          for (LineNumber l : lines) {
+            if (l.location > location) {
+              break;
+            } else {
+              best = l.line;
+            }
+          }
+          return best;
+        } catch (Exception e) {
+          return -1;
+        }
+      }
+    }
+
+    private Set<BP> breaks = new HashSet<>();
+
+    public void setBreakpoints(BP... bs) {
+      for (BP b : bs) {
+        if (breaks.add(b)) {
+          Breakpoint.setBreakpoint(b.method, b.location);
+        }
+      }
+    }
+    public void setBreakpoint(Executable method, long location) {
+      setBreakpoints(new BP(method, location));
+    }
+
+    public void clearBreakpoints(BP... bs) {
+      for (BP b : bs) {
+        if (breaks.remove(b)) {
+          Breakpoint.clearBreakpoint(b.method, b.location);
+        }
+      }
+    }
+    public void clearBreakpoint(Executable method, long location) {
+      clearBreakpoints(new BP(method, location));
+    }
+
+    public void clearAllBreakpoints() {
+      clearBreakpoints(breaks.toArray(new BP[0]));
+    }
+  }
+
+  public static void startBreakpointWatch(Class<?> methodClass,
+                                          Executable breakpointReached,
+                                          Thread thr) {
+    startBreakpointWatch(methodClass, breakpointReached, false, thr);
+  }
+
+  /**
+   * Enables the trapping of breakpoint events.
+   *
+   * If allowRecursive == true then breakpoints will be sent even if one is currently being handled.
+   */
+  public static native void startBreakpointWatch(Class<?> methodClass,
+                                                 Executable breakpointReached,
+                                                 boolean allowRecursive,
+                                                 Thread thr);
+  public static native void stopBreakpointWatch(Thread thr);
+
+  public static final class LineNumber {
+    public final long location;
+    public final int line;
+
+    private LineNumber(long loc, int line) {
+      this.location = loc;
+      this.line = line;
+    }
+
+    public boolean equals(Object other) {
+      return other instanceof LineNumber && ((LineNumber)other).line == line &&
+          ((LineNumber)other).location == location;
+    }
+
+    public int compareTo(LineNumber other) {
+      int v = Integer.valueOf(line).compareTo(Integer.valueOf(other.line));
+      if (v != 0) {
+        return v;
+      } else {
+        return Long.valueOf(location).compareTo(Long.valueOf(other.location));
+      }
+    }
+  }
+
+  public static native void setBreakpoint(Executable m, long loc);
+  public static void setBreakpoint(Executable m, LineNumber l) {
+    setBreakpoint(m, l.location);
+  }
+
+  public static native void clearBreakpoint(Executable m, long loc);
+  public static void clearBreakpoint(Executable m, LineNumber l) {
+    clearBreakpoint(m, l.location);
+  }
+
+  private static native Object[] getLineNumberTableNative(Executable m);
+  public static LineNumber[] getLineNumberTable(Executable m) {
+    Object[] nativeTable = getLineNumberTableNative(m);
+    long[] location = (long[])(nativeTable[0]);
+    int[] lines = (int[])(nativeTable[1]);
+    if (lines.length != location.length) {
+      throw new Error("Lines and locations have different lengths!");
+    }
+    LineNumber[] out = new LineNumber[lines.length];
+    for (int i = 0; i < lines.length; i++) {
+      out[i] = new LineNumber(location[i], lines[i]);
+    }
+    return out;
+  }
+
+  public static native long getStartLocation(Executable m);
+
+  public static int locationToLine(Executable m, long location) {
+    try {
+      Breakpoint.LineNumber[] lines = Breakpoint.getLineNumberTable(m);
+      int best = -1;
+      for (Breakpoint.LineNumber l : lines) {
+        if (l.location > location) {
+          break;
+        } else {
+          best = l.line;
+        }
+      }
+      return best;
+    } catch (Exception e) {
+      return -1;
+    }
+  }
+
+  public static long lineToLocation(Executable m, int line) throws Exception {
+    try {
+      Breakpoint.LineNumber[] lines = Breakpoint.getLineNumberTable(m);
+      for (Breakpoint.LineNumber l : lines) {
+        if (l.line == line) {
+          return l.location;
+        }
+      }
+      throw new Exception("Unable to find line " + line + " in " + m);
+    } catch (Exception e) {
+      throw new Exception("Unable to get line number info for " + m, e);
+    }
+  }
+}
+
diff --git a/test/993-breakpoints/src/art/Test993.java b/test/993-breakpoints/src/art/Test993.java
new file mode 100644
index 0000000..781ebff
--- /dev/null
+++ b/test/993-breakpoints/src/art/Test993.java
@@ -0,0 +1,498 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+package art;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.Arrays;
+import java.lang.reflect.Executable;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Method;
+import java.util.List;
+import java.util.Set;
+import java.util.Spliterator;
+import java.util.Spliterators;
+import java.util.Collection;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.function.IntUnaryOperator;
+import java.util.function.Supplier;
+
+public class Test993 {
+
+  public static final Breakpoint.Manager MANAGER = new Breakpoint.Manager();
+
+  // A function we can use as a start breakpoint.
+  public static void breakpoint() {
+    return;
+  }
+
+  private static void privateBreakpoint() {
+    return;
+  }
+
+  // An interface with a default method we can break on.
+  static interface Breakable {
+    public static void iBreakpoint() {
+      return;
+    }
+
+    public default void breakit() {
+      return;
+    }
+  }
+
+  // A class that has a default method we breakpoint on.
+  public static class TestClass1 implements Breakable {
+    public TestClass1() {
+      super();
+    }
+    public String toString() { return "TestClass1"; }
+  }
+
+  // A class that overrides a default method that we can breakpoint on and calls super.
+  public static class TestClass1ext extends TestClass1 {
+    public TestClass1ext() {
+      super();
+    }
+    public String toString() { return "TestClass1Ext"; }
+    public void breakit() {
+      super.breakit();
+    }
+  }
+
+
+  // A class that overrides a default method that we can breakpoint on.
+  public static class TestClass2 implements Breakable {
+    public String toString() { return "TestClass2"; }
+    public void breakit() {
+      return;
+    }
+  }
+
+  // A class that overrides a default method that we can breakpoint on and calls super.
+  public static class TestClass2ext extends TestClass2 {
+    public String toString() { return "TestClass2ext"; }
+    public void breakit() {
+      super.breakit();
+    }
+  }
+
+  // A class that overrides a default method and calls it directly with interface invoke-super
+  public static class TestClass3 implements Breakable {
+    public String toString() { return "TestClass3"; }
+    public void breakit() {
+      Breakable.super.breakit();
+    }
+  }
+
+  // A class that overrides a default method that we can breakpoint on and calls super to a class
+  // that uses interface-invoke-super.
+  public static class TestClass3ext extends TestClass3 {
+    public String toString() { return "TestClass3ext"; }
+    public void breakit() {
+      super.breakit();
+    }
+  }
+
+  public static class TestClass4 {
+    public String toString() { return "TestClass4"; }
+    public void callPrivateMethod() {
+      privateMethod();
+    }
+    private void privateMethod() {
+      return;
+    }
+  }
+
+  public static void notifyBreakpointReached(Thread thr, Executable e, long loc) {
+    System.out.println("\t\t\tBreakpoint: " + e + " @ line=" + Breakpoint.locationToLine(e, loc));
+  }
+
+  public static interface ThrowRunnable extends Runnable {
+    public default void run() {
+      try {
+        runThrow();
+      } catch (Exception e) {
+        throw new Error("Caught error while running " + this, e);
+      }
+    }
+    public void runThrow() throws Exception;
+  }
+
+  public static class InvokeDirect implements Runnable {
+    String msg;
+    Runnable r;
+    public InvokeDirect(String msg, Runnable r) {
+      this.msg = msg;
+      this.r = r;
+    }
+    @Override
+    public void run() {
+      System.out.println("\t\tInvoking \"" + msg + "\"");
+      r.run();
+    }
+  }
+
+  public static class InvokeReflect implements ThrowRunnable {
+    Method m;
+    Object this_arg;
+    public InvokeReflect(Method m, Object this_arg) {
+      this.m = m;
+      this.this_arg = this_arg;
+    }
+
+    @Override
+    public void runThrow() throws Exception {
+      System.out.println("\t\tReflective invoking: " + m + " args: [this: " + this_arg + "]");
+      m.invoke(this_arg);
+    }
+  }
+
+  public static class InvokeNative implements Runnable {
+    Method m;
+    Object this_arg;
+    public InvokeNative(Method m, Object this_arg) {
+      this.m = m;
+      this.this_arg = this_arg;
+    }
+
+    @Override
+    public void run() {
+      System.out.println("\t\tNative invoking: " + m + " args: [this: " + this_arg + "]");
+      invokeNative(m, m.getDeclaringClass(), this_arg);
+    }
+  }
+
+  public static native void invokeNative(Method m, Class<?> clazz, Object thizz);
+
+  public static class ConstructDirect implements Runnable {
+    String msg;
+    Supplier<Object> s;
+    public ConstructDirect(String msg, Supplier<Object> s) {
+      this.msg = msg;
+      this.s = s;
+    }
+
+    @Override
+    public void run() {
+      System.out.println("\t\tConstructing: " + msg);
+      System.out.println("\t\t\tCreated: " + s.get());
+    }
+  }
+
+  public static class ConstructReflect implements ThrowRunnable {
+    Constructor<?> m;
+    public ConstructReflect(Constructor<?> m) {
+      this.m = m;
+    }
+
+    @Override
+    public void runThrow() throws Exception {
+      System.out.println("\t\tReflective constructor: " + m);
+      System.out.println("\t\t\tCreated: " + m.newInstance());
+    }
+  }
+
+  public static class ConstructNative implements Runnable {
+    Constructor<?> m;
+    Class type;
+    public ConstructNative(Constructor<?> m) {
+      this.m = m;
+      this.type = m.getDeclaringClass();
+    }
+
+    @Override
+    public void run() {
+      System.out.println("\t\tNative constructor: " + m + ", type: " + type);
+      System.out.println("\t\t\tCreated: " + constructNative(m, type));
+    }
+  }
+
+  public static native Object constructNative(Constructor m, Class<?> clazz);
+
+  private static <T> List<List<T>> combinations(List<T> items, int len) {
+    if (len > items.size()) {
+      throw new Error("Bad length" + len + " " + items);
+    }
+    if (len == 1) {
+      List<List<T>> out = new ArrayList<>();
+      for (T t : items) {
+        out.add(Arrays.asList(t));
+      }
+      return out;
+    }
+    List<List<T>> out = new ArrayList<>();
+    for (int rem = 0; rem <= items.size() - len; rem++) {
+      for (List<T> others : combinations(items.subList(rem + 1, items.size()), len - 1)) {
+        List<T> newone = new ArrayList<>();
+        newone.add(items.get(rem));
+        newone.addAll(others);
+        out.add(newone);
+      }
+    }
+    return out;
+  }
+
+  private static <T> List<List<T>> allCombinations(List<T> items) {
+    List<List<T>> out = new ArrayList<List<T>>();
+    out.add(new ArrayList<>());
+    for (int i = 0; i < items.size(); i++) {
+      out.addAll(combinations(items, i + 1));
+    }
+    return out;
+  }
+
+  private static Breakpoint.Manager.BP BP(Executable m) {
+    return new Breakpoint.Manager.BP(m);
+  }
+
+  public static void run() throws Exception {
+    // Set up breakpoints
+    Breakpoint.stopBreakpointWatch(Thread.currentThread());
+    Breakpoint.startBreakpointWatch(
+        Test993.class,
+        Test993.class.getDeclaredMethod("notifyBreakpointReached",
+          Thread.class, Executable.class, Long.TYPE),
+        Thread.currentThread());
+
+    runMethodTests();
+    runConstructorTests();
+
+    Breakpoint.stopBreakpointWatch(Thread.currentThread());
+  }
+
+  public static void runConstructorTests() throws Exception {
+    // The constructors we will be breaking on.
+    Constructor<?> tc1_construct = TestClass1.class.getConstructor();
+    Constructor<?> tc1ext_construct = TestClass1ext.class.getConstructor();
+
+    Runnable[] tc1_constructors = new Runnable[] {
+      new ConstructNative(tc1_construct),
+      new ConstructReflect(tc1_construct),
+      new ConstructDirect("new TestClass1()", TestClass1::new),
+    };
+    Breakpoint.Manager.BP[] tc1_bps = new Breakpoint.Manager.BP[] {
+      BP(tc1_construct),
+    };
+    runTestGroups("TestClass1 constructor", tc1_constructors, tc1_bps);
+
+    Runnable[] tc1ext_constructors = new Runnable[] {
+      new ConstructNative(tc1ext_construct),
+      new ConstructReflect(tc1ext_construct),
+      new ConstructDirect("new TestClass1ext()", TestClass1ext::new),
+    };
+    Breakpoint.Manager.BP[] tc1ext_bps = new Breakpoint.Manager.BP[] {
+      BP(tc1_construct), BP(tc1ext_construct),
+    };
+    runTestGroups("TestClass1ext constructor", tc1ext_constructors, tc1ext_bps);
+  }
+
+  public static void runMethodTests() throws Exception {
+    // The methods we will be breaking on.
+    Method breakpoint_method = Test993.class.getDeclaredMethod("breakpoint");
+    Method private_breakpoint_method = Test993.class.getDeclaredMethod("privateBreakpoint");
+    Method i_breakpoint_method = Breakable.class.getDeclaredMethod("iBreakpoint");
+    Method breakit_method = Breakable.class.getDeclaredMethod("breakit");
+    Method breakit_method_tc1ext = TestClass1ext.class.getDeclaredMethod("breakit");
+    Method breakit_method_tc2 = TestClass2.class.getDeclaredMethod("breakit");
+    Method breakit_method_tc2ext = TestClass2ext.class.getDeclaredMethod("breakit");
+    Method breakit_method_tc3 = TestClass3.class.getDeclaredMethod("breakit");
+    Method breakit_method_tc3ext = TestClass3ext.class.getDeclaredMethod("breakit");
+    Method private_method = TestClass4.class.getDeclaredMethod("privateMethod");
+
+    // Static class function
+    Runnable[] static_invokes = new Runnable[] {
+      new InvokeNative(breakpoint_method, null),
+
+      new InvokeReflect(breakpoint_method, null),
+
+      new InvokeDirect("Test993::breakpoint", Test993::breakpoint),
+    };
+    Breakpoint.Manager.BP[] static_breakpoints = new Breakpoint.Manager.BP[] {
+      BP(breakpoint_method)
+    };
+    runTestGroups("static invoke", static_invokes, static_breakpoints);
+
+    // Static private class function
+    Runnable[] private_static_invokes = new Runnable[] {
+      new InvokeNative(private_breakpoint_method, null),
+
+      new InvokeDirect("Test993::privateBreakpoint", Test993::privateBreakpoint),
+    };
+    Breakpoint.Manager.BP[] private_static_breakpoints = new Breakpoint.Manager.BP[] {
+      BP(private_breakpoint_method)
+    };
+    runTestGroups("private static invoke", private_static_invokes, private_static_breakpoints);
+
+    // Static interface function.
+    Runnable[] i_static_invokes = new Runnable[] {
+      new InvokeNative(i_breakpoint_method, null),
+
+      new InvokeReflect(i_breakpoint_method, null),
+
+      new InvokeDirect("Breakable::iBreakpoint", Breakable::iBreakpoint),
+    };
+    Breakpoint.Manager.BP[] i_static_breakpoints = new Breakpoint.Manager.BP[] {
+      BP(i_breakpoint_method)
+    };
+    runTestGroups("interface static invoke", i_static_invokes, i_static_breakpoints);
+
+    // Call default method through a class.
+    Runnable[] tc1_invokes = new Runnable[] {
+      new InvokeNative(breakit_method, new TestClass1()),
+
+      new InvokeReflect(breakit_method, new TestClass1()),
+
+      new InvokeDirect("((Breakable)new TestClass1()).breakit()",
+                  () -> ((Breakable)new TestClass1()).breakit()),
+      new InvokeDirect("new TestClass1().breakit()",
+                  () -> new TestClass1().breakit()),
+    };
+    Breakpoint.Manager.BP[] tc1_breakpoints = new Breakpoint.Manager.BP[] {
+      BP(breakit_method)
+    };
+    runTestGroups("TestClass1 invokes", tc1_invokes, tc1_breakpoints);
+
+    // Call default method through an override and normal invoke-super
+    Runnable[] tc1ext_invokes = new Runnable[] {
+      new InvokeNative(breakit_method, new TestClass1ext()),
+      new InvokeNative(breakit_method_tc1ext, new TestClass1ext()),
+
+      new InvokeReflect(breakit_method, new TestClass1ext()),
+      new InvokeReflect(breakit_method_tc1ext, new TestClass1ext()),
+
+      new InvokeDirect("((Breakable)new TestClass1ext()).breakit()",
+                  () -> ((Breakable)new TestClass1ext()).breakit()),
+      new InvokeDirect("((TestClass1)new TestClass1ext()).breakit()",
+                  () -> ((TestClass1)new TestClass1ext()).breakit()),
+      new InvokeDirect("new TestClass1ext().breakit()",
+                  () -> new TestClass1ext().breakit()),
+    };
+    Breakpoint.Manager.BP[] tc1ext_breakpoints = new Breakpoint.Manager.BP[] {
+      BP(breakit_method), BP(breakit_method_tc1ext)
+    };
+    runTestGroups("TestClass1ext invokes", tc1ext_invokes, tc1ext_breakpoints);
+
+    // Override default/interface method.
+    Runnable[] tc2_invokes = new Runnable[] {
+      new InvokeNative(breakit_method, new TestClass2()),
+      new InvokeNative(breakit_method_tc2, new TestClass2()),
+
+      new InvokeReflect(breakit_method, new TestClass2()),
+      new InvokeReflect(breakit_method_tc2, new TestClass2()),
+
+      new InvokeDirect("((Breakable)new TestClass2()).breakit()",
+                  () -> ((Breakable)new TestClass2()).breakit()),
+      new InvokeDirect("new TestClass2().breakit()",
+                  () -> new TestClass2().breakit()),
+    };
+    Breakpoint.Manager.BP[] tc2_breakpoints = new Breakpoint.Manager.BP[] {
+      BP(breakit_method), BP(breakit_method_tc2)
+    };
+    runTestGroups("TestClass2 invokes", tc2_invokes, tc2_breakpoints);
+
+    // Call overridden method using invoke-super
+    Runnable[] tc2ext_invokes = new Runnable[] {
+      new InvokeNative(breakit_method, new TestClass2ext()),
+      new InvokeNative(breakit_method_tc2, new TestClass2ext()),
+      new InvokeNative(breakit_method_tc2ext, new TestClass2ext()),
+
+      new InvokeReflect(breakit_method, new TestClass2ext()),
+      new InvokeReflect(breakit_method_tc2, new TestClass2ext()),
+      new InvokeReflect(breakit_method_tc2ext, new TestClass2ext()),
+
+      new InvokeDirect("((Breakable)new TestClass2ext()).breakit()",
+                  () -> ((Breakable)new TestClass2ext()).breakit()),
+      new InvokeDirect("((TestClass2)new TestClass2ext()).breakit()",
+                  () -> ((TestClass2)new TestClass2ext()).breakit()),
+      new InvokeDirect("new TestClass2ext().breakit())",
+                  () -> new TestClass2ext().breakit()),
+    };
+    Breakpoint.Manager.BP[] tc2ext_breakpoints = new Breakpoint.Manager.BP[] {
+      BP(breakit_method), BP(breakit_method_tc2), BP(breakit_method_tc2ext)
+    };
+    runTestGroups("TestClass2ext invokes", tc2ext_invokes, tc2ext_breakpoints);
+
+    // Override default method and call it using interface-invoke-super
+    Runnable[] tc3_invokes = new Runnable[] {
+      new InvokeNative(breakit_method, new TestClass3()),
+      new InvokeNative(breakit_method_tc3, new TestClass3()),
+
+      new InvokeReflect(breakit_method, new TestClass3()),
+      new InvokeReflect(breakit_method_tc3, new TestClass3()),
+
+      new InvokeDirect("((Breakable)new TestClass3()).breakit()",
+                  () -> ((Breakable)new TestClass3()).breakit()),
+      new InvokeDirect("new TestClass3().breakit())",
+                  () -> new TestClass3().breakit()),
+    };
+    Breakpoint.Manager.BP[] tc3_breakpoints = new Breakpoint.Manager.BP[] {
+      BP(breakit_method), BP(breakit_method_tc3)
+    };
+    runTestGroups("TestClass3 invokes", tc3_invokes, tc3_breakpoints);
+
+    // Call overridden method using invoke-super
+    Runnable[] tc3ext_invokes = new Runnable[] {
+      new InvokeNative(breakit_method, new TestClass3ext()),
+      new InvokeNative(breakit_method_tc3, new TestClass3ext()),
+      new InvokeNative(breakit_method_tc3ext, new TestClass3ext()),
+
+      new InvokeReflect(breakit_method, new TestClass3ext()),
+      new InvokeReflect(breakit_method_tc3, new TestClass3ext()),
+      new InvokeReflect(breakit_method_tc3ext, new TestClass3ext()),
+
+      new InvokeDirect("((Breakable)new TestClass3ext()).breakit()",
+                  () -> ((Breakable)new TestClass3ext()).breakit()),
+      new InvokeDirect("((TestClass3)new TestClass3ext()).breakit()",
+                  () -> ((TestClass3)new TestClass3ext()).breakit()),
+      new InvokeDirect("new TestClass3ext().breakit())",
+                  () -> new TestClass3ext().breakit()),
+    };
+    Breakpoint.Manager.BP[] tc3ext_breakpoints = new Breakpoint.Manager.BP[] {
+      BP(breakit_method), BP(breakit_method_tc3), BP(breakit_method_tc3ext)
+    };
+    runTestGroups("TestClass3ext invokes", tc3ext_invokes, tc3ext_breakpoints);
+
+    // private instance method.
+    Runnable[] private_instance_invokes = new Runnable[] {
+      new InvokeNative(private_method, new TestClass4()),
+
+      new InvokeDirect("new TestClass4().callPrivateMethod()",
+                  () -> new TestClass4().callPrivateMethod()),
+    };
+    Breakpoint.Manager.BP[] private_instance_breakpoints = new Breakpoint.Manager.BP[] {
+      BP(private_method)
+    };
+    runTestGroups(
+        "private instance invoke", private_instance_invokes, private_instance_breakpoints);
+  }
+
+  private static void runTestGroups(String name,
+                                    Runnable[] invokes,
+                                    Breakpoint.Manager.BP[] breakpoints) throws Exception {
+    System.out.println("Running " + name);
+    for (List<Breakpoint.Manager.BP> bps : allCombinations(Arrays.asList(breakpoints))) {
+      System.out.println("\tBreaking on " + bps);
+      for (Runnable test : invokes) {
+        MANAGER.clearAllBreakpoints();
+        MANAGER.setBreakpoints(bps.toArray(new Breakpoint.Manager.BP[0]));
+        test.run();
+      }
+    }
+  }
+}
diff --git a/test/994-breakpoint-line/expected.txt b/test/994-breakpoint-line/expected.txt
new file mode 100644
index 0000000..5899659
--- /dev/null
+++ b/test/994-breakpoint-line/expected.txt
@@ -0,0 +1,34 @@
+Breaking on line: 29 calling with arg: true
+	Breakpoint reached: public static void art.Test994.doMultiPath(boolean) @ line=29
+	argument was true
+Breaking on line: 29 calling with arg: false
+	Breakpoint reached: public static void art.Test994.doMultiPath(boolean) @ line=29
+	argument was false
+Breaking on line: 30 calling with arg: true
+	Breakpoint reached: public static void art.Test994.doMultiPath(boolean) @ line=30
+	argument was true
+Breaking on line: 30 calling with arg: false
+	Breakpoint reached: public static void art.Test994.doMultiPath(boolean) @ line=30
+	argument was false
+Breaking on line: 31 calling with arg: true
+	Breakpoint reached: public static void art.Test994.doMultiPath(boolean) @ line=31
+	argument was true
+Breaking on line: 31 calling with arg: false
+	argument was false
+Breaking on line: 33 calling with arg: true
+	argument was true
+Breaking on line: 33 calling with arg: false
+	Breakpoint reached: public static void art.Test994.doMultiPath(boolean) @ line=33
+	argument was false
+Breaking on line: 35 calling with arg: true
+	argument was true
+	Breakpoint reached: public static void art.Test994.doMultiPath(boolean) @ line=35
+Breaking on line: 35 calling with arg: false
+	argument was false
+	Breakpoint reached: public static void art.Test994.doMultiPath(boolean) @ line=35
+Breaking on line: 36 calling with arg: true
+	argument was true
+	Breakpoint reached: public static void art.Test994.doMultiPath(boolean) @ line=36
+Breaking on line: 36 calling with arg: false
+	argument was false
+	Breakpoint reached: public static void art.Test994.doMultiPath(boolean) @ line=36
diff --git a/test/994-breakpoint-line/info.txt b/test/994-breakpoint-line/info.txt
new file mode 100644
index 0000000..210dea0
--- /dev/null
+++ b/test/994-breakpoint-line/info.txt
@@ -0,0 +1,5 @@
+Test basic JVMTI breakpoint functionality.
+
+This test ensures we can place breakpoints on particular lines of a method. It
+sets breakpoints on each line in turn of a function with multiple execution
+paths and then runs the function, receiving the breakpoint events.
diff --git a/test/994-breakpoint-line/run b/test/994-breakpoint-line/run
new file mode 100755
index 0000000..51875a7
--- /dev/null
+++ b/test/994-breakpoint-line/run
@@ -0,0 +1,18 @@
+#!/bin/bash
+#
+# Copyright 2017 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.
+
+# Ask for stack traces to be dumped to a file rather than to stdout.
+./default-run "$@" --jvmti
diff --git a/test/994-breakpoint-line/src/Main.java b/test/994-breakpoint-line/src/Main.java
new file mode 100644
index 0000000..39cfeb3
--- /dev/null
+++ b/test/994-breakpoint-line/src/Main.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+public class Main {
+  public static void main(String[] args) throws Exception {
+    art.Test994.run();
+  }
+}
diff --git a/test/994-breakpoint-line/src/art/Breakpoint.java b/test/994-breakpoint-line/src/art/Breakpoint.java
new file mode 100644
index 0000000..bbb89f7
--- /dev/null
+++ b/test/994-breakpoint-line/src/art/Breakpoint.java
@@ -0,0 +1,202 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+package art;
+
+import java.lang.reflect.Executable;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.Objects;
+
+public class Breakpoint {
+  public static class Manager {
+    public static class BP {
+      public final Executable method;
+      public final long location;
+
+      public BP(Executable method) {
+        this(method, getStartLocation(method));
+      }
+
+      public BP(Executable method, long location) {
+        this.method = method;
+        this.location = location;
+      }
+
+      @Override
+      public boolean equals(Object other) {
+        return (other instanceof BP) &&
+            method.equals(((BP)other).method) &&
+            location == ((BP)other).location;
+      }
+
+      @Override
+      public String toString() {
+        return method.toString() + " @ " + getLine();
+      }
+
+      @Override
+      public int hashCode() {
+        return Objects.hash(method, location);
+      }
+
+      public int getLine() {
+        try {
+          LineNumber[] lines = getLineNumberTable(method);
+          int best = -1;
+          for (LineNumber l : lines) {
+            if (l.location > location) {
+              break;
+            } else {
+              best = l.line;
+            }
+          }
+          return best;
+        } catch (Exception e) {
+          return -1;
+        }
+      }
+    }
+
+    private Set<BP> breaks = new HashSet<>();
+
+    public void setBreakpoints(BP... bs) {
+      for (BP b : bs) {
+        if (breaks.add(b)) {
+          Breakpoint.setBreakpoint(b.method, b.location);
+        }
+      }
+    }
+    public void setBreakpoint(Executable method, long location) {
+      setBreakpoints(new BP(method, location));
+    }
+
+    public void clearBreakpoints(BP... bs) {
+      for (BP b : bs) {
+        if (breaks.remove(b)) {
+          Breakpoint.clearBreakpoint(b.method, b.location);
+        }
+      }
+    }
+    public void clearBreakpoint(Executable method, long location) {
+      clearBreakpoints(new BP(method, location));
+    }
+
+    public void clearAllBreakpoints() {
+      clearBreakpoints(breaks.toArray(new BP[0]));
+    }
+  }
+
+  public static void startBreakpointWatch(Class<?> methodClass,
+                                          Executable breakpointReached,
+                                          Thread thr) {
+    startBreakpointWatch(methodClass, breakpointReached, false, thr);
+  }
+
+  /**
+   * Enables the trapping of breakpoint events.
+   *
+   * If allowRecursive == true then breakpoints will be sent even if one is currently being handled.
+   */
+  public static native void startBreakpointWatch(Class<?> methodClass,
+                                                 Executable breakpointReached,
+                                                 boolean allowRecursive,
+                                                 Thread thr);
+  public static native void stopBreakpointWatch(Thread thr);
+
+  public static final class LineNumber implements Comparable<LineNumber> {
+    public final long location;
+    public final int line;
+
+    private LineNumber(long loc, int line) {
+      this.location = loc;
+      this.line = line;
+    }
+
+    public boolean equals(Object other) {
+      return other instanceof LineNumber && ((LineNumber)other).line == line &&
+          ((LineNumber)other).location == location;
+    }
+
+    public int compareTo(LineNumber other) {
+      int v = Integer.valueOf(line).compareTo(Integer.valueOf(other.line));
+      if (v != 0) {
+        return v;
+      } else {
+        return Long.valueOf(location).compareTo(Long.valueOf(other.location));
+      }
+    }
+  }
+
+  public static native void setBreakpoint(Executable m, long loc);
+  public static void setBreakpoint(Executable m, LineNumber l) {
+    setBreakpoint(m, l.location);
+  }
+
+  public static native void clearBreakpoint(Executable m, long loc);
+  public static void clearBreakpoint(Executable m, LineNumber l) {
+    clearBreakpoint(m, l.location);
+  }
+
+  private static native Object[] getLineNumberTableNative(Executable m);
+  public static LineNumber[] getLineNumberTable(Executable m) {
+    Object[] nativeTable = getLineNumberTableNative(m);
+    long[] location = (long[])(nativeTable[0]);
+    int[] lines = (int[])(nativeTable[1]);
+    if (lines.length != location.length) {
+      throw new Error("Lines and locations have different lengths!");
+    }
+    LineNumber[] out = new LineNumber[lines.length];
+    for (int i = 0; i < lines.length; i++) {
+      out[i] = new LineNumber(location[i], lines[i]);
+    }
+    return out;
+  }
+
+  public static native long getStartLocation(Executable m);
+
+  public static int locationToLine(Executable m, long location) {
+    try {
+      Breakpoint.LineNumber[] lines = Breakpoint.getLineNumberTable(m);
+      int best = -1;
+      for (Breakpoint.LineNumber l : lines) {
+        if (l.location > location) {
+          break;
+        } else {
+          best = l.line;
+        }
+      }
+      return best;
+    } catch (Exception e) {
+      return -1;
+    }
+  }
+
+  public static long lineToLocation(Executable m, int line) throws Exception {
+    try {
+      Breakpoint.LineNumber[] lines = Breakpoint.getLineNumberTable(m);
+      for (Breakpoint.LineNumber l : lines) {
+        if (l.line == line) {
+          return l.location;
+        }
+      }
+      throw new Exception("Unable to find line " + line + " in " + m);
+    } catch (Exception e) {
+      throw new Exception("Unable to get line number info for " + m, e);
+    }
+  }
+}
+
diff --git a/test/994-breakpoint-line/src/art/Test994.java b/test/994-breakpoint-line/src/art/Test994.java
new file mode 100644
index 0000000..6a1c354
--- /dev/null
+++ b/test/994-breakpoint-line/src/art/Test994.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+package art;
+
+import java.util.Arrays;
+import java.lang.reflect.Executable;
+import java.lang.reflect.Method;
+
+public class Test994 {
+  public static final Breakpoint.Manager MANAGER = new Breakpoint.Manager();
+  public static void doNothing() {}
+
+  // Method with multiple paths we can break on.
+  public static void doMultiPath(boolean bit) {
+    doNothing();
+    if (bit) {
+      System.out.println("\targument was true");
+    } else {
+      System.out.println("\targument was false");
+    }
+    doNothing();
+  }
+
+  public static void notifyBreakpointReached(Thread thr, Executable e, long loc) {
+    System.out.println(
+        "\tBreakpoint reached: " + e + " @ line=" + Breakpoint.locationToLine(e, loc));
+  }
+
+  public static void run() throws Exception {
+    // Set up breakpoints
+    Breakpoint.stopBreakpointWatch(Thread.currentThread());
+    Breakpoint.startBreakpointWatch(
+        Test994.class,
+        Test994.class.getDeclaredMethod(
+            "notifyBreakpointReached", Thread.class, Executable.class, Long.TYPE),
+        Thread.currentThread());
+
+    Method multipath_method = Test994.class.getDeclaredMethod("doMultiPath", Boolean.TYPE);
+
+    Breakpoint.LineNumber[] lines = Breakpoint.getLineNumberTable(multipath_method);
+
+    // Make sure everything is in the same order.
+    Arrays.sort(lines);
+
+    boolean[] values = new boolean[] { true, false };
+
+    for (Breakpoint.LineNumber line : lines) {
+      MANAGER.clearAllBreakpoints();
+      MANAGER.setBreakpoint(multipath_method, line.location);
+      for (boolean arg : values) {
+        System.out.println("Breaking on line: " + line.line + " calling with arg: " + arg);
+        doMultiPath(arg);
+      }
+    }
+
+    Breakpoint.stopBreakpointWatch(Thread.currentThread());
+  }
+}
diff --git a/test/995-breakpoints-throw/expected.txt b/test/995-breakpoints-throw/expected.txt
new file mode 100644
index 0000000..a565b7c
--- /dev/null
+++ b/test/995-breakpoints-throw/expected.txt
@@ -0,0 +1,34 @@
+Test "call Test995::breakpoint": Running breakpoint with handler "do nothing"
+	Breakpoint: public static void art.Test995.breakpoint() @ line=34
+Test "call Test995::breakpoint": No error caught with handler "do nothing"
+Test "call Test995::breakpoint": Finished running with handler "do nothing"
+Test "call Test995::breakpointCatch": Running breakpoint with handler "do nothing"
+	Breakpoint: public static void art.Test995.breakpointCatch() @ line=48
+Test "call Test995::breakpointCatch": No error caught with handler "do nothing"
+Test "call Test995::breakpointCatch": Finished running with handler "do nothing"
+Test "call Test995::breakpointCatchLate": Running breakpoint with handler "do nothing"
+	Breakpoint: public static void art.Test995.breakpointCatchLate() @ line=38
+Test "call Test995::breakpointCatchLate": No error caught with handler "do nothing"
+Test "call Test995::breakpointCatchLate": Finished running with handler "do nothing"
+Test "catch subroutine Test995::breakpoint": Running breakpoint with handler "do nothing"
+	Breakpoint: public static void art.Test995.breakpoint() @ line=34
+Test "catch subroutine Test995::breakpoint": No error caught with handler "do nothing"
+Test "catch subroutine Test995::breakpoint": Finished running with handler "do nothing"
+Test "call Test995::breakpoint": Running breakpoint with handler "throw"
+	Breakpoint: public static void art.Test995.breakpoint() @ line=34
+Test "call Test995::breakpoint": Caught error java.lang.Error:"throwing error!" with handler "throw"
+Test "call Test995::breakpoint": Finished running with handler "throw"
+Test "call Test995::breakpointCatch": Running breakpoint with handler "throw"
+	Breakpoint: public static void art.Test995.breakpointCatch() @ line=48
+Caught java.lang.Error: "throwing error!"
+Test "call Test995::breakpointCatch": No error caught with handler "throw"
+Test "call Test995::breakpointCatch": Finished running with handler "throw"
+Test "call Test995::breakpointCatchLate": Running breakpoint with handler "throw"
+	Breakpoint: public static void art.Test995.breakpointCatchLate() @ line=38
+Test "call Test995::breakpointCatchLate": Caught error java.lang.Error:"throwing error!" with handler "throw"
+Test "call Test995::breakpointCatchLate": Finished running with handler "throw"
+Test "catch subroutine Test995::breakpoint": Running breakpoint with handler "throw"
+	Breakpoint: public static void art.Test995.breakpoint() @ line=34
+Caught java.lang.Error:"throwing error!"
+Test "catch subroutine Test995::breakpoint": No error caught with handler "throw"
+Test "catch subroutine Test995::breakpoint": Finished running with handler "throw"
diff --git a/test/995-breakpoints-throw/info.txt b/test/995-breakpoints-throw/info.txt
new file mode 100644
index 0000000..80f9cf9
--- /dev/null
+++ b/test/995-breakpoints-throw/info.txt
@@ -0,0 +1,6 @@
+Test basic JVMTI breakpoint functionality.
+
+Tests that it is possible to throw exceptions while handling breakpoint events
+and that they are handled appropriately. This includes checking that it is
+possible for the method being breakpointed to catch exceptions thrown by the
+handler.
diff --git a/test/995-breakpoints-throw/run b/test/995-breakpoints-throw/run
new file mode 100755
index 0000000..51875a7
--- /dev/null
+++ b/test/995-breakpoints-throw/run
@@ -0,0 +1,18 @@
+#!/bin/bash
+#
+# Copyright 2017 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.
+
+# Ask for stack traces to be dumped to a file rather than to stdout.
+./default-run "$@" --jvmti
diff --git a/test/995-breakpoints-throw/src/Main.java b/test/995-breakpoints-throw/src/Main.java
new file mode 100644
index 0000000..6f80b43
--- /dev/null
+++ b/test/995-breakpoints-throw/src/Main.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+public class Main {
+  public static void main(String[] args) throws Exception {
+    art.Test995.run();
+  }
+}
diff --git a/test/995-breakpoints-throw/src/art/Breakpoint.java b/test/995-breakpoints-throw/src/art/Breakpoint.java
new file mode 100644
index 0000000..bbb89f7
--- /dev/null
+++ b/test/995-breakpoints-throw/src/art/Breakpoint.java
@@ -0,0 +1,202 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+package art;
+
+import java.lang.reflect.Executable;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.Objects;
+
+public class Breakpoint {
+  public static class Manager {
+    public static class BP {
+      public final Executable method;
+      public final long location;
+
+      public BP(Executable method) {
+        this(method, getStartLocation(method));
+      }
+
+      public BP(Executable method, long location) {
+        this.method = method;
+        this.location = location;
+      }
+
+      @Override
+      public boolean equals(Object other) {
+        return (other instanceof BP) &&
+            method.equals(((BP)other).method) &&
+            location == ((BP)other).location;
+      }
+
+      @Override
+      public String toString() {
+        return method.toString() + " @ " + getLine();
+      }
+
+      @Override
+      public int hashCode() {
+        return Objects.hash(method, location);
+      }
+
+      public int getLine() {
+        try {
+          LineNumber[] lines = getLineNumberTable(method);
+          int best = -1;
+          for (LineNumber l : lines) {
+            if (l.location > location) {
+              break;
+            } else {
+              best = l.line;
+            }
+          }
+          return best;
+        } catch (Exception e) {
+          return -1;
+        }
+      }
+    }
+
+    private Set<BP> breaks = new HashSet<>();
+
+    public void setBreakpoints(BP... bs) {
+      for (BP b : bs) {
+        if (breaks.add(b)) {
+          Breakpoint.setBreakpoint(b.method, b.location);
+        }
+      }
+    }
+    public void setBreakpoint(Executable method, long location) {
+      setBreakpoints(new BP(method, location));
+    }
+
+    public void clearBreakpoints(BP... bs) {
+      for (BP b : bs) {
+        if (breaks.remove(b)) {
+          Breakpoint.clearBreakpoint(b.method, b.location);
+        }
+      }
+    }
+    public void clearBreakpoint(Executable method, long location) {
+      clearBreakpoints(new BP(method, location));
+    }
+
+    public void clearAllBreakpoints() {
+      clearBreakpoints(breaks.toArray(new BP[0]));
+    }
+  }
+
+  public static void startBreakpointWatch(Class<?> methodClass,
+                                          Executable breakpointReached,
+                                          Thread thr) {
+    startBreakpointWatch(methodClass, breakpointReached, false, thr);
+  }
+
+  /**
+   * Enables the trapping of breakpoint events.
+   *
+   * If allowRecursive == true then breakpoints will be sent even if one is currently being handled.
+   */
+  public static native void startBreakpointWatch(Class<?> methodClass,
+                                                 Executable breakpointReached,
+                                                 boolean allowRecursive,
+                                                 Thread thr);
+  public static native void stopBreakpointWatch(Thread thr);
+
+  public static final class LineNumber implements Comparable<LineNumber> {
+    public final long location;
+    public final int line;
+
+    private LineNumber(long loc, int line) {
+      this.location = loc;
+      this.line = line;
+    }
+
+    public boolean equals(Object other) {
+      return other instanceof LineNumber && ((LineNumber)other).line == line &&
+          ((LineNumber)other).location == location;
+    }
+
+    public int compareTo(LineNumber other) {
+      int v = Integer.valueOf(line).compareTo(Integer.valueOf(other.line));
+      if (v != 0) {
+        return v;
+      } else {
+        return Long.valueOf(location).compareTo(Long.valueOf(other.location));
+      }
+    }
+  }
+
+  public static native void setBreakpoint(Executable m, long loc);
+  public static void setBreakpoint(Executable m, LineNumber l) {
+    setBreakpoint(m, l.location);
+  }
+
+  public static native void clearBreakpoint(Executable m, long loc);
+  public static void clearBreakpoint(Executable m, LineNumber l) {
+    clearBreakpoint(m, l.location);
+  }
+
+  private static native Object[] getLineNumberTableNative(Executable m);
+  public static LineNumber[] getLineNumberTable(Executable m) {
+    Object[] nativeTable = getLineNumberTableNative(m);
+    long[] location = (long[])(nativeTable[0]);
+    int[] lines = (int[])(nativeTable[1]);
+    if (lines.length != location.length) {
+      throw new Error("Lines and locations have different lengths!");
+    }
+    LineNumber[] out = new LineNumber[lines.length];
+    for (int i = 0; i < lines.length; i++) {
+      out[i] = new LineNumber(location[i], lines[i]);
+    }
+    return out;
+  }
+
+  public static native long getStartLocation(Executable m);
+
+  public static int locationToLine(Executable m, long location) {
+    try {
+      Breakpoint.LineNumber[] lines = Breakpoint.getLineNumberTable(m);
+      int best = -1;
+      for (Breakpoint.LineNumber l : lines) {
+        if (l.location > location) {
+          break;
+        } else {
+          best = l.line;
+        }
+      }
+      return best;
+    } catch (Exception e) {
+      return -1;
+    }
+  }
+
+  public static long lineToLocation(Executable m, int line) throws Exception {
+    try {
+      Breakpoint.LineNumber[] lines = Breakpoint.getLineNumberTable(m);
+      for (Breakpoint.LineNumber l : lines) {
+        if (l.line == line) {
+          return l.location;
+        }
+      }
+      throw new Exception("Unable to find line " + line + " in " + m);
+    } catch (Exception e) {
+      throw new Exception("Unable to get line number info for " + m, e);
+    }
+  }
+}
+
diff --git a/test/995-breakpoints-throw/src/art/Test995.java b/test/995-breakpoints-throw/src/art/Test995.java
new file mode 100644
index 0000000..a4023fb
--- /dev/null
+++ b/test/995-breakpoints-throw/src/art/Test995.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+package art;
+
+import java.util.Arrays;
+import java.lang.reflect.Executable;
+import java.lang.reflect.Method;
+
+public class Test995 {
+  public static final Breakpoint.Manager MANAGER = new Breakpoint.Manager();
+  public static BreakpointHandler HANDLER = null;
+
+  public static void doNothing() { }
+
+  public static interface BreakpointHandler {
+    public void breakpointReached(Executable e, long loc);
+  }
+
+  public static void breakpoint() {
+    return;
+  }
+
+  public static void breakpointCatchLate() {
+    doNothing();
+    try {
+      doNothing();
+    } catch (Throwable t) {
+      System.out.println("Caught " + t.getClass().getName() + ": \"" + t.getMessage() + "\"");
+    }
+  }
+
+  public static void breakpointCatch() {
+    try {
+      doNothing();
+    } catch (Throwable t) {
+      System.out.println("Caught " + t.getClass().getName() + ": \"" + t.getMessage() + "\"");
+    }
+  }
+
+  public static void notifyBreakpointReached(Thread thr, Executable e, long loc) {
+    System.out.println("\tBreakpoint: " + e + " @ line=" + Breakpoint.locationToLine(e, loc));
+    HANDLER.breakpointReached(e, loc);
+  }
+
+
+  public static BreakpointHandler makeHandler(String name, BreakpointHandler h) {
+    return new BreakpointHandler() {
+      public String toString() {
+        return name;
+      }
+      public void breakpointReached(Executable e, long loc) {
+        h.breakpointReached(e, loc);
+      }
+    };
+  }
+
+  public static Runnable makeTest(String name, Runnable test) {
+    return new Runnable() {
+      public String toString() { return name; }
+      public void run() { test.run(); }
+    };
+  }
+
+  public static void run() throws Exception {
+    // Set up breakpoints
+    Breakpoint.stopBreakpointWatch(Thread.currentThread());
+    Breakpoint.startBreakpointWatch(
+        Test995.class,
+        Test995.class.getDeclaredMethod(
+            "notifyBreakpointReached", Thread.class, Executable.class, Long.TYPE),
+        Thread.currentThread());
+
+    Method breakpoint_method = Test995.class.getDeclaredMethod("breakpoint");
+    Method breakpoint_catch_method = Test995.class.getDeclaredMethod("breakpointCatch");
+    Method breakpoint_catch_late_method = Test995.class.getDeclaredMethod("breakpointCatchLate");
+    MANAGER.setBreakpoint(breakpoint_method, Breakpoint.getStartLocation(breakpoint_method));
+    MANAGER.setBreakpoint(
+        breakpoint_catch_method, Breakpoint.getStartLocation(breakpoint_catch_method));
+    MANAGER.setBreakpoint(
+        breakpoint_catch_late_method, Breakpoint.getStartLocation(breakpoint_catch_late_method));
+
+    BreakpointHandler[] handlers = new BreakpointHandler[] {
+      makeHandler("do nothing", (e, l) -> {}),
+      makeHandler("throw", (e, l) -> { throw new Error("throwing error!"); }),
+    };
+
+    Runnable[] tests = new Runnable[] {
+      makeTest("call Test995::breakpoint", Test995::breakpoint),
+      makeTest("call Test995::breakpointCatch", Test995::breakpointCatch),
+      makeTest("call Test995::breakpointCatchLate", Test995::breakpointCatchLate),
+      makeTest("catch subroutine Test995::breakpoint",
+          () -> {
+            try {
+              breakpoint();
+            } catch (Throwable t) {
+              System.out.printf("Caught %s:\"%s\"\n", t.getClass().getName(), t.getMessage());
+            }
+          }),
+    };
+
+    for (BreakpointHandler handler : handlers) {
+      for (Runnable test : tests) {
+        try {
+          HANDLER = handler;
+          System.out.printf("Test \"%s\": Running breakpoint with handler \"%s\"\n",
+              test, handler);
+          test.run();
+          System.out.printf("Test \"%s\": No error caught with handler \"%s\"\n",
+              test, handler);
+        } catch (Throwable e) {
+          System.out.printf("Test \"%s\": Caught error %s:\"%s\" with handler \"%s\"\n",
+              test, e.getClass().getName(), e.getMessage(), handler);
+        }
+        System.out.printf("Test \"%s\": Finished running with handler \"%s\"\n", test, handler);
+        HANDLER = null;
+      }
+    }
+
+    MANAGER.clearAllBreakpoints();
+    Breakpoint.stopBreakpointWatch(Thread.currentThread());
+  }
+}
diff --git a/test/996-breakpoint-obsolete/expected.txt b/test/996-breakpoint-obsolete/expected.txt
new file mode 100644
index 0000000..e0d419e
--- /dev/null
+++ b/test/996-breakpoint-obsolete/expected.txt
@@ -0,0 +1,14 @@
+Initially setting breakpoint to line 42
+Running transform without redefinition.
+Should be after first breakpoint.
+Breakpoint reached: public void art.Test996$Transform.run(java.lang.Runnable) @ line=42
+Running transform with redefinition.
+Redefining calling function!
+Setting breakpoint on now obsolete method to line 40
+Breakpoint reached: public void art.Test996$Transform.run(java.lang.Runnable) @ line=40
+Should be after first breakpoint.
+Running transform post redefinition. Should not hit any breakpoints.
+Doing nothing transformed
+Setting initial breakpoint on redefined method.
+Doing nothing transformed
+Breakpoint reached: public void art.Test996$Transform.run(java.lang.Runnable) @ line=8
diff --git a/test/996-breakpoint-obsolete/info.txt b/test/996-breakpoint-obsolete/info.txt
new file mode 100644
index 0000000..58536ac
--- /dev/null
+++ b/test/996-breakpoint-obsolete/info.txt
@@ -0,0 +1,4 @@
+Test JVMTI breakpoint/obsolete method interaction.
+
+This checks that redefining a class will clear breakpoints on the class's
+methods and that it is possible to set breakpoints on obsolete methods.
diff --git a/test/996-breakpoint-obsolete/obsolete_breakpoints.cc b/test/996-breakpoint-obsolete/obsolete_breakpoints.cc
new file mode 100644
index 0000000..b6a67e4
--- /dev/null
+++ b/test/996-breakpoint-obsolete/obsolete_breakpoints.cc
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2013 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 <inttypes.h>
+#include <memory>
+#include <stdio.h>
+
+#include "android-base/logging.h"
+#include "android-base/stringprintf.h"
+
+#include "jni.h"
+#include "jvmti.h"
+#include "scoped_local_ref.h"
+
+// Test infrastructure
+#include "jni_binder.h"
+#include "jni_helper.h"
+#include "jvmti_helper.h"
+#include "test_env.h"
+#include "ti_macros.h"
+
+namespace art {
+namespace Test996ObsoleteBreakpoints {
+
+static constexpr jint kNumFrames = 10;
+
+static jmethodID GetFirstObsoleteMethod(JNIEnv* env, jvmtiEnv* jvmti_env) {
+  jint frame_count;
+  jvmtiFrameInfo frames[kNumFrames];
+  if (JvmtiErrorToException(env, jvmti_env,
+                            jvmti_env->GetStackTrace(nullptr,  // current thread
+                                                     0,
+                                                     kNumFrames,
+                                                     frames,
+                                                     &frame_count))) {
+    return nullptr;
+  }
+  for (jint i = 0; i < frame_count; i++) {
+    jboolean is_obsolete = false;
+    if (JvmtiErrorToException(env, jvmti_env,
+                              jvmti_env->IsMethodObsolete(frames[i].method, &is_obsolete))) {
+      return nullptr;
+    }
+    if (is_obsolete) {
+      return frames[i].method;
+    }
+  }
+  ScopedLocalRef<jclass> rt_exception(env, env->FindClass("java/lang/RuntimeException"));
+  env->ThrowNew(rt_exception.get(), "Unable to find obsolete method!");
+  return nullptr;
+}
+
+extern "C" JNIEXPORT void JNICALL Java_art_Test996_setBreakpointOnObsoleteMethod(
+    JNIEnv* env, jclass k ATTRIBUTE_UNUSED, jlong loc) {
+  jmethodID method = GetFirstObsoleteMethod(env, jvmti_env);
+  if (method == nullptr) {
+    return;
+  }
+  JvmtiErrorToException(env, jvmti_env, jvmti_env->SetBreakpoint(method, loc));
+}
+
+}  // namespace Test996ObsoleteBreakpoints
+}  // namespace art
diff --git a/test/996-breakpoint-obsolete/run b/test/996-breakpoint-obsolete/run
new file mode 100755
index 0000000..51875a7
--- /dev/null
+++ b/test/996-breakpoint-obsolete/run
@@ -0,0 +1,18 @@
+#!/bin/bash
+#
+# Copyright 2017 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.
+
+# Ask for stack traces to be dumped to a file rather than to stdout.
+./default-run "$@" --jvmti
diff --git a/test/996-breakpoint-obsolete/src/Main.java b/test/996-breakpoint-obsolete/src/Main.java
new file mode 100644
index 0000000..1b9b0a9
--- /dev/null
+++ b/test/996-breakpoint-obsolete/src/Main.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+public class Main {
+  public static void main(String[] args) throws Exception {
+    art.Test996.run();
+  }
+}
diff --git a/test/996-breakpoint-obsolete/src/art/Breakpoint.java b/test/996-breakpoint-obsolete/src/art/Breakpoint.java
new file mode 100644
index 0000000..bbb89f7
--- /dev/null
+++ b/test/996-breakpoint-obsolete/src/art/Breakpoint.java
@@ -0,0 +1,202 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+package art;
+
+import java.lang.reflect.Executable;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.Objects;
+
+public class Breakpoint {
+  public static class Manager {
+    public static class BP {
+      public final Executable method;
+      public final long location;
+
+      public BP(Executable method) {
+        this(method, getStartLocation(method));
+      }
+
+      public BP(Executable method, long location) {
+        this.method = method;
+        this.location = location;
+      }
+
+      @Override
+      public boolean equals(Object other) {
+        return (other instanceof BP) &&
+            method.equals(((BP)other).method) &&
+            location == ((BP)other).location;
+      }
+
+      @Override
+      public String toString() {
+        return method.toString() + " @ " + getLine();
+      }
+
+      @Override
+      public int hashCode() {
+        return Objects.hash(method, location);
+      }
+
+      public int getLine() {
+        try {
+          LineNumber[] lines = getLineNumberTable(method);
+          int best = -1;
+          for (LineNumber l : lines) {
+            if (l.location > location) {
+              break;
+            } else {
+              best = l.line;
+            }
+          }
+          return best;
+        } catch (Exception e) {
+          return -1;
+        }
+      }
+    }
+
+    private Set<BP> breaks = new HashSet<>();
+
+    public void setBreakpoints(BP... bs) {
+      for (BP b : bs) {
+        if (breaks.add(b)) {
+          Breakpoint.setBreakpoint(b.method, b.location);
+        }
+      }
+    }
+    public void setBreakpoint(Executable method, long location) {
+      setBreakpoints(new BP(method, location));
+    }
+
+    public void clearBreakpoints(BP... bs) {
+      for (BP b : bs) {
+        if (breaks.remove(b)) {
+          Breakpoint.clearBreakpoint(b.method, b.location);
+        }
+      }
+    }
+    public void clearBreakpoint(Executable method, long location) {
+      clearBreakpoints(new BP(method, location));
+    }
+
+    public void clearAllBreakpoints() {
+      clearBreakpoints(breaks.toArray(new BP[0]));
+    }
+  }
+
+  public static void startBreakpointWatch(Class<?> methodClass,
+                                          Executable breakpointReached,
+                                          Thread thr) {
+    startBreakpointWatch(methodClass, breakpointReached, false, thr);
+  }
+
+  /**
+   * Enables the trapping of breakpoint events.
+   *
+   * If allowRecursive == true then breakpoints will be sent even if one is currently being handled.
+   */
+  public static native void startBreakpointWatch(Class<?> methodClass,
+                                                 Executable breakpointReached,
+                                                 boolean allowRecursive,
+                                                 Thread thr);
+  public static native void stopBreakpointWatch(Thread thr);
+
+  public static final class LineNumber implements Comparable<LineNumber> {
+    public final long location;
+    public final int line;
+
+    private LineNumber(long loc, int line) {
+      this.location = loc;
+      this.line = line;
+    }
+
+    public boolean equals(Object other) {
+      return other instanceof LineNumber && ((LineNumber)other).line == line &&
+          ((LineNumber)other).location == location;
+    }
+
+    public int compareTo(LineNumber other) {
+      int v = Integer.valueOf(line).compareTo(Integer.valueOf(other.line));
+      if (v != 0) {
+        return v;
+      } else {
+        return Long.valueOf(location).compareTo(Long.valueOf(other.location));
+      }
+    }
+  }
+
+  public static native void setBreakpoint(Executable m, long loc);
+  public static void setBreakpoint(Executable m, LineNumber l) {
+    setBreakpoint(m, l.location);
+  }
+
+  public static native void clearBreakpoint(Executable m, long loc);
+  public static void clearBreakpoint(Executable m, LineNumber l) {
+    clearBreakpoint(m, l.location);
+  }
+
+  private static native Object[] getLineNumberTableNative(Executable m);
+  public static LineNumber[] getLineNumberTable(Executable m) {
+    Object[] nativeTable = getLineNumberTableNative(m);
+    long[] location = (long[])(nativeTable[0]);
+    int[] lines = (int[])(nativeTable[1]);
+    if (lines.length != location.length) {
+      throw new Error("Lines and locations have different lengths!");
+    }
+    LineNumber[] out = new LineNumber[lines.length];
+    for (int i = 0; i < lines.length; i++) {
+      out[i] = new LineNumber(location[i], lines[i]);
+    }
+    return out;
+  }
+
+  public static native long getStartLocation(Executable m);
+
+  public static int locationToLine(Executable m, long location) {
+    try {
+      Breakpoint.LineNumber[] lines = Breakpoint.getLineNumberTable(m);
+      int best = -1;
+      for (Breakpoint.LineNumber l : lines) {
+        if (l.location > location) {
+          break;
+        } else {
+          best = l.line;
+        }
+      }
+      return best;
+    } catch (Exception e) {
+      return -1;
+    }
+  }
+
+  public static long lineToLocation(Executable m, int line) throws Exception {
+    try {
+      Breakpoint.LineNumber[] lines = Breakpoint.getLineNumberTable(m);
+      for (Breakpoint.LineNumber l : lines) {
+        if (l.line == line) {
+          return l.location;
+        }
+      }
+      throw new Exception("Unable to find line " + line + " in " + m);
+    } catch (Exception e) {
+      throw new Exception("Unable to get line number info for " + m, e);
+    }
+  }
+}
+
diff --git a/test/996-breakpoint-obsolete/src/art/Redefinition.java b/test/996-breakpoint-obsolete/src/art/Redefinition.java
new file mode 100644
index 0000000..56d2938
--- /dev/null
+++ b/test/996-breakpoint-obsolete/src/art/Redefinition.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+package art;
+
+import java.util.ArrayList;
+// Common Redefinition functions. Placed here for use by CTS
+public class Redefinition {
+  public static final class CommonClassDefinition {
+    public final Class<?> target;
+    public final byte[] class_file_bytes;
+    public final byte[] dex_file_bytes;
+
+    public CommonClassDefinition(Class<?> target, byte[] class_file_bytes, byte[] dex_file_bytes) {
+      this.target = target;
+      this.class_file_bytes = class_file_bytes;
+      this.dex_file_bytes = dex_file_bytes;
+    }
+  }
+
+  // A set of possible test configurations. Test should set this if they need to.
+  // This must be kept in sync with the defines in ti-agent/common_helper.cc
+  public static enum Config {
+    COMMON_REDEFINE(0),
+    COMMON_RETRANSFORM(1),
+    COMMON_TRANSFORM(2);
+
+    private final int val;
+    private Config(int val) {
+      this.val = val;
+    }
+  }
+
+  public static void setTestConfiguration(Config type) {
+    nativeSetTestConfiguration(type.val);
+  }
+
+  private static native void nativeSetTestConfiguration(int type);
+
+  // Transforms the class
+  public static native void doCommonClassRedefinition(Class<?> target,
+                                                      byte[] classfile,
+                                                      byte[] dexfile);
+
+  public static void doMultiClassRedefinition(CommonClassDefinition... defs) {
+    ArrayList<Class<?>> classes = new ArrayList<>();
+    ArrayList<byte[]> class_files = new ArrayList<>();
+    ArrayList<byte[]> dex_files = new ArrayList<>();
+
+    for (CommonClassDefinition d : defs) {
+      classes.add(d.target);
+      class_files.add(d.class_file_bytes);
+      dex_files.add(d.dex_file_bytes);
+    }
+    doCommonMultiClassRedefinition(classes.toArray(new Class<?>[0]),
+                                   class_files.toArray(new byte[0][]),
+                                   dex_files.toArray(new byte[0][]));
+  }
+
+  public static void addMultiTransformationResults(CommonClassDefinition... defs) {
+    for (CommonClassDefinition d : defs) {
+      addCommonTransformationResult(d.target.getCanonicalName(),
+                                    d.class_file_bytes,
+                                    d.dex_file_bytes);
+    }
+  }
+
+  public static native void doCommonMultiClassRedefinition(Class<?>[] targets,
+                                                           byte[][] classfiles,
+                                                           byte[][] dexfiles);
+  public static native void doCommonClassRetransformation(Class<?>... target);
+  public static native void setPopRetransformations(boolean pop);
+  public static native void popTransformationFor(String name);
+  public static native void enableCommonRetransformation(boolean enable);
+  public static native void addCommonTransformationResult(String target_name,
+                                                          byte[] class_bytes,
+                                                          byte[] dex_bytes);
+}
diff --git a/test/996-breakpoint-obsolete/src/art/Test996.java b/test/996-breakpoint-obsolete/src/art/Test996.java
new file mode 100644
index 0000000..f3166c3
--- /dev/null
+++ b/test/996-breakpoint-obsolete/src/art/Test996.java
@@ -0,0 +1,152 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+package art;
+
+import java.lang.reflect.Executable;
+import java.lang.reflect.Method;
+import java.util.Base64;
+
+public class Test996 {
+  // The line we are going to break on. This should be the println in the Transform class. We set a
+  // breakpoint here after we have redefined the class.
+  public static final int TRANSFORM_BREAKPOINT_REDEFINED_LINE = 40;
+
+  // The line we initially set a breakpoint on. This should be the doNothing call. This should be
+  // cleared by the redefinition and should only be caught on the initial run.
+  public static final int TRANSFORM_BREAKPOINT_INITIAL_LINE = 42;
+
+  // A function that doesn't do anything. Used for giving places to break on in a function.
+  public static void doNothing() {}
+
+  public static final class Transform {
+    public void run(Runnable r) {
+      r.run();
+      // Make sure we don't change anything above this line to keep all the breakpoint stuff
+      // working. We will be putting a breakpoint before this line in the runnable.
+      System.out.println("Should be after first breakpoint.");
+      // This is set as a breakpoint prior to redefinition. It should not be hit.
+      doNothing();
+    }
+  }
+
+  /* ******************************************************************************************** */
+  // Try to keep all edits to this file below the above line. If edits need to be made above this
+  // line be sure to update the TRANSFORM_BREAKPOINT_REDEFINED_LINE and
+  // TRANSFORM_BREAKPOINT_INITIAL_LINE to their appropriate values.
+
+  public static final int TRANSFORM_BREAKPOINT_POST_REDEFINITION_LINE = 8;
+
+  // The base64 encoding of the following class. The redefined 'run' method should have the same
+  // instructions as the original. This means that the locations of each line should stay the same
+  // and the set of valid locations will not change. We use this to ensure that breakpoints are
+  // removed from the redefined method.
+  // public static final class Transform {
+  //   public void run(Runnable r) {
+  //     r.run();
+  //     System.out.println("Doing nothing transformed");
+  //     doNothing();  // try to catch non-removed breakpoints
+  //   }
+  // }
+  private static final byte[] CLASS_BYTES = Base64.getDecoder().decode(
+    "yv66vgAAADQAKAoACAARCwASABMJABQAFQgAFgoAFwAYCgAZABoHABsHAB4BAAY8aW5pdD4BAAMo" +
+    "KVYBAARDb2RlAQAPTGluZU51bWJlclRhYmxlAQADcnVuAQAXKExqYXZhL2xhbmcvUnVubmFibGU7" +
+    "KVYBAApTb3VyY2VGaWxlAQAMVGVzdDk5Ni5qYXZhDAAJAAoHAB8MAA0ACgcAIAwAIQAiAQAZRG9p" +
+    "bmcgbm90aGluZyB0cmFuc2Zvcm1lZAcAIwwAJAAlBwAmDAAnAAoBABVhcnQvVGVzdDk5NiRUcmFu" +
+    "c2Zvcm0BAAlUcmFuc2Zvcm0BAAxJbm5lckNsYXNzZXMBABBqYXZhL2xhbmcvT2JqZWN0AQASamF2" +
+    "YS9sYW5nL1J1bm5hYmxlAQAQamF2YS9sYW5nL1N5c3RlbQEAA291dAEAFUxqYXZhL2lvL1ByaW50" +
+    "U3RyZWFtOwEAE2phdmEvaW8vUHJpbnRTdHJlYW0BAAdwcmludGxuAQAVKExqYXZhL2xhbmcvU3Ry" +
+    "aW5nOylWAQALYXJ0L1Rlc3Q5OTYBAAlkb05vdGhpbmcAMQAHAAgAAAAAAAIAAQAJAAoAAQALAAAA" +
+    "HQABAAEAAAAFKrcAAbEAAAABAAwAAAAGAAEAAAAEAAEADQAOAAEACwAAADYAAgACAAAAEiu5AAIB" +
+    "ALIAAxIEtgAFuAAGsQAAAAEADAAAABIABAAAAAYABgAHAA4ACAARAAkAAgAPAAAAAgAQAB0AAAAK" +
+    "AAEABwAZABwAGQ==");
+  private static final byte[] DEX_BYTES = Base64.getDecoder().decode(
+    "ZGV4CjAzNQBzn3TiKGAiM0fubj25v816W0k+niqj+SQcBAAAcAAAAHhWNBIAAAAAAAAAAFgDAAAW" +
+    "AAAAcAAAAAoAAADIAAAAAwAAAPAAAAABAAAAFAEAAAYAAAAcAQAAAQAAAEwBAACwAgAAbAEAANoB" +
+    "AADiAQAA/QEAABYCAAAlAgAASQIAAGkCAACAAgAAlAIAAKoCAAC+AgAA0gIAAOACAADrAgAA7gIA" +
+    "APICAAD/AgAACgMAABADAAAVAwAAHgMAACMDAAACAAAAAwAAAAQAAAAFAAAABgAAAAcAAAAIAAAA" +
+    "CQAAAAoAAAANAAAADQAAAAkAAAAAAAAADgAAAAkAAADMAQAADgAAAAkAAADUAQAACAAEABIAAAAA" +
+    "AAAAAAAAAAAAAQAUAAAAAQAAABAAAAAEAAIAEwAAAAUAAAAAAAAABgAAABQAAAAAAAAAEQAAAAUA" +
+    "AAAAAAAACwAAALwBAABHAwAAAAAAAAIAAAA4AwAAPgMAAAEAAQABAAAAKgMAAAQAAABwEAQAAAAO" +
+    "AAQAAgACAAAALwMAAA4AAAByEAUAAwBiAAAAGgEBAG4gAwAQAHEAAgAAAA4AbAEAAAAAAAAAAAAA" +
+    "AAAAAAEAAAAGAAAAAQAAAAcABjxpbml0PgAZRG9pbmcgbm90aGluZyB0cmFuc2Zvcm1lZAAXTGFy" +
+    "dC9UZXN0OTk2JFRyYW5zZm9ybTsADUxhcnQvVGVzdDk5NjsAIkxkYWx2aWsvYW5ub3RhdGlvbi9F" +
+    "bmNsb3NpbmdDbGFzczsAHkxkYWx2aWsvYW5ub3RhdGlvbi9Jbm5lckNsYXNzOwAVTGphdmEvaW8v" +
+    "UHJpbnRTdHJlYW07ABJMamF2YS9sYW5nL09iamVjdDsAFExqYXZhL2xhbmcvUnVubmFibGU7ABJM" +
+    "amF2YS9sYW5nL1N0cmluZzsAEkxqYXZhL2xhbmcvU3lzdGVtOwAMVGVzdDk5Ni5qYXZhAAlUcmFu" +
+    "c2Zvcm0AAVYAAlZMAAthY2Nlc3NGbGFncwAJZG9Ob3RoaW5nAARuYW1lAANvdXQAB3ByaW50bG4A" +
+    "A3J1bgAFdmFsdWUABAAHDgAGAQAHDjx4PAACAgEVGAECAwIPBBkRFwwAAAEBAIGABPgCAQGQAwAA" +
+    "ABAAAAAAAAAAAQAAAAAAAAABAAAAFgAAAHAAAAACAAAACgAAAMgAAAADAAAAAwAAAPAAAAAEAAAA" +
+    "AQAAABQBAAAFAAAABgAAABwBAAAGAAAAAQAAAEwBAAADEAAAAQAAAGwBAAABIAAAAgAAAHgBAAAG" +
+    "IAAAAQAAALwBAAABEAAAAgAAAMwBAAACIAAAFgAAANoBAAADIAAAAgAAACoDAAAEIAAAAgAAADgD" +
+    "AAAAIAAAAQAAAEcDAAAAEAAAAQAAAFgDAAA=");
+
+  public static void notifyBreakpointReached(Thread thr, Executable e, long loc) {
+    int line = Breakpoint.locationToLine(e, loc);
+    if (line == -1 && e.getName().equals("run") && e.getDeclaringClass().equals(Transform.class)) {
+      // RI always reports line = -1 for obsolete methods. Just replace it with the real line for
+      // consistency.
+      line = TRANSFORM_BREAKPOINT_REDEFINED_LINE;
+    }
+    System.out.println("Breakpoint reached: " + e + " @ line=" + line);
+  }
+
+  public static void run() throws Exception {
+    // Set up breakpoints
+    Breakpoint.stopBreakpointWatch(Thread.currentThread());
+    Breakpoint.startBreakpointWatch(
+        Test996.class,
+        Test996.class.getDeclaredMethod(
+            "notifyBreakpointReached", Thread.class, Executable.class, Long.TYPE),
+        Thread.currentThread());
+
+    Transform t = new Transform();
+    Method non_obsolete_run_method = Transform.class.getDeclaredMethod("run", Runnable.class);
+    final long obsolete_breakpoint_location =
+        Breakpoint.lineToLocation(non_obsolete_run_method, TRANSFORM_BREAKPOINT_REDEFINED_LINE);
+
+    System.out.println("Initially setting breakpoint to line " + TRANSFORM_BREAKPOINT_INITIAL_LINE);
+    long initial_breakpoint_location =
+        Breakpoint.lineToLocation(non_obsolete_run_method, TRANSFORM_BREAKPOINT_INITIAL_LINE);
+    Breakpoint.setBreakpoint(non_obsolete_run_method, initial_breakpoint_location);
+
+    System.out.println("Running transform without redefinition.");
+    t.run(() -> {});
+
+    System.out.println("Running transform with redefinition.");
+    t.run(() -> {
+      System.out.println("Redefining calling function!");
+      // This should clear the breakpoint set to TRANSFORM_BREAKPOINT_INITIAL_LINE
+      Redefinition.doCommonClassRedefinition(Transform.class, CLASS_BYTES, DEX_BYTES);
+      System.out.println("Setting breakpoint on now obsolete method to line " +
+          TRANSFORM_BREAKPOINT_REDEFINED_LINE);
+      setBreakpointOnObsoleteMethod(obsolete_breakpoint_location);
+    });
+    System.out.println("Running transform post redefinition. Should not hit any breakpoints.");
+    t.run(() -> {});
+
+    System.out.println("Setting initial breakpoint on redefined method.");
+    long final_breakpoint_location =
+        Breakpoint.lineToLocation(non_obsolete_run_method,
+                                  TRANSFORM_BREAKPOINT_POST_REDEFINITION_LINE);
+    Breakpoint.setBreakpoint(non_obsolete_run_method, final_breakpoint_location);
+    t.run(() -> {});
+
+    Breakpoint.stopBreakpointWatch(Thread.currentThread());
+  }
+
+  public static native void setBreakpointOnObsoleteMethod(long location);
+}
diff --git a/test/997-single-step/expected.txt b/test/997-single-step/expected.txt
new file mode 100644
index 0000000..69c554c
--- /dev/null
+++ b/test/997-single-step/expected.txt
@@ -0,0 +1,12 @@
+Stepping through doMultiPath(true)
+Single step: public static void art.Test997.doMultiPath(boolean) @ line=41
+Single step: public static void art.Test997.doMultiPath(boolean) @ line=42
+Single step: public static void art.Test997.doMultiPath(boolean) @ line=43
+Single step: public static void art.Test997.doMultiPath(boolean) @ line=47
+Single step: public static void art.Test997.doMultiPath(boolean) @ line=48
+Stepping through doMultiPath(false)
+Single step: public static void art.Test997.doMultiPath(boolean) @ line=41
+Single step: public static void art.Test997.doMultiPath(boolean) @ line=42
+Single step: public static void art.Test997.doMultiPath(boolean) @ line=45
+Single step: public static void art.Test997.doMultiPath(boolean) @ line=47
+Single step: public static void art.Test997.doMultiPath(boolean) @ line=48
diff --git a/test/997-single-step/info.txt b/test/997-single-step/info.txt
new file mode 100644
index 0000000..e4a584e
--- /dev/null
+++ b/test/997-single-step/info.txt
@@ -0,0 +1,3 @@
+Test basic JVMTI single step functionality.
+
+Ensures that we can receive single step events from JVMTI.
diff --git a/test/997-single-step/run b/test/997-single-step/run
new file mode 100755
index 0000000..51875a7
--- /dev/null
+++ b/test/997-single-step/run
@@ -0,0 +1,18 @@
+#!/bin/bash
+#
+# Copyright 2017 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.
+
+# Ask for stack traces to be dumped to a file rather than to stdout.
+./default-run "$@" --jvmti
diff --git a/test/997-single-step/src/Main.java b/test/997-single-step/src/Main.java
new file mode 100644
index 0000000..1927f04
--- /dev/null
+++ b/test/997-single-step/src/Main.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+public class Main {
+  public static void main(String[] args) throws Exception {
+    art.Test997.run();
+  }
+}
diff --git a/test/997-single-step/src/art/Breakpoint.java b/test/997-single-step/src/art/Breakpoint.java
new file mode 100644
index 0000000..bbb89f7
--- /dev/null
+++ b/test/997-single-step/src/art/Breakpoint.java
@@ -0,0 +1,202 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+package art;
+
+import java.lang.reflect.Executable;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.Objects;
+
+public class Breakpoint {
+  public static class Manager {
+    public static class BP {
+      public final Executable method;
+      public final long location;
+
+      public BP(Executable method) {
+        this(method, getStartLocation(method));
+      }
+
+      public BP(Executable method, long location) {
+        this.method = method;
+        this.location = location;
+      }
+
+      @Override
+      public boolean equals(Object other) {
+        return (other instanceof BP) &&
+            method.equals(((BP)other).method) &&
+            location == ((BP)other).location;
+      }
+
+      @Override
+      public String toString() {
+        return method.toString() + " @ " + getLine();
+      }
+
+      @Override
+      public int hashCode() {
+        return Objects.hash(method, location);
+      }
+
+      public int getLine() {
+        try {
+          LineNumber[] lines = getLineNumberTable(method);
+          int best = -1;
+          for (LineNumber l : lines) {
+            if (l.location > location) {
+              break;
+            } else {
+              best = l.line;
+            }
+          }
+          return best;
+        } catch (Exception e) {
+          return -1;
+        }
+      }
+    }
+
+    private Set<BP> breaks = new HashSet<>();
+
+    public void setBreakpoints(BP... bs) {
+      for (BP b : bs) {
+        if (breaks.add(b)) {
+          Breakpoint.setBreakpoint(b.method, b.location);
+        }
+      }
+    }
+    public void setBreakpoint(Executable method, long location) {
+      setBreakpoints(new BP(method, location));
+    }
+
+    public void clearBreakpoints(BP... bs) {
+      for (BP b : bs) {
+        if (breaks.remove(b)) {
+          Breakpoint.clearBreakpoint(b.method, b.location);
+        }
+      }
+    }
+    public void clearBreakpoint(Executable method, long location) {
+      clearBreakpoints(new BP(method, location));
+    }
+
+    public void clearAllBreakpoints() {
+      clearBreakpoints(breaks.toArray(new BP[0]));
+    }
+  }
+
+  public static void startBreakpointWatch(Class<?> methodClass,
+                                          Executable breakpointReached,
+                                          Thread thr) {
+    startBreakpointWatch(methodClass, breakpointReached, false, thr);
+  }
+
+  /**
+   * Enables the trapping of breakpoint events.
+   *
+   * If allowRecursive == true then breakpoints will be sent even if one is currently being handled.
+   */
+  public static native void startBreakpointWatch(Class<?> methodClass,
+                                                 Executable breakpointReached,
+                                                 boolean allowRecursive,
+                                                 Thread thr);
+  public static native void stopBreakpointWatch(Thread thr);
+
+  public static final class LineNumber implements Comparable<LineNumber> {
+    public final long location;
+    public final int line;
+
+    private LineNumber(long loc, int line) {
+      this.location = loc;
+      this.line = line;
+    }
+
+    public boolean equals(Object other) {
+      return other instanceof LineNumber && ((LineNumber)other).line == line &&
+          ((LineNumber)other).location == location;
+    }
+
+    public int compareTo(LineNumber other) {
+      int v = Integer.valueOf(line).compareTo(Integer.valueOf(other.line));
+      if (v != 0) {
+        return v;
+      } else {
+        return Long.valueOf(location).compareTo(Long.valueOf(other.location));
+      }
+    }
+  }
+
+  public static native void setBreakpoint(Executable m, long loc);
+  public static void setBreakpoint(Executable m, LineNumber l) {
+    setBreakpoint(m, l.location);
+  }
+
+  public static native void clearBreakpoint(Executable m, long loc);
+  public static void clearBreakpoint(Executable m, LineNumber l) {
+    clearBreakpoint(m, l.location);
+  }
+
+  private static native Object[] getLineNumberTableNative(Executable m);
+  public static LineNumber[] getLineNumberTable(Executable m) {
+    Object[] nativeTable = getLineNumberTableNative(m);
+    long[] location = (long[])(nativeTable[0]);
+    int[] lines = (int[])(nativeTable[1]);
+    if (lines.length != location.length) {
+      throw new Error("Lines and locations have different lengths!");
+    }
+    LineNumber[] out = new LineNumber[lines.length];
+    for (int i = 0; i < lines.length; i++) {
+      out[i] = new LineNumber(location[i], lines[i]);
+    }
+    return out;
+  }
+
+  public static native long getStartLocation(Executable m);
+
+  public static int locationToLine(Executable m, long location) {
+    try {
+      Breakpoint.LineNumber[] lines = Breakpoint.getLineNumberTable(m);
+      int best = -1;
+      for (Breakpoint.LineNumber l : lines) {
+        if (l.location > location) {
+          break;
+        } else {
+          best = l.line;
+        }
+      }
+      return best;
+    } catch (Exception e) {
+      return -1;
+    }
+  }
+
+  public static long lineToLocation(Executable m, int line) throws Exception {
+    try {
+      Breakpoint.LineNumber[] lines = Breakpoint.getLineNumberTable(m);
+      for (Breakpoint.LineNumber l : lines) {
+        if (l.line == line) {
+          return l.location;
+        }
+      }
+      throw new Exception("Unable to find line " + line + " in " + m);
+    } catch (Exception e) {
+      throw new Exception("Unable to get line number info for " + m, e);
+    }
+  }
+}
+
diff --git a/test/997-single-step/src/art/Test997.java b/test/997-single-step/src/art/Test997.java
new file mode 100644
index 0000000..a7a522d
--- /dev/null
+++ b/test/997-single-step/src/art/Test997.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+package art;
+
+import java.util.Arrays;
+import java.lang.reflect.Executable;
+import java.lang.reflect.Method;
+
+public class Test997 {
+  static final int NO_LAST_LINE_NUMBER = -1;
+  static int LAST_LINE_NUMBER = NO_LAST_LINE_NUMBER;
+  static Method DO_MULTIPATH_METHOD;
+
+  static {
+    try {
+      DO_MULTIPATH_METHOD = Test997.class.getDeclaredMethod("doMultiPath", Boolean.TYPE);
+    } catch (Exception e) {
+      throw new Error("could not find method doMultiPath", e);
+    }
+  }
+
+  // Function that acts simply to ensure there are multiple lines.
+  public static void doNothing() {}
+
+  // Method with multiple paths we can break on.
+  public static void doMultiPath(boolean bit) {
+    doNothing();
+    if (bit) {
+      doNothing();
+    } else {
+      doNothing();
+    }
+    doNothing();
+  }
+
+  public static void notifySingleStep(Thread thr, Executable e, long loc) {
+    if (!e.equals(DO_MULTIPATH_METHOD)) {
+      // Only report steps in doMultiPath
+      return;
+    }
+    int cur_line = Breakpoint.locationToLine(e, loc);
+    // Only report anything when the line number changes. This is so we can run this test against
+    // both the RI and ART and also to prevent front-end compiler changes from affecting output.
+    if (LAST_LINE_NUMBER == NO_LAST_LINE_NUMBER || LAST_LINE_NUMBER != cur_line) {
+      LAST_LINE_NUMBER = cur_line;
+      System.out.println("Single step: " + e + " @ line=" + cur_line);
+    }
+  }
+
+  public static void resetTest() {
+    LAST_LINE_NUMBER = NO_LAST_LINE_NUMBER;
+  }
+
+  public static void run() throws Exception {
+    boolean[] values = new boolean[] { true, false };
+    Trace.enableSingleStepTracing(Test997.class,
+        Test997.class.getDeclaredMethod(
+            "notifySingleStep", Thread.class, Executable.class, Long.TYPE),
+        Thread.currentThread());
+    for (boolean arg : values) {
+      System.out.println("Stepping through doMultiPath(" + arg + ")");
+      resetTest();
+      doMultiPath(arg);
+    }
+
+    Trace.disableTracing(Thread.currentThread());
+  }
+}
diff --git a/test/997-single-step/src/art/Trace.java b/test/997-single-step/src/art/Trace.java
new file mode 100644
index 0000000..ba3d397
--- /dev/null
+++ b/test/997-single-step/src/art/Trace.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+package art;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+
+public class Trace {
+  public static native void enableTracing(Class<?> methodClass,
+                                          Method entryMethod,
+                                          Method exitMethod,
+                                          Method fieldAccess,
+                                          Method fieldModify,
+                                          Method singleStep,
+                                          Thread thr);
+  public static native void disableTracing(Thread thr);
+
+  public static void enableFieldTracing(Class<?> methodClass,
+                                        Method fieldAccess,
+                                        Method fieldModify,
+                                        Thread thr) {
+    enableTracing(methodClass, null, null, fieldAccess, fieldModify, null, thr);
+  }
+
+  public static void enableMethodTracing(Class<?> methodClass,
+                                         Method entryMethod,
+                                         Method exitMethod,
+                                         Thread thr) {
+    enableTracing(methodClass, entryMethod, exitMethod, null, null, null, thr);
+  }
+
+  public static void enableSingleStepTracing(Class<?> methodClass,
+                                             Method singleStep,
+                                             Thread thr) {
+    enableTracing(methodClass, null, null, null, null, singleStep, thr);
+  }
+
+  public static native void watchFieldAccess(Field f);
+  public static native void watchFieldModification(Field f);
+  public static native void watchAllFieldAccesses();
+  public static native void watchAllFieldModifications();
+}
diff --git a/test/Android.bp b/test/Android.bp
index 23ffc7e..0dff01b 100644
--- a/test/Android.bp
+++ b/test/Android.bp
@@ -281,6 +281,8 @@
         "989-method-trace-throw/method_trace.cc",
         "991-field-trace-2/field_trace.cc",
         "992-source-data/source_file.cc",
+        "993-breakpoints/breakpoints.cc",
+        "996-breakpoint-obsolete/obsolete_breakpoints.cc",
     ],
     shared_libs: [
         "libbase",
diff --git a/test/etc/run-test-jar b/test/etc/run-test-jar
index 7a48bca..ca7db48 100755
--- a/test/etc/run-test-jar
+++ b/test/etc/run-test-jar
@@ -63,6 +63,7 @@
 TEST_IS_NDEBUG="n"
 APP_IMAGE="y"
 JVMTI_STRESS="n"
+JVMTI_STEP_STRESS="n"
 JVMTI_FIELD_STRESS="n"
 JVMTI_TRACE_STRESS="n"
 JVMTI_REDEFINE_STRESS="n"
@@ -163,6 +164,10 @@
         JVMTI_STRESS="y"
         JVMTI_REDEFINE_STRESS="y"
         shift
+    elif [ "x$1" = "x--jvmti-step-stress" ]; then
+        JVMTI_STRESS="y"
+        JVMTI_STEP_STRESS="y"
+        shift
     elif [ "x$1" = "x--jvmti-field-stress" ]; then
         JVMTI_STRESS="y"
         JVMTI_FIELD_STRESS="y"
@@ -426,6 +431,9 @@
   if [[ "$JVMTI_FIELD_STRESS" = "y" ]]; then
     agent_args="${agent_args},field"
   fi
+  if [[ "$JVMTI_STEP_STRESS" = "y" ]]; then
+    agent_args="${agent_args},step"
+  fi
   if [[ "$JVMTI_TRACE_STRESS" = "y" ]]; then
     agent_args="${agent_args},trace"
   fi
diff --git a/test/knownfailures.json b/test/knownfailures.json
index c4a28a1..a8d492b 100644
--- a/test/knownfailures.json
+++ b/test/knownfailures.json
@@ -502,7 +502,7 @@
             "645-checker-abs-simd",
             "706-checker-scheduler"],
         "description": ["Checker tests are not compatible with jvmti."],
-        "variant": "jvmti-stress | redefine-stress | trace-stress | field-stress"
+        "variant": "jvmti-stress | redefine-stress | trace-stress | field-stress | step-stress"
     },
     {
         "tests": [
@@ -510,7 +510,7 @@
             "964-default-iface-init-gen"
         ],
         "description": ["Tests that just take too long with jvmti-stress"],
-        "variant": "jvmti-stress | redefine-stress | trace-stress"
+        "variant": "jvmti-stress | redefine-stress | trace-stress | step-stress"
     },
     {
         "tests": [
@@ -541,7 +541,7 @@
             "981-dedup-original-dex"
         ],
         "description": ["Tests that require exact knowledge of the number of plugins and agents."],
-        "variant": "jvmti-stress | redefine-stress | trace-stress | field-stress"
+        "variant": "jvmti-stress | redefine-stress | trace-stress | field-stress | step-stress"
     },
     {
         "tests": [
@@ -579,7 +579,7 @@
             "004-ThreadStress"
         ],
         "description": "The thread stress test just takes too long with field-stress",
-        "variant": "jvmti-stress | field-stress"
+        "variant": "jvmti-stress | field-stress | step-stress"
     },
     {
         "tests": [
diff --git a/test/run-test b/test/run-test
index 9fe1496..486b465 100755
--- a/test/run-test
+++ b/test/run-test
@@ -145,6 +145,7 @@
 gc_stress="false"
 jvmti_trace_stress="false"
 jvmti_field_stress="false"
+jvmti_step_stress="false"
 jvmti_redefine_stress="false"
 strace="false"
 always_clean="no"
@@ -242,6 +243,9 @@
         basic_verify="true"
         gc_stress="true"
         shift
+    elif [ "x$1" = "x--jvmti-step-stress" ]; then
+        jvmti_step_stress="true"
+        shift
     elif [ "x$1" = "x--jvmti-redefine-stress" ]; then
         jvmti_redefine_stress="true"
         shift
@@ -464,6 +468,9 @@
 if [ "$jvmti_redefine_stress" = "true" ]; then
     run_args="${run_args} --no-app-image --jvmti-redefine-stress"
 fi
+if [ "$jvmti_step_stress" = "true" ]; then
+    run_args="${run_args} --no-app-image --jvmti-step-stress"
+fi
 if [ "$jvmti_field_stress" = "true" ]; then
     run_args="${run_args} --no-app-image --jvmti-field-stress"
 fi
@@ -679,6 +686,7 @@
         echo "    --gcstress            Run with gc stress testing"
         echo "    --gcverify            Run with gc verification"
         echo "    --jvmti-trace-stress  Run with jvmti method tracing stress testing"
+        echo "    --jvmti-step-stress   Run with jvmti single step stress testing"
         echo "    --jvmti-redefine-stress"
         echo "                          Run with jvmti method redefinition stress testing"
         echo "    --always-clean        Delete the test files even if the test fails."
diff --git a/test/testrunner/testrunner.py b/test/testrunner/testrunner.py
index b6a5963..68e1856 100755
--- a/test/testrunner/testrunner.py
+++ b/test/testrunner/testrunner.py
@@ -148,7 +148,7 @@
   VARIANT_TYPE_DICT['jni'] = {'jni', 'forcecopy', 'checkjni'}
   VARIANT_TYPE_DICT['address_sizes'] = {'64', '32'}
   VARIANT_TYPE_DICT['jvmti'] = {'no-jvmti', 'jvmti-stress', 'redefine-stress', 'trace-stress',
-                                'field-stress'}
+                                'field-stress', 'step-stress'}
   VARIANT_TYPE_DICT['compiler'] = {'interp-ac', 'interpreter', 'jit', 'optimizing',
                               'regalloc_gc', 'speed-profile'}
 
@@ -445,6 +445,8 @@
         options_test += ' --jvmti-trace-stress'
       elif jvmti == 'redefine-stress':
         options_test += ' --jvmti-redefine-stress'
+      elif jvmti == 'step-stress':
+        options_test += ' --jvmti-step-stress'
 
       if address_size == '64':
         options_test += ' --64'
@@ -965,6 +967,8 @@
     JVMTI_TYPES.add('redefine-stress')
   if options['field_stress']:
     JVMTI_TYPES.add('field-stress')
+  if options['step_stress']:
+    JVMTI_TYPES.add('step-stress')
   if options['trace_stress']:
     JVMTI_TYPES.add('trace-stress')
   if options['no_jvmti']:
diff --git a/test/ti-agent/common_helper.cc b/test/ti-agent/common_helper.cc
index 4fe58db..0eb71f8 100644
--- a/test/ti-agent/common_helper.cc
+++ b/test/ti-agent/common_helper.cc
@@ -38,6 +38,9 @@
 static void SetupCommonRedefine();
 static void SetupCommonTransform();
 
+// Taken from art/runtime/modifiers.h
+static constexpr uint32_t kAccStatic =       0x0008;  // field, method, ic
+
 template <bool is_redefine>
 static void throwCommonRedefinitionError(jvmtiEnv* jvmti,
                                          JNIEnv* env,
@@ -69,22 +72,6 @@
   env->ThrowNew(env->FindClass("java/lang/Exception"), message.c_str());
 }
 
-namespace common_trace {
-
-// Taken from art/runtime/modifiers.h
-static constexpr uint32_t kAccStatic =       0x0008;  // field, method, ic
-
-struct TraceData {
-  jclass test_klass;
-  jmethodID enter_method;
-  jmethodID exit_method;
-  jmethodID field_access;
-  jmethodID field_modify;
-  bool in_callback;
-  bool access_watch_on_load;
-  bool modify_watch_on_load;
-};
-
 static jobject GetJavaField(jvmtiEnv* jvmti, JNIEnv* env, jclass field_klass, jfieldID f) {
   jint mods = 0;
   if (JvmtiErrorToException(env, jvmti, jvmti->GetFieldModifiers(field_klass, f, &mods))) {
@@ -175,6 +162,221 @@
   return GetJavaValueByType(env, type[0], value);
 }
 
+namespace common_breakpoint {
+
+struct BreakpointData {
+  jclass test_klass;
+  jmethodID breakpoint_method;
+  bool in_callback;
+  bool allow_recursive;
+};
+
+extern "C" void breakpointCB(jvmtiEnv* jvmti,
+                             JNIEnv* jnienv,
+                             jthread thread,
+                             jmethodID method,
+                             jlocation location) {
+  BreakpointData* data = nullptr;
+  if (JvmtiErrorToException(jnienv, jvmti,
+                            jvmti->GetEnvironmentLocalStorage(reinterpret_cast<void**>(&data)))) {
+    return;
+  }
+  if (data->in_callback && !data->allow_recursive) {
+    return;
+  }
+  data->in_callback = true;
+  jobject method_arg = GetJavaMethod(jvmti, jnienv, method);
+  jnienv->CallStaticVoidMethod(data->test_klass,
+                               data->breakpoint_method,
+                               thread,
+                               method_arg,
+                               static_cast<jlong>(location));
+  jnienv->DeleteLocalRef(method_arg);
+  data->in_callback = false;
+}
+
+extern "C" JNIEXPORT jobjectArray JNICALL Java_art_Breakpoint_getLineNumberTableNative(
+    JNIEnv* env,
+    jclass k ATTRIBUTE_UNUSED,
+    jobject target) {
+  jmethodID method = env->FromReflectedMethod(target);
+  if (env->ExceptionCheck()) {
+    return nullptr;
+  }
+  jint nlines;
+  jvmtiLineNumberEntry* lines = nullptr;
+  if (JvmtiErrorToException(env, jvmti_env,
+                            jvmti_env->GetLineNumberTable(method, &nlines, &lines))) {
+    return nullptr;
+  }
+  jintArray lines_array = env->NewIntArray(nlines);
+  if (env->ExceptionCheck()) {
+    jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(lines));
+    return nullptr;
+  }
+  jlongArray locs_array = env->NewLongArray(nlines);
+  if (env->ExceptionCheck()) {
+    jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(lines));
+    return nullptr;
+  }
+  ScopedLocalRef<jclass> object_class(env, env->FindClass("java/lang/Object"));
+  if (env->ExceptionCheck()) {
+    jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(lines));
+    return nullptr;
+  }
+  jobjectArray ret = env->NewObjectArray(2, object_class.get(), nullptr);
+  if (env->ExceptionCheck()) {
+    jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(lines));
+    return nullptr;
+  }
+  jint* temp_lines = env->GetIntArrayElements(lines_array, /*isCopy*/nullptr);
+  jlong* temp_locs = env->GetLongArrayElements(locs_array, /*isCopy*/nullptr);
+  for (jint i = 0; i < nlines; i++) {
+    temp_lines[i] = lines[i].line_number;
+    temp_locs[i] = lines[i].start_location;
+  }
+  env->ReleaseIntArrayElements(lines_array, temp_lines, 0);
+  env->ReleaseLongArrayElements(locs_array, temp_locs, 0);
+  env->SetObjectArrayElement(ret, 0, locs_array);
+  env->SetObjectArrayElement(ret, 1, lines_array);
+  jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(lines));
+  return ret;
+}
+
+extern "C" JNIEXPORT jlong JNICALL Java_art_Breakpoint_getStartLocation(JNIEnv* env,
+                                                                        jclass k ATTRIBUTE_UNUSED,
+                                                                        jobject target) {
+  jmethodID method = env->FromReflectedMethod(target);
+  if (env->ExceptionCheck()) {
+    return 0;
+  }
+  jlong start = 0;
+  jlong end = end;
+  JvmtiErrorToException(env, jvmti_env, jvmti_env->GetMethodLocation(method, &start, &end));
+  return start;
+}
+
+extern "C" JNIEXPORT void JNICALL Java_art_Breakpoint_clearBreakpoint(JNIEnv* env,
+                                                                      jclass k ATTRIBUTE_UNUSED,
+                                                                      jobject target,
+                                                                      jlocation location) {
+  jmethodID method = env->FromReflectedMethod(target);
+  if (env->ExceptionCheck()) {
+    return;
+  }
+  JvmtiErrorToException(env, jvmti_env, jvmti_env->ClearBreakpoint(method, location));
+}
+
+extern "C" JNIEXPORT void JNICALL Java_art_Breakpoint_setBreakpoint(JNIEnv* env,
+                                                                    jclass k ATTRIBUTE_UNUSED,
+                                                                    jobject target,
+                                                                    jlocation location) {
+  jmethodID method = env->FromReflectedMethod(target);
+  if (env->ExceptionCheck()) {
+    return;
+  }
+  JvmtiErrorToException(env, jvmti_env, jvmti_env->SetBreakpoint(method, location));
+}
+
+extern "C" JNIEXPORT void JNICALL Java_art_Breakpoint_startBreakpointWatch(
+    JNIEnv* env,
+    jclass k ATTRIBUTE_UNUSED,
+    jclass method_klass,
+    jobject method,
+    jboolean allow_recursive,
+    jthread thr) {
+  BreakpointData* data = nullptr;
+  if (JvmtiErrorToException(env,
+                            jvmti_env,
+                            jvmti_env->Allocate(sizeof(BreakpointData),
+                                                reinterpret_cast<unsigned char**>(&data)))) {
+    return;
+  }
+  memset(data, 0, sizeof(BreakpointData));
+  data->test_klass = reinterpret_cast<jclass>(env->NewGlobalRef(method_klass));
+  data->breakpoint_method = env->FromReflectedMethod(method);
+  data->in_callback = false;
+  data->allow_recursive = allow_recursive;
+
+  void* old_data = nullptr;
+  if (JvmtiErrorToException(env, jvmti_env, jvmti_env->GetEnvironmentLocalStorage(&old_data))) {
+    return;
+  } else if (old_data != nullptr) {
+    ScopedLocalRef<jclass> rt_exception(env, env->FindClass("java/lang/RuntimeException"));
+    env->ThrowNew(rt_exception.get(), "Environment already has local storage set!");
+    return;
+  }
+  if (JvmtiErrorToException(env, jvmti_env, jvmti_env->SetEnvironmentLocalStorage(data))) {
+    return;
+  }
+  jvmtiEventCallbacks cb;
+  memset(&cb, 0, sizeof(cb));
+  cb.Breakpoint = breakpointCB;
+  if (JvmtiErrorToException(env, jvmti_env, jvmti_env->SetEventCallbacks(&cb, sizeof(cb)))) {
+    return;
+  }
+  if (JvmtiErrorToException(env,
+                            jvmti_env,
+                            jvmti_env->SetEventNotificationMode(JVMTI_ENABLE,
+                                                                JVMTI_EVENT_BREAKPOINT,
+                                                                thr))) {
+    return;
+  }
+}
+
+extern "C" JNIEXPORT void JNICALL Java_art_Breakpoint_stopBreakpointWatch(
+    JNIEnv* env,
+    jclass k ATTRIBUTE_UNUSED,
+    jthread thr) {
+  if (JvmtiErrorToException(env, jvmti_env,
+                            jvmti_env->SetEventNotificationMode(JVMTI_DISABLE,
+                                                                JVMTI_EVENT_BREAKPOINT,
+                                                                thr))) {
+    return;
+  }
+}
+
+}  // namespace common_breakpoint
+
+namespace common_trace {
+
+struct TraceData {
+  jclass test_klass;
+  jmethodID enter_method;
+  jmethodID exit_method;
+  jmethodID field_access;
+  jmethodID field_modify;
+  jmethodID single_step;
+  bool in_callback;
+  bool access_watch_on_load;
+  bool modify_watch_on_load;
+};
+
+static void singleStepCB(jvmtiEnv* jvmti,
+                         JNIEnv* jnienv,
+                         jthread thread,
+                         jmethodID method,
+                         jlocation location) {
+  TraceData* data = nullptr;
+  if (JvmtiErrorToException(jnienv, jvmti,
+                            jvmti->GetEnvironmentLocalStorage(reinterpret_cast<void**>(&data)))) {
+    return;
+  }
+  if (data->in_callback) {
+    return;
+  }
+  CHECK(data->single_step != nullptr);
+  data->in_callback = true;
+  jobject method_arg = GetJavaMethod(jvmti, jnienv, method);
+  jnienv->CallStaticVoidMethod(data->test_klass,
+                               data->single_step,
+                               thread,
+                               method_arg,
+                               static_cast<jlong>(location));
+  jnienv->DeleteLocalRef(method_arg);
+  data->in_callback = false;
+}
+
 static void fieldAccessCB(jvmtiEnv* jvmti,
                           JNIEnv* jnienv,
                           jthread thr ATTRIBUTE_UNUSED,
@@ -481,6 +683,7 @@
     jobject exit,
     jobject field_access,
     jobject field_modify,
+    jobject single_step,
     jthread thr) {
   TraceData* data = nullptr;
   if (JvmtiErrorToException(env,
@@ -495,8 +698,17 @@
   data->exit_method = exit != nullptr ? env->FromReflectedMethod(exit) : nullptr;
   data->field_access = field_access != nullptr ? env->FromReflectedMethod(field_access) : nullptr;
   data->field_modify = field_modify != nullptr ? env->FromReflectedMethod(field_modify) : nullptr;
+  data->single_step = single_step != nullptr ? env->FromReflectedMethod(single_step) : nullptr;
   data->in_callback = false;
 
+  void* old_data = nullptr;
+  if (JvmtiErrorToException(env, jvmti_env, jvmti_env->GetEnvironmentLocalStorage(&old_data))) {
+    return;
+  } else if (old_data != nullptr) {
+    ScopedLocalRef<jclass> rt_exception(env, env->FindClass("java/lang/RuntimeException"));
+    env->ThrowNew(rt_exception.get(), "Environment already has local storage set!");
+    return;
+  }
   if (JvmtiErrorToException(env, jvmti_env, jvmti_env->SetEnvironmentLocalStorage(data))) {
     return;
   }
@@ -508,6 +720,7 @@
   cb.FieldAccess = fieldAccessCB;
   cb.FieldModification = fieldModificationCB;
   cb.ClassPrepare = classPrepareCB;
+  cb.SingleStep = singleStepCB;
   if (JvmtiErrorToException(env, jvmti_env, jvmti_env->SetEventCallbacks(&cb, sizeof(cb)))) {
     return;
   }
@@ -543,6 +756,14 @@
                                                                 thr))) {
     return;
   }
+  if (single_step != nullptr &&
+      JvmtiErrorToException(env,
+                            jvmti_env,
+                            jvmti_env->SetEventNotificationMode(JVMTI_ENABLE,
+                                                                JVMTI_EVENT_SINGLE_STEP,
+                                                                thr))) {
+    return;
+  }
 }
 
 extern "C" JNIEXPORT void JNICALL Java_art_Trace_disableTracing(
@@ -571,6 +792,12 @@
                                                                 thr))) {
     return;
   }
+  if (JvmtiErrorToException(env, jvmti_env,
+                            jvmti_env->SetEventNotificationMode(JVMTI_DISABLE,
+                                                                JVMTI_EVENT_SINGLE_STEP,
+                                                                thr))) {
+    return;
+  }
 }
 
 }  // namespace common_trace
diff --git a/test/ti-stress/stress.cc b/test/ti-stress/stress.cc
index 40fcc4f..d197acd 100644
--- a/test/ti-stress/stress.cc
+++ b/test/ti-stress/stress.cc
@@ -17,6 +17,7 @@
 #include <jni.h>
 #include <stdio.h>
 #include <iostream>
+#include <iomanip>
 #include <fstream>
 #include <memory>
 #include <stdio.h>
@@ -40,6 +41,7 @@
   bool trace_stress;
   bool redefine_stress;
   bool field_stress;
+  bool step_stress;
 };
 
 static void WriteToFile(const std::string& fname, jint data_len, const unsigned char* data) {
@@ -586,6 +588,21 @@
   }
 }
 
+void JNICALL SingleStepHook(jvmtiEnv* jvmtienv,
+                            JNIEnv* env,
+                            jthread thread,
+                            jmethodID method,
+                            jlocation location) {
+  ScopedThreadInfo info(jvmtienv, env, thread);
+  ScopedMethodInfo method_info(jvmtienv, env, method);
+  if (!method_info.Init()) {
+    LOG(ERROR) << "Unable to get method info!";
+    return;
+  }
+  LOG(INFO) << "Single step at location: 0x" << std::setw(8) << std::setfill('0') << std::hex
+            << location << " in method " << method_info << " thread: " << info.GetName();
+}
+
 // The hook we are using.
 void JNICALL ClassFileLoadHookSecretNoOp(jvmtiEnv* jvmti,
                                          JNIEnv* jni_env ATTRIBUTE_UNUSED,
@@ -645,6 +662,8 @@
     std::string cur = GetOption(ops);
     if (cur == "trace") {
       data->trace_stress = true;
+    } else if (cur == "step") {
+      data->step_stress = true;
     } else if (cur == "field") {
       data->field_stress = true;
     } else if (cur == "redefine") {
@@ -776,6 +795,7 @@
   cb.FieldAccess = FieldAccessHook;
   cb.FieldModification = FieldModificationHook;
   cb.ClassPrepare = ClassPrepareHook;
+  cb.SingleStep = SingleStepHook;
   if (jvmti->SetEventCallbacks(&cb, sizeof(cb)) != JVMTI_ERROR_NONE) {
     LOG(ERROR) << "Unable to set class file load hook cb!";
     return 1;
@@ -837,6 +857,13 @@
       return 1;
     }
   }
+  if (data->step_stress) {
+    if (jvmti->SetEventNotificationMode(JVMTI_ENABLE,
+                                        JVMTI_EVENT_SINGLE_STEP,
+                                        nullptr) != JVMTI_ERROR_NONE) {
+      return 1;
+    }
+  }
   return 0;
 }