diff options
| author | 2017-06-29 00:35:46 +0000 | |
|---|---|---|
| committer | 2017-06-29 00:35:46 +0000 | |
| commit | f88e73f2461c54f582834bed193d76fc0dd27dd2 (patch) | |
| tree | c7ada7f5888033ed00416a4685e15114300a2410 | |
| parent | 5cc179c6858827dcef1b79e2af38819169640f27 (diff) | |
| parent | c38c3699b8d91c837c0e590a618aeb4860eed5cc (diff) | |
Merge changes Id2a4db5a,I5e71b85d,I70f76ba7,I4d19cc10,I231178e7
* changes:
Add JVMTI breakpoint and SingleStep tests
Add JVMTI Breakpoint and SingleStep functionality
Interpreter needs to handle DexPcMovedEvent throwing.
Move debugger.cc GetCanonicalMethod to ArtMethod
Interpreter: Save and restore object result register.
59 files changed, 3883 insertions, 76 deletions
diff --git a/runtime/art_method.cc b/runtime/art_method.cc index 155498639e..3e7ed9799d 100644 --- a/runtime/art_method.cc +++ b/runtime/art_method.cc @@ -61,6 +61,19 @@ DEFINE_RUNTIME_DEBUG_FLAG(ArtMethod, kCheckDeclaringClassState); 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 96306af177..4b3e8efdad 100644 --- a/runtime/art_method.h +++ b/runtime/art_method.h @@ -483,6 +483,12 @@ class ArtMethod FINAL { } } + // 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 12bdb32fec..cc12439074 100644 --- a/runtime/debugger.cc +++ b/runtime/debugger.cc @@ -77,25 +77,10 @@ static uint16_t CappedAllocRecordCount(size_t alloc_record_count) { 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 @@ class Breakpoint : public ValueObject { // 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 @@ JDWP::FieldId Dbg::ToFieldId(const ArtField* f) { 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 @@ static void SetEventLocation(JDWP::EventLocation* location, ArtMethod* m, uint32 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 45788e7617..a7a2d1264f 100644 --- a/runtime/interpreter/interpreter_switch_impl.cc +++ b/runtime/interpreter/interpreter_switch_impl.cc @@ -64,13 +64,22 @@ namespace interpreter { } // 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 @@ namespace interpreter { } \ } 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 @@ JValue ExecuteSwitchImpl(Thread* self, const DexFile::CodeItem* code_item, 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 e38f265c5a..619a49aa71 100644 --- a/runtime/openjdkjvmti/Android.bp +++ b/runtime/openjdkjvmti/Android.bp @@ -27,6 +27,7 @@ cc_defaults { "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 0896210f1c..e3768b358f 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 @@ class JvmtiFunctions { 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 b5f12191e6..2d5d527e68 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 @@ struct ArtJvmTiEnv : public jvmtiEnv { 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 @@ const jvmtiCapabilities kPotentialCapabilities = { .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 af99233f90..f30d7cecb3 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 @@ inline void EventHandler::DispatchEvent(ArtJvmTiEnv* env, art::Thread* thread, A } } +// 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 989b9af591..f749daa918 100644 --- a/runtime/openjdkjvmti/events.cc +++ b/runtime/openjdkjvmti/events.cc @@ -423,14 +423,30 @@ class JvmtiMethodTraceListener FINAL : public art::instrumentation::Instrumentat } } - // 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 @@ static uint32_t GetInstrumentationEventsFor(ArtJvmtiEvent event) { 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 @@ static void SetupTraceListener(JvmtiMethodTraceListener* listener, 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 @@ void EventHandler::HandleEventType(ArtJvmtiEvent event, bool enable) { 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 0000000000..6d0e2c60c1 --- /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 0000000000..c3dbef7baf --- /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 5422f48664..debee913ee 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 @@ jvmtiError Redefiner::RedefineClassesDirect(ArtJvmTiEnv* env, 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 @@ bool Redefiner::ClassRedefinition::FinishRemainingAllocations( 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 @@ jvmtiError Redefiner::Run() { // 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 ec4a8b2789..27d7c3d726 100644 --- a/runtime/openjdkjvmti/ti_redefine.h +++ b/runtime/openjdkjvmti/ti_redefine.h @@ -199,6 +199,8 @@ class Redefiner { 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 @@ class Redefiner { art::ArrayRef<const unsigned char> original_dex_file_; }; + ArtJvmTiEnv* env_; jvmtiError result_; art::Runtime* runtime_; art::Thread* self_; @@ -216,10 +219,12 @@ class Redefiner { // 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 30ad532f6c..7eec7831e9 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 9c27c9f69e..ba3d397b0b 100644 --- a/test/988-method-trace/src/art/Trace.java +++ b/test/988-method-trace/src/art/Trace.java @@ -25,6 +25,7 @@ public class Trace { Method exitMethod, Method fieldAccess, Method fieldModify, + Method singleStep, Thread thr); public static native void disableTracing(Thread thr); @@ -32,14 +33,20 @@ public class Trace { 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 9c27c9f69e..ba3d397b0b 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 @@ public class Trace { Method exitMethod, Method fieldAccess, Method fieldModify, + Method singleStep, Thread thr); public static native void disableTracing(Thread thr); @@ -32,14 +33,20 @@ public class Trace { 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 9c27c9f69e..ba3d397b0b 100644 --- a/test/990-field-trace/src/art/Trace.java +++ b/test/990-field-trace/src/art/Trace.java @@ -25,6 +25,7 @@ public class Trace { Method exitMethod, Method fieldAccess, Method fieldModify, + Method singleStep, Thread thr); public static native void disableTracing(Thread thr); @@ -32,14 +33,20 @@ public class Trace { 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 9c27c9f69e..ba3d397b0b 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 @@ public class Trace { Method exitMethod, Method fieldAccess, Method fieldModify, + Method singleStep, Thread thr); public static native void disableTracing(Thread thr); @@ -32,14 +33,20 @@ public class Trace { 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 0000000000..129207098d --- /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 0000000000..962154734b --- /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 0000000000..b5eb5464b7 --- /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 0000000000..51875a7e86 --- /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 0000000000..b11f6f8174 --- /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 0000000000..2a370ebd40 --- /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 0000000000..781ebffc0f --- /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 0000000000..5899659b3c --- /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 0000000000..210dea0471 --- /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 0000000000..51875a7e86 --- /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 0000000000..39cfeb3ee4 --- /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 0000000000..bbb89f707f --- /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 0000000000..6a1c354b39 --- /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 0000000000..a565b7cf14 --- /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 0000000000..80f9cf94bf --- /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 0000000000..51875a7e86 --- /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 0000000000..6f80b43255 --- /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 0000000000..bbb89f707f --- /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 0000000000..a4023fb80a --- /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 0000000000..e0d419e3f5 --- /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 0000000000..58536acece --- /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 0000000000..b6a67e4a08 --- /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 0000000000..51875a7e86 --- /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 0000000000..1b9b0a9b4b --- /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 0000000000..bbb89f707f --- /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 0000000000..56d2938a01 --- /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 0000000000..f3166c33c7 --- /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 0000000000..69c554ca7f --- /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 0000000000..e4a584e46f --- /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 0000000000..51875a7e86 --- /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 0000000000..1927f04d50 --- /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 0000000000..bbb89f707f --- /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 0000000000..a7a522dcca --- /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 0000000000..ba3d397b0b --- /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 23ffc7e5a3..0dff01b6cf 100644 --- a/test/Android.bp +++ b/test/Android.bp @@ -281,6 +281,8 @@ art_cc_defaults { "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 d24edccd8b..ede485a81b 100755 --- a/test/etc/run-test-jar +++ b/test/etc/run-test-jar @@ -63,6 +63,7 @@ TEST_VDEX="n" 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 @@ while true; do 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_STRESS" = "y" ]]; then 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 c4a28a1ed4..a8d492bf4d 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 9fe149697a..486b465a89 100755 --- a/test/run-test +++ b/test/run-test @@ -145,6 +145,7 @@ gc_verify="false" 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 @@ while true; do 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 @@ fi 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 @@ if [ "$usage" = "yes" ]; then 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 b6a5963cf4..68e1856adb 100755 --- a/test/testrunner/testrunner.py +++ b/test/testrunner/testrunner.py @@ -148,7 +148,7 @@ def gather_test_info(): 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 @@ def run_tests(tests): 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 @@ def parse_option(): 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 4fe58db169..0eb71f8371 100644 --- a/test/ti-agent/common_helper.cc +++ b/test/ti-agent/common_helper.cc @@ -38,6 +38,9 @@ static void SetupCommonRetransform(); 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 @@ static void throwCommonRedefinitionError(jvmtiEnv* jvmti, 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 @@ static jobject GetJavaValue(jvmtiEnv* jvmtienv, 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 @@ extern "C" JNIEXPORT void JNICALL Java_art_Trace_enableTracing( jobject exit, jobject field_access, jobject field_modify, + jobject single_step, jthread thr) { TraceData* data = nullptr; if (JvmtiErrorToException(env, @@ -495,8 +698,17 @@ extern "C" JNIEXPORT void JNICALL Java_art_Trace_enableTracing( 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 @@ extern "C" JNIEXPORT void JNICALL Java_art_Trace_enableTracing( 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 @@ extern "C" JNIEXPORT void JNICALL Java_art_Trace_enableTracing( 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 @@ extern "C" JNIEXPORT void JNICALL Java_art_Trace_disableTracing( 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 40fcc4f11d..d197acd216 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 @@ struct StressData { 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 ClassPrepareHook(jvmtiEnv* jvmtienv, } } +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 @@ static void ReadOptions(StressData* data, char* options) { 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 @@ extern "C" JNIEXPORT jint JNICALL Agent_OnLoad(JavaVM* vm, 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 @@ extern "C" JNIEXPORT jint JNICALL Agent_OnLoad(JavaVM* vm, return 1; } } + if (data->step_stress) { + if (jvmti->SetEventNotificationMode(JVMTI_ENABLE, + JVMTI_EVENT_SINGLE_STEP, + nullptr) != JVMTI_ERROR_NONE) { + return 1; + } + } return 0; } |