From 5eae455507399286c845ba54796c47087a72b3e2 Mon Sep 17 00:00:00 2001 From: Sebastien Hertz Date: Wed, 1 Apr 2015 16:34:17 +0200 Subject: [PATCH] Support multiple instrumentation clients Changes Instrumentation::ConfigureStubs to support multiple clients that need different levels of instrumenation. A client is identified by a string key used to save the desired instrumentation level. Also adds regression gtest instrumentation_test and some cleanup. Bug: 19829329 (cherry picked from commit 0462c4c87c39db6cfcd338f323844738109ac3c9) Change-Id: I1fc24a86fcb7cb46d4be806895376c25cc0a0b3c --- build/Android.gtest.mk | 3 + runtime/debugger.cc | 9 +- runtime/instrumentation.cc | 253 +++++----- runtime/instrumentation.h | 48 +- runtime/instrumentation_test.cc | 791 ++++++++++++++++++++++++++++++ runtime/jit/jit.h | 2 - runtime/trace.cc | 11 +- test/Instrumentation/Instrumentation.java | 22 + 8 files changed, 993 insertions(+), 146 deletions(-) create mode 100644 runtime/instrumentation_test.cc create mode 100644 test/Instrumentation/Instrumentation.java diff --git a/build/Android.gtest.mk b/build/Android.gtest.mk index 5a3236d95..730e61d48 100644 --- a/build/Android.gtest.mk +++ b/build/Android.gtest.mk @@ -26,6 +26,7 @@ GTEST_DEX_DIRECTORIES := \ AllFields \ ExceptionHandle \ GetMethodSignature \ + Instrumentation \ Interfaces \ Main \ MultiDex \ @@ -64,6 +65,7 @@ ART_GTEST_class_linker_test_DEX_DEPS := Interfaces MultiDex MyClass Nested Stati ART_GTEST_compiler_driver_test_DEX_DEPS := AbstractMethod StaticLeafMethods ART_GTEST_dex_file_test_DEX_DEPS := GetMethodSignature Main Nested ART_GTEST_exception_test_DEX_DEPS := ExceptionHandle +ART_GTEST_instrumentation_test_DEX_DEPS := Instrumentation ART_GTEST_jni_compiler_test_DEX_DEPS := MyClassNatives ART_GTEST_jni_internal_test_DEX_DEPS := AllFields StaticLeafMethods ART_GTEST_oat_file_assistant_test_DEX_DEPS := Main MainStripped MultiDex Nested @@ -157,6 +159,7 @@ RUNTIME_GTEST_COMMON_SRC_FILES := \ runtime/handle_scope_test.cc \ runtime/indenter_test.cc \ runtime/indirect_reference_table_test.cc \ + runtime/instrumentation_test.cc \ runtime/intern_table_test.cc \ runtime/interpreter/safe_math_test.cc \ runtime/java_vm_ext_test.cc \ diff --git a/runtime/debugger.cc b/runtime/debugger.cc index 47371e54c..9b33e5088 100644 --- a/runtime/debugger.cc +++ b/runtime/debugger.cc @@ -57,6 +57,9 @@ namespace art { +// The key identifying the debugger to update instrumentation. +static constexpr const char* kDbgInstrumentationKey = "Debugger"; + static const size_t kMaxAllocRecordStackDepth = 16; // Max 255. static const size_t kDefaultNumAllocRecords = 64*1024; // Must be a power of 2. 2BE can hold 64k-1. @@ -733,7 +736,7 @@ void Dbg::Disconnected() { instrumentation_events_ = 0; } if (RequiresDeoptimization()) { - runtime->GetInstrumentation()->DisableDeoptimization(); + runtime->GetInstrumentation()->DisableDeoptimization(kDbgInstrumentationKey); } gDebuggerActive = false; } @@ -3054,12 +3057,12 @@ void Dbg::ProcessDeoptimizationRequest(const DeoptimizationRequest& request) { break; case DeoptimizationRequest::kFullDeoptimization: VLOG(jdwp) << "Deoptimize the world ..."; - instrumentation->DeoptimizeEverything(); + instrumentation->DeoptimizeEverything(kDbgInstrumentationKey); VLOG(jdwp) << "Deoptimize the world DONE"; break; case DeoptimizationRequest::kFullUndeoptimization: VLOG(jdwp) << "Undeoptimize the world ..."; - instrumentation->UndeoptimizeEverything(); + instrumentation->UndeoptimizeEverything(kDbgInstrumentationKey); VLOG(jdwp) << "Undeoptimize the world DONE"; break; case DeoptimizationRequest::kSelectiveDeoptimization: diff --git a/runtime/instrumentation.cc b/runtime/instrumentation.cc index e6c333d5c..f810bc828 100644 --- a/runtime/instrumentation.cc +++ b/runtime/instrumentation.cc @@ -16,13 +16,10 @@ #include "instrumentation.h" -#include - #include #include "arch/context.h" #include "atomic.h" -#include "base/unix_file/fd_file.h" #include "class_linker.h" #include "debugger.h" #include "dex_file-inl.h" @@ -39,16 +36,13 @@ #include "mirror/object_array-inl.h" #include "mirror/object-inl.h" #include "nth_caller_visitor.h" -#include "os.h" -#include "scoped_thread_state_change.h" #include "thread.h" #include "thread_list.h" namespace art { - namespace instrumentation { -const bool kVerboseInstrumentation = false; +constexpr bool kVerboseInstrumentation = false; static bool InstallStubsClassVisitor(mirror::Class* klass, void* arg) EXCLUSIVE_LOCKS_REQUIRED(Locks::mutator_lock_) { @@ -64,7 +58,7 @@ Instrumentation::Instrumentation() have_method_entry_listeners_(false), have_method_exit_listeners_(false), have_method_unwind_listeners_(false), have_dex_pc_listeners_(false), have_field_read_listeners_(false), have_field_write_listeners_(false), - have_exception_caught_listeners_(false), + have_exception_caught_listeners_(false), have_backward_branch_listeners_(false), deoptimized_methods_lock_("deoptimized methods lock"), deoptimization_enabled_(false), interpreter_handler_table_(kMainHandlerTable), @@ -166,7 +160,7 @@ void Instrumentation::InstallStubsForMethod(mirror::ArtMethod* method) { // existing instrumentation frames. static void InstrumentationInstallStack(Thread* thread, void* arg) SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { - struct InstallStackVisitor : public StackVisitor { + struct InstallStackVisitor FINAL : public StackVisitor { InstallStackVisitor(Thread* thread_in, Context* context, uintptr_t instrumentation_exit_pc) : StackVisitor(thread_in, context), instrumentation_stack_(thread_in->GetInstrumentationStack()), @@ -175,7 +169,7 @@ static void InstrumentationInstallStack(Thread* thread, void* arg) last_return_pc_(0) { } - virtual bool VisitFrame() SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { + bool VisitFrame() OVERRIDE SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { mirror::ArtMethod* m = GetMethod(); if (m == nullptr) { if (kVerboseInstrumentation) { @@ -306,7 +300,7 @@ static void InstrumentationInstallStack(Thread* thread, void* arg) // Removes the instrumentation exit pc as the return PC for every quick frame. static void InstrumentationRestoreStack(Thread* thread, void* arg) SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { - struct RestoreStackVisitor : public StackVisitor { + struct RestoreStackVisitor FINAL : public StackVisitor { RestoreStackVisitor(Thread* thread_in, uintptr_t instrumentation_exit_pc, Instrumentation* instrumentation) : StackVisitor(thread_in, nullptr), thread_(thread_in), @@ -315,7 +309,7 @@ static void InstrumentationRestoreStack(Thread* thread, void* arg) instrumentation_stack_(thread_in->GetInstrumentationStack()), frames_removed_(0) {} - virtual bool VisitFrame() SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { + bool VisitFrame() OVERRIDE SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { if (instrumentation_stack_->size() == 0) { return false; // Stop. } @@ -390,25 +384,29 @@ static void InstrumentationRestoreStack(Thread* thread, void* arg) } } +static bool HasEvent(Instrumentation::InstrumentationEvent expected, uint32_t events) { + return (events & expected) != 0; +} + void Instrumentation::AddListener(InstrumentationListener* listener, uint32_t events) { Locks::mutator_lock_->AssertExclusiveHeld(Thread::Current()); - if ((events & kMethodEntered) != 0) { + if (HasEvent(kMethodEntered, events)) { method_entry_listeners_.push_back(listener); have_method_entry_listeners_ = true; } - if ((events & kMethodExited) != 0) { + if (HasEvent(kMethodExited, events)) { method_exit_listeners_.push_back(listener); have_method_exit_listeners_ = true; } - if ((events & kMethodUnwind) != 0) { + if (HasEvent(kMethodUnwind, events)) { method_unwind_listeners_.push_back(listener); have_method_unwind_listeners_ = true; } - if ((events & kBackwardBranch) != 0) { + if (HasEvent(kBackwardBranch, events)) { backward_branch_listeners_.push_back(listener); have_backward_branch_listeners_ = true; } - if ((events & kDexPcMoved) != 0) { + if (HasEvent(kDexPcMoved, events)) { std::list* modified; if (have_dex_pc_listeners_) { modified = new std::list(*dex_pc_listeners_.get()); @@ -419,7 +417,7 @@ void Instrumentation::AddListener(InstrumentationListener* listener, uint32_t ev dex_pc_listeners_.reset(modified); have_dex_pc_listeners_ = true; } - if ((events & kFieldRead) != 0) { + if (HasEvent(kFieldRead, events)) { std::list* modified; if (have_field_read_listeners_) { modified = new std::list(*field_read_listeners_.get()); @@ -430,7 +428,7 @@ void Instrumentation::AddListener(InstrumentationListener* listener, uint32_t ev field_read_listeners_.reset(modified); have_field_read_listeners_ = true; } - if ((events & kFieldWritten) != 0) { + if (HasEvent(kFieldWritten, events)) { std::list* modified; if (have_field_write_listeners_) { modified = new std::list(*field_write_listeners_.get()); @@ -441,7 +439,7 @@ void Instrumentation::AddListener(InstrumentationListener* listener, uint32_t ev field_write_listeners_.reset(modified); have_field_write_listeners_ = true; } - if ((events & kExceptionCaught) != 0) { + if (HasEvent(kExceptionCaught, events)) { std::list* modified; if (have_exception_caught_listeners_) { modified = new std::list(*exception_caught_listeners_.get()); @@ -458,102 +456,104 @@ void Instrumentation::AddListener(InstrumentationListener* listener, uint32_t ev void Instrumentation::RemoveListener(InstrumentationListener* listener, uint32_t events) { Locks::mutator_lock_->AssertExclusiveHeld(Thread::Current()); - if ((events & kMethodEntered) != 0) { - if (have_method_entry_listeners_) { - method_entry_listeners_.remove(listener); - have_method_entry_listeners_ = !method_entry_listeners_.empty(); - } + if (HasEvent(kMethodEntered, events) && have_method_entry_listeners_) { + method_entry_listeners_.remove(listener); + have_method_entry_listeners_ = !method_entry_listeners_.empty(); } - if ((events & kMethodExited) != 0) { - if (have_method_exit_listeners_) { - method_exit_listeners_.remove(listener); - have_method_exit_listeners_ = !method_exit_listeners_.empty(); - } + if (HasEvent(kMethodExited, events) && have_method_exit_listeners_) { + method_exit_listeners_.remove(listener); + have_method_exit_listeners_ = !method_exit_listeners_.empty(); } - if ((events & kMethodUnwind) != 0) { - if (have_method_unwind_listeners_) { + if (HasEvent(kMethodUnwind, events) && have_method_unwind_listeners_) { method_unwind_listeners_.remove(listener); have_method_unwind_listeners_ = !method_unwind_listeners_.empty(); - } } - if ((events & kDexPcMoved) != 0) { + if (HasEvent(kBackwardBranch, events) && have_backward_branch_listeners_) { + backward_branch_listeners_.remove(listener); + have_backward_branch_listeners_ = !backward_branch_listeners_.empty(); + } + if (HasEvent(kDexPcMoved, events) && have_dex_pc_listeners_) { + std::list* modified = + new std::list(*dex_pc_listeners_.get()); + modified->remove(listener); + have_dex_pc_listeners_ = !modified->empty(); if (have_dex_pc_listeners_) { - std::list* modified = - new std::list(*dex_pc_listeners_.get()); - modified->remove(listener); - have_dex_pc_listeners_ = !modified->empty(); - if (have_dex_pc_listeners_) { - dex_pc_listeners_.reset(modified); - } else { - dex_pc_listeners_.reset(); - delete modified; - } + dex_pc_listeners_.reset(modified); + } else { + dex_pc_listeners_.reset(); + delete modified; } } - if ((events & kFieldRead) != 0) { + if (HasEvent(kFieldRead, events) && have_field_read_listeners_) { + std::list* modified = + new std::list(*field_read_listeners_.get()); + modified->remove(listener); + have_field_read_listeners_ = !modified->empty(); if (have_field_read_listeners_) { - std::list* modified = - new std::list(*field_read_listeners_.get()); - modified->remove(listener); - have_field_read_listeners_ = !modified->empty(); - if (have_field_read_listeners_) { - field_read_listeners_.reset(modified); - } else { - field_read_listeners_.reset(); - delete modified; - } + field_read_listeners_.reset(modified); + } else { + field_read_listeners_.reset(); + delete modified; } } - if ((events & kFieldWritten) != 0) { + if (HasEvent(kFieldWritten, events) && have_field_write_listeners_) { + std::list* modified = + new std::list(*field_write_listeners_.get()); + modified->remove(listener); + have_field_write_listeners_ = !modified->empty(); if (have_field_write_listeners_) { - std::list* modified = - new std::list(*field_write_listeners_.get()); - modified->remove(listener); - have_field_write_listeners_ = !modified->empty(); - if (have_field_write_listeners_) { - field_write_listeners_.reset(modified); - } else { - field_write_listeners_.reset(); - delete modified; - } + field_write_listeners_.reset(modified); + } else { + field_write_listeners_.reset(); + delete modified; } } - if ((events & kExceptionCaught) != 0) { + if (HasEvent(kExceptionCaught, events) && have_exception_caught_listeners_) { + std::list* modified = + new std::list(*exception_caught_listeners_.get()); + modified->remove(listener); + have_exception_caught_listeners_ = !modified->empty(); if (have_exception_caught_listeners_) { - std::list* modified = - new std::list(*exception_caught_listeners_.get()); - modified->remove(listener); - have_exception_caught_listeners_ = !modified->empty(); - if (have_exception_caught_listeners_) { - exception_caught_listeners_.reset(modified); - } else { - exception_caught_listeners_.reset(); - delete modified; - } + exception_caught_listeners_.reset(modified); + } else { + exception_caught_listeners_.reset(); + delete modified; } } UpdateInterpreterHandlerTable(); } -void Instrumentation::ConfigureStubs(bool require_entry_exit_stubs, bool require_interpreter) { - interpret_only_ = require_interpreter || forced_interpret_only_; - // Compute what level of instrumentation is required and compare to current. - int desired_level, current_level; - if (require_interpreter) { - desired_level = 2; - } else if (require_entry_exit_stubs) { - desired_level = 1; - } else { - desired_level = 0; - } +Instrumentation::InstrumentationLevel Instrumentation::GetCurrentInstrumentationLevel() const { if (interpreter_stubs_installed_) { - current_level = 2; + return InstrumentationLevel::kInstrumentWithInterpreter; } else if (entry_exit_stubs_installed_) { - current_level = 1; + return InstrumentationLevel::kInstrumentWithInstrumentationStubs; + } else { + return InstrumentationLevel::kInstrumentNothing; + } +} + +void Instrumentation::ConfigureStubs(const char* key, InstrumentationLevel desired_level) { + // Store the instrumentation level for this key or remove it. + if (desired_level == InstrumentationLevel::kInstrumentNothing) { + // The client no longer needs instrumentation. + requested_instrumentation_levels_.erase(key); } else { - current_level = 0; + // The client needs instrumentation. + requested_instrumentation_levels_.Overwrite(key, desired_level); } - if (desired_level == current_level) { + + // Look for the highest required instrumentation level. + InstrumentationLevel requested_level = InstrumentationLevel::kInstrumentNothing; + for (const auto& v : requested_instrumentation_levels_) { + requested_level = std::max(requested_level, v.second); + } + + interpret_only_ = (requested_level == InstrumentationLevel::kInstrumentWithInterpreter) || + forced_interpret_only_; + + InstrumentationLevel current_level = GetCurrentInstrumentationLevel(); + if (requested_level == current_level) { // We're already set. return; } @@ -561,12 +561,14 @@ void Instrumentation::ConfigureStubs(bool require_entry_exit_stubs, bool require Runtime* runtime = Runtime::Current(); Locks::mutator_lock_->AssertExclusiveHeld(self); Locks::thread_list_lock_->AssertNotHeld(self); - if (desired_level > 0) { - if (require_interpreter) { + if (requested_level > InstrumentationLevel::kInstrumentNothing) { + if (requested_level == InstrumentationLevel::kInstrumentWithInterpreter) { interpreter_stubs_installed_ = true; + entry_exit_stubs_installed_ = true; } else { - CHECK(require_entry_exit_stubs); + CHECK_EQ(requested_level, InstrumentationLevel::kInstrumentWithInstrumentationStubs); entry_exit_stubs_installed_ = true; + interpreter_stubs_installed_ = false; } runtime->GetClassLinker()->VisitClasses(InstallStubsClassVisitor, this); instrumentation_stubs_installed_ = true; @@ -590,8 +592,7 @@ void Instrumentation::ConfigureStubs(bool require_entry_exit_stubs, bool require } } -static void ResetQuickAllocEntryPointsForThread(Thread* thread, void* arg) { - UNUSED(arg); +static void ResetQuickAllocEntryPointsForThread(Thread* thread, void* arg ATTRIBUTE_UNUSED) { thread->ResetQuickAllocEntryPointsForThread(); } @@ -804,11 +805,11 @@ void Instrumentation::EnableDeoptimization() { deoptimization_enabled_ = true; } -void Instrumentation::DisableDeoptimization() { +void Instrumentation::DisableDeoptimization(const char* key) { CHECK_EQ(deoptimization_enabled_, true); // If we deoptimized everything, undo it. if (interpreter_stubs_installed_) { - UndeoptimizeEverything(); + UndeoptimizeEverything(key); } // Undeoptimized selected methods. while (true) { @@ -828,25 +829,35 @@ void Instrumentation::DisableDeoptimization() { // Indicates if instrumentation should notify method enter/exit events to the listeners. bool Instrumentation::ShouldNotifyMethodEnterExitEvents() const { + if (!HasMethodEntryListeners() && !HasMethodExitListeners()) { + return false; + } return !deoptimization_enabled_ && !interpreter_stubs_installed_; } -void Instrumentation::DeoptimizeEverything() { - CHECK(!interpreter_stubs_installed_); - ConfigureStubs(false, true); +void Instrumentation::DeoptimizeEverything(const char* key) { + CHECK(deoptimization_enabled_); + ConfigureStubs(key, InstrumentationLevel::kInstrumentWithInterpreter); } -void Instrumentation::UndeoptimizeEverything() { +void Instrumentation::UndeoptimizeEverything(const char* key) { CHECK(interpreter_stubs_installed_); - ConfigureStubs(false, false); + CHECK(deoptimization_enabled_); + ConfigureStubs(key, InstrumentationLevel::kInstrumentNothing); } -void Instrumentation::EnableMethodTracing(bool require_interpreter) { - ConfigureStubs(!require_interpreter, require_interpreter); +void Instrumentation::EnableMethodTracing(const char* key, bool needs_interpreter) { + InstrumentationLevel level; + if (needs_interpreter) { + level = InstrumentationLevel::kInstrumentWithInterpreter; + } else { + level = InstrumentationLevel::kInstrumentWithInstrumentationStubs; + } + ConfigureStubs(key, level); } -void Instrumentation::DisableMethodTracing() { - ConfigureStubs(false, false); +void Instrumentation::DisableMethodTracing(const char* key) { + ConfigureStubs(key, InstrumentationLevel::kInstrumentNothing); } const void* Instrumentation::GetQuickCodeFor(mirror::ArtMethod* method, size_t pointer_size) const { @@ -896,7 +907,7 @@ void Instrumentation::MethodExitEventImpl(Thread* thread, mirror::Object* this_o void Instrumentation::MethodUnwindEvent(Thread* thread, mirror::Object* this_object, mirror::ArtMethod* method, uint32_t dex_pc) const { - if (have_method_unwind_listeners_) { + if (HasMethodUnwindListeners()) { for (InstrumentationListener* listener : method_unwind_listeners_) { listener->MethodUnwind(thread, this_object, method, dex_pc); } @@ -906,11 +917,9 @@ void Instrumentation::MethodUnwindEvent(Thread* thread, mirror::Object* this_obj void Instrumentation::DexPcMovedEventImpl(Thread* thread, mirror::Object* this_object, mirror::ArtMethod* method, uint32_t dex_pc) const { - if (HasDexPcListeners()) { - std::shared_ptr> original(dex_pc_listeners_); - for (InstrumentationListener* listener : *original.get()) { - listener->DexPcMoved(thread, this_object, method, dex_pc); - } + std::shared_ptr> original(dex_pc_listeners_); + for (InstrumentationListener* listener : *original.get()) { + listener->DexPcMoved(thread, this_object, method, dex_pc); } } @@ -924,22 +933,18 @@ void Instrumentation::BackwardBranchImpl(Thread* thread, mirror::ArtMethod* meth void Instrumentation::FieldReadEventImpl(Thread* thread, mirror::Object* this_object, mirror::ArtMethod* method, uint32_t dex_pc, ArtField* field) const { - if (HasFieldReadListeners()) { - std::shared_ptr> original(field_read_listeners_); - for (InstrumentationListener* listener : *original.get()) { - listener->FieldRead(thread, this_object, method, dex_pc, field); - } + std::shared_ptr> original(field_read_listeners_); + for (InstrumentationListener* listener : *original.get()) { + listener->FieldRead(thread, this_object, method, dex_pc, field); } } void Instrumentation::FieldWriteEventImpl(Thread* thread, mirror::Object* this_object, mirror::ArtMethod* method, uint32_t dex_pc, ArtField* field, const JValue& field_value) const { - if (HasFieldWriteListeners()) { - std::shared_ptr> original(field_write_listeners_); - for (InstrumentationListener* listener : *original.get()) { - listener->FieldWritten(thread, this_object, method, dex_pc, field, field_value); - } + std::shared_ptr> original(field_write_listeners_); + for (InstrumentationListener* listener : *original.get()) { + listener->FieldWritten(thread, this_object, method, dex_pc, field, field_value); } } diff --git a/runtime/instrumentation.h b/runtime/instrumentation.h index 8b7fcca48..7d70d211b 100644 --- a/runtime/instrumentation.h +++ b/runtime/instrumentation.h @@ -22,11 +22,10 @@ #include #include "arch/instruction_set.h" -#include "atomic.h" #include "base/macros.h" #include "base/mutex.h" #include "gc_root.h" -#include "object_callbacks.h" +#include "safe_map.h" namespace art { namespace mirror { @@ -67,8 +66,6 @@ struct InstrumentationListener { uint32_t dex_pc) SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) = 0; // Call-back for when a method is exited. - // TODO: its likely passing the return value would be useful, however, we may need to get and - // parse the shorty to determine what kind of register holds the result. virtual void MethodExited(Thread* thread, mirror::Object* this_object, mirror::ArtMethod* method, uint32_t dex_pc, const JValue& return_value) @@ -119,6 +116,12 @@ class Instrumentation { kBackwardBranch = 0x80, }; + enum class InstrumentationLevel { + kInstrumentNothing, // execute without instrumentation + kInstrumentWithInstrumentationStubs, // execute with instrumentation entry/exit stubs + kInstrumentWithInterpreter // execute with interpreter + }; + Instrumentation(); // Add a listener to be notified of the masked together sent of instrumentation events. This @@ -138,7 +141,7 @@ class Instrumentation { void EnableDeoptimization() EXCLUSIVE_LOCKS_REQUIRED(Locks::mutator_lock_) LOCKS_EXCLUDED(deoptimized_methods_lock_); - void DisableDeoptimization() + void DisableDeoptimization(const char* key) EXCLUSIVE_LOCKS_REQUIRED(Locks::mutator_lock_) LOCKS_EXCLUDED(deoptimized_methods_lock_); bool AreAllMethodsDeoptimized() const { @@ -147,12 +150,12 @@ class Instrumentation { bool ShouldNotifyMethodEnterExitEvents() const SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); // Executes everything with interpreter. - void DeoptimizeEverything() + void DeoptimizeEverything(const char* key) EXCLUSIVE_LOCKS_REQUIRED(Locks::mutator_lock_) LOCKS_EXCLUDED(Locks::thread_list_lock_, Locks::classlinker_classes_lock_); // Executes everything with compiled code (or interpreter if there is no code). - void UndeoptimizeEverything() + void UndeoptimizeEverything(const char* key) EXCLUSIVE_LOCKS_REQUIRED(Locks::mutator_lock_) LOCKS_EXCLUDED(Locks::thread_list_lock_, Locks::classlinker_classes_lock_); @@ -170,18 +173,19 @@ class Instrumentation { LOCKS_EXCLUDED(Locks::thread_list_lock_, deoptimized_methods_lock_) EXCLUSIVE_LOCKS_REQUIRED(Locks::mutator_lock_); + // Indicates whether the method has been deoptimized so it is executed with the interpreter. bool IsDeoptimized(mirror::ArtMethod* method) LOCKS_EXCLUDED(deoptimized_methods_lock_) SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); - // Enable method tracing by installing instrumentation entry/exit stubs. - void EnableMethodTracing( - bool require_interpreter = kDeoptimizeForAccurateMethodEntryExitListeners) + // Enable method tracing by installing instrumentation entry/exit stubs or interpreter. + void EnableMethodTracing(const char* key, + bool needs_interpreter = kDeoptimizeForAccurateMethodEntryExitListeners) EXCLUSIVE_LOCKS_REQUIRED(Locks::mutator_lock_) LOCKS_EXCLUDED(Locks::thread_list_lock_, Locks::classlinker_classes_lock_); - // Disable method tracing by uninstalling instrumentation entry/exit stubs. - void DisableMethodTracing() + // Disable method tracing by uninstalling instrumentation entry/exit stubs or interpreter. + void DisableMethodTracing(const char* key) EXCLUSIVE_LOCKS_REQUIRED(Locks::mutator_lock_) LOCKS_EXCLUDED(Locks::thread_list_lock_, Locks::classlinker_classes_lock_); @@ -236,6 +240,10 @@ class Instrumentation { return have_method_exit_listeners_; } + bool HasMethodUnwindListeners() const SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { + return have_method_unwind_listeners_; + } + bool HasDexPcListeners() const SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { return have_dex_pc_listeners_; } @@ -355,8 +363,14 @@ class Instrumentation { LOCKS_EXCLUDED(deoptimized_methods_lock_); private: + InstrumentationLevel GetCurrentInstrumentationLevel() const; + // Does the job of installing or removing instrumentation code within methods. - void ConfigureStubs(bool require_entry_exit_stubs, bool require_interpreter) + // In order to support multiple clients using instrumentation at the same time, + // the caller must pass a unique key (a string) identifying it so we remind which + // instrumentation level it needs. Therefore the current instrumentation level + // becomes the highest instrumentation level required by a client. + void ConfigureStubs(const char* key, InstrumentationLevel desired_instrumentation_level) EXCLUSIVE_LOCKS_REQUIRED(Locks::mutator_lock_) LOCKS_EXCLUDED(Locks::thread_list_lock_, Locks::classlinker_classes_lock_, deoptimized_methods_lock_); @@ -452,6 +466,11 @@ class Instrumentation { // Do we have any backward branch listeners? Short-cut to avoid taking the instrumentation_lock_. bool have_backward_branch_listeners_ GUARDED_BY(Locks::mutator_lock_); + // Contains the instrumentation level required by each client of the instrumentation identified + // by a string key. + typedef SafeMap InstrumentationLevelTable; + InstrumentationLevelTable requested_instrumentation_levels_ GUARDED_BY(Locks::mutator_lock_); + // The event listeners, written to with the mutator_lock_ exclusively held. std::list method_entry_listeners_ GUARDED_BY(Locks::mutator_lock_); std::list method_exit_listeners_ GUARDED_BY(Locks::mutator_lock_); @@ -481,9 +500,12 @@ class Instrumentation { size_t quick_alloc_entry_points_instrumentation_counter_ GUARDED_BY(Locks::instrument_entrypoints_lock_); + friend class InstrumentationTest; // For GetCurrentInstrumentationLevel and ConfigureStubs. + DISALLOW_COPY_AND_ASSIGN(Instrumentation); }; std::ostream& operator<<(std::ostream& os, const Instrumentation::InstrumentationEvent& rhs); +std::ostream& operator<<(std::ostream& os, const Instrumentation::InstrumentationLevel& rhs); // An element in the instrumentation side stack maintained in art::Thread. struct InstrumentationStackFrame { diff --git a/runtime/instrumentation_test.cc b/runtime/instrumentation_test.cc new file mode 100644 index 000000000..5afacb8fe --- /dev/null +++ b/runtime/instrumentation_test.cc @@ -0,0 +1,791 @@ +/* + * Copyright (C) 2015 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 "instrumentation.h" + +#include "common_runtime_test.h" +#include "common_throws.h" +#include "class_linker-inl.h" +#include "dex_file.h" +#include "handle_scope-inl.h" +#include "jvalue.h" +#include "runtime.h" +#include "scoped_thread_state_change.h" +#include "thread_list.h" +#include "thread-inl.h" + +namespace art { +namespace instrumentation { + +class TestInstrumentationListener FINAL : public instrumentation::InstrumentationListener { + public: + TestInstrumentationListener() + : received_method_enter_event(false), received_method_exit_event(false), + received_method_unwind_event(false), received_dex_pc_moved_event(false), + received_field_read_event(false), received_field_written_event(false), + received_exception_caught_event(false), received_backward_branch_event(false) {} + + virtual ~TestInstrumentationListener() {} + + void MethodEntered(Thread* thread ATTRIBUTE_UNUSED, + mirror::Object* this_object ATTRIBUTE_UNUSED, + mirror::ArtMethod* method ATTRIBUTE_UNUSED, + uint32_t dex_pc ATTRIBUTE_UNUSED) + OVERRIDE SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { + received_method_enter_event = true; + } + + void MethodExited(Thread* thread ATTRIBUTE_UNUSED, + mirror::Object* this_object ATTRIBUTE_UNUSED, + mirror::ArtMethod* method ATTRIBUTE_UNUSED, + uint32_t dex_pc ATTRIBUTE_UNUSED, + const JValue& return_value ATTRIBUTE_UNUSED) + OVERRIDE SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { + received_method_exit_event = true; + } + + void MethodUnwind(Thread* thread ATTRIBUTE_UNUSED, + mirror::Object* this_object ATTRIBUTE_UNUSED, + mirror::ArtMethod* method ATTRIBUTE_UNUSED, + uint32_t dex_pc ATTRIBUTE_UNUSED) + OVERRIDE SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { + received_method_unwind_event = true; + } + + void DexPcMoved(Thread* thread ATTRIBUTE_UNUSED, + mirror::Object* this_object ATTRIBUTE_UNUSED, + mirror::ArtMethod* method ATTRIBUTE_UNUSED, + uint32_t new_dex_pc ATTRIBUTE_UNUSED) + OVERRIDE SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { + received_dex_pc_moved_event = true; + } + + void FieldRead(Thread* thread ATTRIBUTE_UNUSED, + mirror::Object* this_object ATTRIBUTE_UNUSED, + mirror::ArtMethod* method ATTRIBUTE_UNUSED, + uint32_t dex_pc ATTRIBUTE_UNUSED, + ArtField* field ATTRIBUTE_UNUSED) + OVERRIDE SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { + received_field_read_event = true; + } + + void FieldWritten(Thread* thread ATTRIBUTE_UNUSED, + mirror::Object* this_object ATTRIBUTE_UNUSED, + mirror::ArtMethod* method ATTRIBUTE_UNUSED, + uint32_t dex_pc ATTRIBUTE_UNUSED, + ArtField* field ATTRIBUTE_UNUSED, + const JValue& field_value ATTRIBUTE_UNUSED) + OVERRIDE SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { + received_field_written_event = true; + } + + void ExceptionCaught(Thread* thread ATTRIBUTE_UNUSED, + mirror::Throwable* exception_object ATTRIBUTE_UNUSED) + OVERRIDE SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { + received_exception_caught_event = true; + } + + void BackwardBranch(Thread* thread ATTRIBUTE_UNUSED, + mirror::ArtMethod* method ATTRIBUTE_UNUSED, + int32_t dex_pc_offset ATTRIBUTE_UNUSED) + OVERRIDE SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { + received_backward_branch_event = true; + } + + void Reset() { + received_method_enter_event = false; + received_method_exit_event = false; + received_method_unwind_event = false; + received_dex_pc_moved_event = false; + received_field_read_event = false; + received_field_written_event = false; + received_exception_caught_event = false; + received_backward_branch_event = false; + } + + bool received_method_enter_event; + bool received_method_exit_event; + bool received_method_unwind_event; + bool received_dex_pc_moved_event; + bool received_field_read_event; + bool received_field_written_event; + bool received_exception_caught_event; + bool received_backward_branch_event; + + private: + DISALLOW_COPY_AND_ASSIGN(TestInstrumentationListener); +}; + +class InstrumentationTest : public CommonRuntimeTest { + public: + // Unique keys used to test Instrumentation::ConfigureStubs. + static constexpr const char* kClientOneKey = "TestClient1"; + static constexpr const char* kClientTwoKey = "TestClient2"; + + void CheckConfigureStubs(const char* key, Instrumentation::InstrumentationLevel level) { + ScopedObjectAccess soa(Thread::Current()); + instrumentation::Instrumentation* instr = Runtime::Current()->GetInstrumentation(); + { + soa.Self()->TransitionFromRunnableToSuspended(kSuspended); + Runtime* runtime = Runtime::Current(); + runtime->GetThreadList()->SuspendAll("Instrumentation::ConfigureStubs"); + instr->ConfigureStubs(key, level); + runtime->GetThreadList()->ResumeAll(); + soa.Self()->TransitionFromSuspendedToRunnable(); + } + } + + Instrumentation::InstrumentationLevel GetCurrentInstrumentationLevel() { + return Runtime::Current()->GetInstrumentation()->GetCurrentInstrumentationLevel(); + } + + size_t GetInstrumentationUserCount() { + ScopedObjectAccess soa(Thread::Current()); + return Runtime::Current()->GetInstrumentation()->requested_instrumentation_levels_.size(); + } + + void TestEvent(uint32_t instrumentation_event) { + ScopedObjectAccess soa(Thread::Current()); + instrumentation::Instrumentation* instr = Runtime::Current()->GetInstrumentation(); + TestInstrumentationListener listener; + { + soa.Self()->TransitionFromRunnableToSuspended(kSuspended); + Runtime* runtime = Runtime::Current(); + runtime->GetThreadList()->SuspendAll("Add instrumentation listener"); + instr->AddListener(&listener, instrumentation_event); + runtime->GetThreadList()->ResumeAll(); + soa.Self()->TransitionFromSuspendedToRunnable(); + } + + mirror::ArtMethod* const event_method = nullptr; + mirror::Object* const event_obj = nullptr; + const uint32_t event_dex_pc = 0; + + // Check the listener is registered and is notified of the event. + EXPECT_TRUE(HasEventListener(instr, instrumentation_event)); + EXPECT_FALSE(DidListenerReceiveEvent(listener, instrumentation_event)); + ReportEvent(instr, instrumentation_event, soa.Self(), event_method, event_obj, event_dex_pc); + EXPECT_TRUE(DidListenerReceiveEvent(listener, instrumentation_event)); + + listener.Reset(); + { + soa.Self()->TransitionFromRunnableToSuspended(kSuspended); + Runtime* runtime = Runtime::Current(); + runtime->GetThreadList()->SuspendAll("Remove instrumentation listener"); + instr->RemoveListener(&listener, instrumentation_event); + runtime->GetThreadList()->ResumeAll(); + soa.Self()->TransitionFromSuspendedToRunnable(); + } + + // Check the listener is not registered and is not notified of the event. + EXPECT_FALSE(HasEventListener(instr, instrumentation_event)); + EXPECT_FALSE(DidListenerReceiveEvent(listener, instrumentation_event)); + ReportEvent(instr, instrumentation_event, soa.Self(), event_method, event_obj, event_dex_pc); + EXPECT_FALSE(DidListenerReceiveEvent(listener, instrumentation_event)); + } + + void DeoptimizeMethod(Thread* self, Handle method, + bool enable_deoptimization) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { + Runtime* runtime = Runtime::Current(); + instrumentation::Instrumentation* instrumentation = runtime->GetInstrumentation(); + self->TransitionFromRunnableToSuspended(kSuspended); + runtime->GetThreadList()->SuspendAll("Single method deoptimization"); + if (enable_deoptimization) { + instrumentation->EnableDeoptimization(); + } + instrumentation->Deoptimize(method.Get()); + runtime->GetThreadList()->ResumeAll(); + self->TransitionFromSuspendedToRunnable(); + } + + void UndeoptimizeMethod(Thread* self, Handle method, + const char* key, bool disable_deoptimization) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { + Runtime* runtime = Runtime::Current(); + instrumentation::Instrumentation* instrumentation = runtime->GetInstrumentation(); + self->TransitionFromRunnableToSuspended(kSuspended); + runtime->GetThreadList()->SuspendAll("Single method undeoptimization"); + instrumentation->Undeoptimize(method.Get()); + if (disable_deoptimization) { + instrumentation->DisableDeoptimization(key); + } + runtime->GetThreadList()->ResumeAll(); + self->TransitionFromSuspendedToRunnable(); + } + + void DeoptimizeEverything(Thread* self, const char* key, bool enable_deoptimization) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { + Runtime* runtime = Runtime::Current(); + instrumentation::Instrumentation* instrumentation = runtime->GetInstrumentation(); + self->TransitionFromRunnableToSuspended(kSuspended); + runtime->GetThreadList()->SuspendAll("Full deoptimization"); + if (enable_deoptimization) { + instrumentation->EnableDeoptimization(); + } + instrumentation->DeoptimizeEverything(key); + runtime->GetThreadList()->ResumeAll(); + self->TransitionFromSuspendedToRunnable(); + } + + void UndeoptimizeEverything(Thread* self, const char* key, bool disable_deoptimization) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { + Runtime* runtime = Runtime::Current(); + instrumentation::Instrumentation* instrumentation = runtime->GetInstrumentation(); + self->TransitionFromRunnableToSuspended(kSuspended); + runtime->GetThreadList()->SuspendAll("Full undeoptimization"); + instrumentation->UndeoptimizeEverything(key); + if (disable_deoptimization) { + instrumentation->DisableDeoptimization(key); + } + runtime->GetThreadList()->ResumeAll(); + self->TransitionFromSuspendedToRunnable(); + } + + void EnableMethodTracing(Thread* self, const char* key, bool needs_interpreter) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { + Runtime* runtime = Runtime::Current(); + instrumentation::Instrumentation* instrumentation = runtime->GetInstrumentation(); + self->TransitionFromRunnableToSuspended(kSuspended); + runtime->GetThreadList()->SuspendAll("EnableMethodTracing"); + instrumentation->EnableMethodTracing(key, needs_interpreter); + runtime->GetThreadList()->ResumeAll(); + self->TransitionFromSuspendedToRunnable(); + } + + void DisableMethodTracing(Thread* self, const char* key) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { + Runtime* runtime = Runtime::Current(); + instrumentation::Instrumentation* instrumentation = runtime->GetInstrumentation(); + self->TransitionFromRunnableToSuspended(kSuspended); + runtime->GetThreadList()->SuspendAll("EnableMethodTracing"); + instrumentation->DisableMethodTracing(key); + runtime->GetThreadList()->ResumeAll(); + self->TransitionFromSuspendedToRunnable(); + } + + private: + static bool HasEventListener(const instrumentation::Instrumentation* instr, uint32_t event_type) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { + switch (event_type) { + case instrumentation::Instrumentation::kMethodEntered: + return instr->HasMethodEntryListeners(); + case instrumentation::Instrumentation::kMethodExited: + return instr->HasMethodExitListeners(); + case instrumentation::Instrumentation::kMethodUnwind: + return instr->HasMethodUnwindListeners(); + case instrumentation::Instrumentation::kDexPcMoved: + return instr->HasDexPcListeners(); + case instrumentation::Instrumentation::kFieldRead: + return instr->HasFieldReadListeners(); + case instrumentation::Instrumentation::kFieldWritten: + return instr->HasFieldWriteListeners(); + case instrumentation::Instrumentation::kExceptionCaught: + return instr->HasExceptionCaughtListeners(); + case instrumentation::Instrumentation::kBackwardBranch: + return instr->HasBackwardBranchListeners(); + default: + LOG(FATAL) << "Unknown instrumentation event " << event_type; + UNREACHABLE(); + } + } + + static void ReportEvent(const instrumentation::Instrumentation* instr, uint32_t event_type, + Thread* self, mirror::ArtMethod* method, mirror::Object* obj, + uint32_t dex_pc) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { + switch (event_type) { + case instrumentation::Instrumentation::kMethodEntered: + instr->MethodEnterEvent(self, obj, method, dex_pc); + break; + case instrumentation::Instrumentation::kMethodExited: { + JValue value; + instr->MethodExitEvent(self, obj, method, dex_pc, value); + break; + } + case instrumentation::Instrumentation::kMethodUnwind: + instr->MethodUnwindEvent(self, obj, method, dex_pc); + break; + case instrumentation::Instrumentation::kDexPcMoved: + instr->DexPcMovedEvent(self, obj, method, dex_pc); + break; + case instrumentation::Instrumentation::kFieldRead: + instr->FieldReadEvent(self, obj, method, dex_pc, nullptr); + break; + case instrumentation::Instrumentation::kFieldWritten: { + JValue value; + instr->FieldWriteEvent(self, obj, method, dex_pc, nullptr, value); + break; + } + case instrumentation::Instrumentation::kExceptionCaught: { + ThrowArithmeticExceptionDivideByZero(); + mirror::Throwable* event_exception = self->GetException(); + instr->ExceptionCaughtEvent(self, event_exception); + self->ClearException(); + break; + } + case instrumentation::Instrumentation::kBackwardBranch: + instr->BackwardBranch(self, method, dex_pc); + break; + default: + LOG(FATAL) << "Unknown instrumentation event " << event_type; + UNREACHABLE(); + } + } + + static bool DidListenerReceiveEvent(const TestInstrumentationListener& listener, + uint32_t event_type) { + switch (event_type) { + case instrumentation::Instrumentation::kMethodEntered: + return listener.received_method_enter_event; + case instrumentation::Instrumentation::kMethodExited: + return listener.received_method_exit_event; + case instrumentation::Instrumentation::kMethodUnwind: + return listener.received_method_unwind_event; + case instrumentation::Instrumentation::kDexPcMoved: + return listener.received_dex_pc_moved_event; + case instrumentation::Instrumentation::kFieldRead: + return listener.received_field_read_event; + case instrumentation::Instrumentation::kFieldWritten: + return listener.received_field_written_event; + case instrumentation::Instrumentation::kExceptionCaught: + return listener.received_exception_caught_event; + case instrumentation::Instrumentation::kBackwardBranch: + return listener.received_backward_branch_event; + default: + LOG(FATAL) << "Unknown instrumentation event " << event_type; + UNREACHABLE(); + } + } +}; + +TEST_F(InstrumentationTest, NoInstrumentation) { + ScopedObjectAccess soa(Thread::Current()); + instrumentation::Instrumentation* instr = Runtime::Current()->GetInstrumentation(); + ASSERT_NE(instr, nullptr); + + EXPECT_FALSE(instr->AreExitStubsInstalled()); + EXPECT_FALSE(instr->AreAllMethodsDeoptimized()); + EXPECT_FALSE(instr->IsActive()); + EXPECT_FALSE(instr->ShouldNotifyMethodEnterExitEvents()); + + // Test interpreter table is the default one. + EXPECT_EQ(instrumentation::kMainHandlerTable, instr->GetInterpreterHandlerTable()); + + // Check there is no registered listener. + EXPECT_FALSE(instr->HasDexPcListeners()); + EXPECT_FALSE(instr->HasExceptionCaughtListeners()); + EXPECT_FALSE(instr->HasFieldReadListeners()); + EXPECT_FALSE(instr->HasFieldWriteListeners()); + EXPECT_FALSE(instr->HasMethodEntryListeners()); + EXPECT_FALSE(instr->HasMethodExitListeners()); + EXPECT_FALSE(instr->IsActive()); +} + +// Test instrumentation listeners for each event. +TEST_F(InstrumentationTest, MethodEntryEvent) { + TestEvent(instrumentation::Instrumentation::kMethodEntered); +} + +TEST_F(InstrumentationTest, MethodExitEvent) { + TestEvent(instrumentation::Instrumentation::kMethodExited); +} + +TEST_F(InstrumentationTest, MethodUnwindEvent) { + TestEvent(instrumentation::Instrumentation::kMethodUnwind); +} + +TEST_F(InstrumentationTest, DexPcMovedEvent) { + TestEvent(instrumentation::Instrumentation::kDexPcMoved); +} + +TEST_F(InstrumentationTest, FieldReadEvent) { + TestEvent(instrumentation::Instrumentation::kFieldRead); +} + +TEST_F(InstrumentationTest, FieldWriteEvent) { + TestEvent(instrumentation::Instrumentation::kFieldWritten); +} + +TEST_F(InstrumentationTest, ExceptionCaughtEvent) { + TestEvent(instrumentation::Instrumentation::kExceptionCaught); +} + +TEST_F(InstrumentationTest, BackwardBranchEvent) { + TestEvent(instrumentation::Instrumentation::kBackwardBranch); +} + +TEST_F(InstrumentationTest, DeoptimizeDirectMethod) { + ScopedObjectAccess soa(Thread::Current()); + jobject class_loader = LoadDex("Instrumentation"); + Runtime* const runtime = Runtime::Current(); + instrumentation::Instrumentation* instr = runtime->GetInstrumentation(); + ClassLinker* class_linker = runtime->GetClassLinker(); + StackHandleScope<2> hs(soa.Self()); + Handle loader(hs.NewHandle(soa.Decode(class_loader))); + mirror::Class* klass = class_linker->FindClass(soa.Self(), "LInstrumentation;", loader); + ASSERT_TRUE(klass != nullptr); + Handle method_to_deoptimize( + hs.NewHandle(klass->FindDeclaredDirectMethod("instanceMethod", "()V"))); + ASSERT_TRUE(method_to_deoptimize.Get() != nullptr); + + EXPECT_FALSE(instr->AreAllMethodsDeoptimized()); + EXPECT_FALSE(instr->IsDeoptimized(method_to_deoptimize.Get())); + + DeoptimizeMethod(soa.Self(), method_to_deoptimize, true); + + EXPECT_FALSE(instr->AreAllMethodsDeoptimized()); + EXPECT_TRUE(instr->AreExitStubsInstalled()); + EXPECT_TRUE(instr->IsDeoptimized(method_to_deoptimize.Get())); + + constexpr const char* instrumentation_key = "DeoptimizeDirectMethod"; + UndeoptimizeMethod(soa.Self(), method_to_deoptimize, instrumentation_key, true); + + EXPECT_FALSE(instr->AreAllMethodsDeoptimized()); + EXPECT_FALSE(instr->IsDeoptimized(method_to_deoptimize.Get())); +} + +TEST_F(InstrumentationTest, FullDeoptimization) { + ScopedObjectAccess soa(Thread::Current()); + Runtime* const runtime = Runtime::Current(); + instrumentation::Instrumentation* instr = runtime->GetInstrumentation(); + EXPECT_FALSE(instr->AreAllMethodsDeoptimized()); + + constexpr const char* instrumentation_key = "FullDeoptimization"; + DeoptimizeEverything(soa.Self(), instrumentation_key, true); + + EXPECT_TRUE(instr->AreAllMethodsDeoptimized()); + EXPECT_TRUE(instr->AreExitStubsInstalled()); + + UndeoptimizeEverything(soa.Self(), instrumentation_key, true); + + EXPECT_FALSE(instr->AreAllMethodsDeoptimized()); +} + +TEST_F(InstrumentationTest, MixedDeoptimization) { + ScopedObjectAccess soa(Thread::Current()); + jobject class_loader = LoadDex("Instrumentation"); + Runtime* const runtime = Runtime::Current(); + instrumentation::Instrumentation* instr = runtime->GetInstrumentation(); + ClassLinker* class_linker = runtime->GetClassLinker(); + StackHandleScope<2> hs(soa.Self()); + Handle loader(hs.NewHandle(soa.Decode(class_loader))); + mirror::Class* klass = class_linker->FindClass(soa.Self(), "LInstrumentation;", loader); + ASSERT_TRUE(klass != nullptr); + Handle method_to_deoptimize( + hs.NewHandle(klass->FindDeclaredDirectMethod("instanceMethod", "()V"))); + ASSERT_TRUE(method_to_deoptimize.Get() != nullptr); + + EXPECT_FALSE(instr->AreAllMethodsDeoptimized()); + EXPECT_FALSE(instr->IsDeoptimized(method_to_deoptimize.Get())); + + DeoptimizeMethod(soa.Self(), method_to_deoptimize, true); + // Deoptimizing a method does not change instrumentation level. + EXPECT_EQ(Instrumentation::InstrumentationLevel::kInstrumentNothing, + GetCurrentInstrumentationLevel()); + EXPECT_FALSE(instr->AreAllMethodsDeoptimized()); + EXPECT_TRUE(instr->AreExitStubsInstalled()); + EXPECT_TRUE(instr->IsDeoptimized(method_to_deoptimize.Get())); + + constexpr const char* instrumentation_key = "MixedDeoptimization"; + DeoptimizeEverything(soa.Self(), instrumentation_key, false); + EXPECT_EQ(Instrumentation::InstrumentationLevel::kInstrumentWithInterpreter, + GetCurrentInstrumentationLevel()); + EXPECT_TRUE(instr->AreAllMethodsDeoptimized()); + EXPECT_TRUE(instr->AreExitStubsInstalled()); + EXPECT_TRUE(instr->IsDeoptimized(method_to_deoptimize.Get())); + + UndeoptimizeEverything(soa.Self(), instrumentation_key, false); + EXPECT_EQ(Instrumentation::InstrumentationLevel::kInstrumentNothing, + GetCurrentInstrumentationLevel()); + EXPECT_FALSE(instr->AreAllMethodsDeoptimized()); + EXPECT_TRUE(instr->AreExitStubsInstalled()); + EXPECT_TRUE(instr->IsDeoptimized(method_to_deoptimize.Get())); + + UndeoptimizeMethod(soa.Self(), method_to_deoptimize, instrumentation_key, true); + EXPECT_EQ(Instrumentation::InstrumentationLevel::kInstrumentNothing, + GetCurrentInstrumentationLevel()); + EXPECT_FALSE(instr->AreAllMethodsDeoptimized()); + EXPECT_FALSE(instr->IsDeoptimized(method_to_deoptimize.Get())); +} + +TEST_F(InstrumentationTest, MethodTracing_Interpreter) { + ScopedObjectAccess soa(Thread::Current()); + Runtime* const runtime = Runtime::Current(); + instrumentation::Instrumentation* instr = runtime->GetInstrumentation(); + EXPECT_FALSE(instr->AreAllMethodsDeoptimized()); + + constexpr const char* instrumentation_key = "MethodTracing"; + EnableMethodTracing(soa.Self(), instrumentation_key, true); + EXPECT_EQ(Instrumentation::InstrumentationLevel::kInstrumentWithInterpreter, + GetCurrentInstrumentationLevel()); + EXPECT_TRUE(instr->AreAllMethodsDeoptimized()); + EXPECT_TRUE(instr->AreExitStubsInstalled()); + + DisableMethodTracing(soa.Self(), instrumentation_key); + EXPECT_EQ(Instrumentation::InstrumentationLevel::kInstrumentNothing, + GetCurrentInstrumentationLevel()); + EXPECT_FALSE(instr->AreAllMethodsDeoptimized()); +} + +TEST_F(InstrumentationTest, MethodTracing_InstrumentationEntryExitStubs) { + ScopedObjectAccess soa(Thread::Current()); + Runtime* const runtime = Runtime::Current(); + instrumentation::Instrumentation* instr = runtime->GetInstrumentation(); + EXPECT_FALSE(instr->AreAllMethodsDeoptimized()); + + constexpr const char* instrumentation_key = "MethodTracing"; + EnableMethodTracing(soa.Self(), instrumentation_key, false); + EXPECT_EQ(Instrumentation::InstrumentationLevel::kInstrumentWithInstrumentationStubs, + GetCurrentInstrumentationLevel()); + EXPECT_FALSE(instr->AreAllMethodsDeoptimized()); + EXPECT_TRUE(instr->AreExitStubsInstalled()); + + DisableMethodTracing(soa.Self(), instrumentation_key); + EXPECT_EQ(Instrumentation::InstrumentationLevel::kInstrumentNothing, + GetCurrentInstrumentationLevel()); + EXPECT_FALSE(instr->AreAllMethodsDeoptimized()); +} + +// We use a macro to print the line number where the test is failing. +#define CHECK_INSTRUMENTATION(_level, _user_count) \ + do { \ + Instrumentation* const instr = Runtime::Current()->GetInstrumentation(); \ + bool interpreter = \ + (_level == Instrumentation::InstrumentationLevel::kInstrumentWithInterpreter); \ + EXPECT_EQ(_level, GetCurrentInstrumentationLevel()); \ + EXPECT_EQ(_user_count, GetInstrumentationUserCount()); \ + if (instr->IsForcedInterpretOnly()) { \ + EXPECT_TRUE(instr->InterpretOnly()); \ + } else if (interpreter) { \ + EXPECT_TRUE(instr->InterpretOnly()); \ + } else { \ + EXPECT_FALSE(instr->InterpretOnly()); \ + } \ + if (interpreter) { \ + EXPECT_TRUE(instr->AreAllMethodsDeoptimized()); \ + } else { \ + EXPECT_FALSE(instr->AreAllMethodsDeoptimized()); \ + } \ + } while (false) + +TEST_F(InstrumentationTest, ConfigureStubs_Nothing) { + CHECK_INSTRUMENTATION(Instrumentation::InstrumentationLevel::kInstrumentNothing, 0U); + + // Check no-op. + CheckConfigureStubs(kClientOneKey, Instrumentation::InstrumentationLevel::kInstrumentNothing); + CHECK_INSTRUMENTATION(Instrumentation::InstrumentationLevel::kInstrumentNothing, 0U); +} + +TEST_F(InstrumentationTest, ConfigureStubs_InstrumentationStubs) { + CHECK_INSTRUMENTATION(Instrumentation::InstrumentationLevel::kInstrumentNothing, 0U); + + // Check we can switch to instrumentation stubs + CheckConfigureStubs(kClientOneKey, + Instrumentation::InstrumentationLevel::kInstrumentWithInstrumentationStubs); + CHECK_INSTRUMENTATION(Instrumentation::InstrumentationLevel::kInstrumentWithInstrumentationStubs, + 1U); + + // Check we can disable instrumentation. + CheckConfigureStubs(kClientOneKey, Instrumentation::InstrumentationLevel::kInstrumentNothing); + CHECK_INSTRUMENTATION(Instrumentation::InstrumentationLevel::kInstrumentNothing, 0U); +} + +TEST_F(InstrumentationTest, ConfigureStubs_Interpreter) { + CHECK_INSTRUMENTATION(Instrumentation::InstrumentationLevel::kInstrumentNothing, 0U); + + // Check we can switch to interpreter + CheckConfigureStubs(kClientOneKey, + Instrumentation::InstrumentationLevel::kInstrumentWithInterpreter); + CHECK_INSTRUMENTATION(Instrumentation::InstrumentationLevel::kInstrumentWithInterpreter, 1U); + + // Check we can disable instrumentation. + CheckConfigureStubs(kClientOneKey, Instrumentation::InstrumentationLevel::kInstrumentNothing); + CHECK_INSTRUMENTATION(Instrumentation::InstrumentationLevel::kInstrumentNothing, 0U); +} + +TEST_F(InstrumentationTest, ConfigureStubs_InstrumentationStubsToInterpreter) { + CHECK_INSTRUMENTATION(Instrumentation::InstrumentationLevel::kInstrumentNothing, 0U); + + // Configure stubs with instrumentation stubs. + CheckConfigureStubs(kClientOneKey, + Instrumentation::InstrumentationLevel::kInstrumentWithInstrumentationStubs); + CHECK_INSTRUMENTATION(Instrumentation::InstrumentationLevel::kInstrumentWithInstrumentationStubs, + 1U); + + // Configure stubs with interpreter. + CheckConfigureStubs(kClientOneKey, + Instrumentation::InstrumentationLevel::kInstrumentWithInterpreter); + CHECK_INSTRUMENTATION(Instrumentation::InstrumentationLevel::kInstrumentWithInterpreter, 1U); + + // Check we can disable instrumentation. + CheckConfigureStubs(kClientOneKey, Instrumentation::InstrumentationLevel::kInstrumentNothing); + CHECK_INSTRUMENTATION(Instrumentation::InstrumentationLevel::kInstrumentNothing, 0U); +} + +TEST_F(InstrumentationTest, ConfigureStubs_InterpreterToInstrumentationStubs) { + CHECK_INSTRUMENTATION(Instrumentation::InstrumentationLevel::kInstrumentNothing, 0U); + + // Configure stubs with interpreter. + CheckConfigureStubs(kClientOneKey, + Instrumentation::InstrumentationLevel::kInstrumentWithInterpreter); + CHECK_INSTRUMENTATION(Instrumentation::InstrumentationLevel::kInstrumentWithInterpreter, 1U); + + // Configure stubs with instrumentation stubs. + CheckConfigureStubs(kClientOneKey, + Instrumentation::InstrumentationLevel::kInstrumentWithInstrumentationStubs); + CHECK_INSTRUMENTATION(Instrumentation::InstrumentationLevel::kInstrumentWithInstrumentationStubs, + 1U); + + // Check we can disable instrumentation. + CheckConfigureStubs(kClientOneKey, Instrumentation::InstrumentationLevel::kInstrumentNothing); + CHECK_INSTRUMENTATION(Instrumentation::InstrumentationLevel::kInstrumentNothing, 0U); +} + +TEST_F(InstrumentationTest, + ConfigureStubs_InstrumentationStubsToInterpreterToInstrumentationStubs) { + CHECK_INSTRUMENTATION(Instrumentation::InstrumentationLevel::kInstrumentNothing, 0U); + + // Configure stubs with instrumentation stubs. + CheckConfigureStubs(kClientOneKey, + Instrumentation::InstrumentationLevel::kInstrumentWithInstrumentationStubs); + CHECK_INSTRUMENTATION(Instrumentation::InstrumentationLevel::kInstrumentWithInstrumentationStubs, + 1U); + + // Configure stubs with interpreter. + CheckConfigureStubs(kClientOneKey, + Instrumentation::InstrumentationLevel::kInstrumentWithInterpreter); + CHECK_INSTRUMENTATION(Instrumentation::InstrumentationLevel::kInstrumentWithInterpreter, 1U); + + // Configure stubs with instrumentation stubs again. + CheckConfigureStubs(kClientOneKey, + Instrumentation::InstrumentationLevel::kInstrumentWithInstrumentationStubs); + CHECK_INSTRUMENTATION(Instrumentation::InstrumentationLevel::kInstrumentWithInstrumentationStubs, + 1U); + + // Check we can disable instrumentation. + CheckConfigureStubs(kClientOneKey, Instrumentation::InstrumentationLevel::kInstrumentNothing); + CHECK_INSTRUMENTATION(Instrumentation::InstrumentationLevel::kInstrumentNothing, 0U); +} + +TEST_F(InstrumentationTest, MultiConfigureStubs_Nothing) { + CHECK_INSTRUMENTATION(Instrumentation::InstrumentationLevel::kInstrumentNothing, 0U); + + // Check kInstrumentNothing with two clients. + CheckConfigureStubs(kClientOneKey, Instrumentation::InstrumentationLevel::kInstrumentNothing); + CHECK_INSTRUMENTATION(Instrumentation::InstrumentationLevel::kInstrumentNothing, 0U); + + CheckConfigureStubs(kClientTwoKey, Instrumentation::InstrumentationLevel::kInstrumentNothing); + CHECK_INSTRUMENTATION(Instrumentation::InstrumentationLevel::kInstrumentNothing, 0U); +} + +TEST_F(InstrumentationTest, MultiConfigureStubs_InstrumentationStubs) { + CHECK_INSTRUMENTATION(Instrumentation::InstrumentationLevel::kInstrumentNothing, 0U); + + // Configure stubs with instrumentation stubs for 1st client. + CheckConfigureStubs(kClientOneKey, + Instrumentation::InstrumentationLevel::kInstrumentWithInstrumentationStubs); + CHECK_INSTRUMENTATION(Instrumentation::InstrumentationLevel::kInstrumentWithInstrumentationStubs, + 1U); + + // Configure stubs with instrumentation stubs for 2nd client. + CheckConfigureStubs(kClientTwoKey, + Instrumentation::InstrumentationLevel::kInstrumentWithInstrumentationStubs); + CHECK_INSTRUMENTATION(Instrumentation::InstrumentationLevel::kInstrumentWithInstrumentationStubs, + 2U); + + // 1st client requests instrumentation deactivation but 2nd client still needs + // instrumentation stubs. + CheckConfigureStubs(kClientOneKey, Instrumentation::InstrumentationLevel::kInstrumentNothing); + CHECK_INSTRUMENTATION(Instrumentation::InstrumentationLevel::kInstrumentWithInstrumentationStubs, + 1U); + + // 2nd client requests instrumentation deactivation + CheckConfigureStubs(kClientTwoKey, Instrumentation::InstrumentationLevel::kInstrumentNothing); + CHECK_INSTRUMENTATION(Instrumentation::InstrumentationLevel::kInstrumentNothing, 0U); +} + +TEST_F(InstrumentationTest, MultiConfigureStubs_Interpreter) { + CHECK_INSTRUMENTATION(Instrumentation::InstrumentationLevel::kInstrumentNothing, 0U); + + // Configure stubs with interpreter for 1st client. + CheckConfigureStubs(kClientOneKey, + Instrumentation::InstrumentationLevel::kInstrumentWithInterpreter); + CHECK_INSTRUMENTATION(Instrumentation::InstrumentationLevel::kInstrumentWithInterpreter, 1U); + + // Configure stubs with interpreter for 2nd client. + CheckConfigureStubs(kClientTwoKey, + Instrumentation::InstrumentationLevel::kInstrumentWithInterpreter); + CHECK_INSTRUMENTATION(Instrumentation::InstrumentationLevel::kInstrumentWithInterpreter, 2U); + + // 1st client requests instrumentation deactivation but 2nd client still needs interpreter. + CheckConfigureStubs(kClientOneKey, Instrumentation::InstrumentationLevel::kInstrumentNothing); + CHECK_INSTRUMENTATION(Instrumentation::InstrumentationLevel::kInstrumentWithInterpreter, 1U); + + // 2nd client requests instrumentation deactivation + CheckConfigureStubs(kClientTwoKey, Instrumentation::InstrumentationLevel::kInstrumentNothing); + CHECK_INSTRUMENTATION(Instrumentation::InstrumentationLevel::kInstrumentNothing, 0U); +} + +TEST_F(InstrumentationTest, MultiConfigureStubs_InstrumentationStubsThenInterpreter) { + CHECK_INSTRUMENTATION(Instrumentation::InstrumentationLevel::kInstrumentNothing, 0U); + + // Configure stubs with instrumentation stubs for 1st client. + CheckConfigureStubs(kClientOneKey, + Instrumentation::InstrumentationLevel::kInstrumentWithInstrumentationStubs); + CHECK_INSTRUMENTATION(Instrumentation::InstrumentationLevel::kInstrumentWithInstrumentationStubs, + 1U); + + // Configure stubs with interpreter for 2nd client. + CheckConfigureStubs(kClientTwoKey, + Instrumentation::InstrumentationLevel::kInstrumentWithInterpreter); + CHECK_INSTRUMENTATION(Instrumentation::InstrumentationLevel::kInstrumentWithInterpreter, 2U); + + // 1st client requests instrumentation deactivation but 2nd client still needs interpreter. + CheckConfigureStubs(kClientOneKey, Instrumentation::InstrumentationLevel::kInstrumentNothing); + CHECK_INSTRUMENTATION(Instrumentation::InstrumentationLevel::kInstrumentWithInterpreter, 1U); + + // 2nd client requests instrumentation deactivation + CheckConfigureStubs(kClientTwoKey, Instrumentation::InstrumentationLevel::kInstrumentNothing); + CHECK_INSTRUMENTATION(Instrumentation::InstrumentationLevel::kInstrumentNothing, 0U); +} + +TEST_F(InstrumentationTest, MultiConfigureStubs_InterpreterThenInstrumentationStubs) { + CHECK_INSTRUMENTATION(Instrumentation::InstrumentationLevel::kInstrumentNothing, 0U); + + // Configure stubs with interpreter for 1st client. + CheckConfigureStubs(kClientOneKey, + Instrumentation::InstrumentationLevel::kInstrumentWithInterpreter); + CHECK_INSTRUMENTATION(Instrumentation::InstrumentationLevel::kInstrumentWithInterpreter, 1U); + + // Configure stubs with instrumentation stubs for 2nd client. + CheckConfigureStubs(kClientTwoKey, + Instrumentation::InstrumentationLevel::kInstrumentWithInstrumentationStubs); + CHECK_INSTRUMENTATION(Instrumentation::InstrumentationLevel::kInstrumentWithInterpreter, 2U); + + // 1st client requests instrumentation deactivation but 2nd client still needs + // instrumentation stubs. + CheckConfigureStubs(kClientOneKey, Instrumentation::InstrumentationLevel::kInstrumentNothing); + CHECK_INSTRUMENTATION(Instrumentation::InstrumentationLevel::kInstrumentWithInstrumentationStubs, + 1U); + + // 2nd client requests instrumentation deactivation + CheckConfigureStubs(kClientTwoKey, Instrumentation::InstrumentationLevel::kInstrumentNothing); + CHECK_INSTRUMENTATION(Instrumentation::InstrumentationLevel::kInstrumentNothing, 0U); +} + +} // namespace instrumentation +} // namespace art diff --git a/runtime/jit/jit.h b/runtime/jit/jit.h index f5ad8b837..c698cfc18 100644 --- a/runtime/jit/jit.h +++ b/runtime/jit/jit.h @@ -19,8 +19,6 @@ #include -#include "instrumentation.h" - #include "atomic.h" #include "base/macros.h" #include "base/mutex.h" diff --git a/runtime/trace.cc b/runtime/trace.cc index 9eca517dc..3b8feda2c 100644 --- a/runtime/trace.cc +++ b/runtime/trace.cc @@ -126,6 +126,9 @@ Trace* volatile Trace::the_trace_ = nullptr; pthread_t Trace::sampling_pthread_ = 0U; std::unique_ptr> Trace::temp_stack_trace_; +// The key identifying the tracer to update instrumentation. +static constexpr const char* kTracerInstrumentationKey = "Tracer"; + static mirror::ArtMethod* DecodeTraceMethodId(uint32_t tmid) { return reinterpret_cast(tmid & ~kTraceMethodActionMask); } @@ -393,7 +396,7 @@ void Trace::Start(const char* trace_filename, int trace_fd, size_t buffer_size, instrumentation::Instrumentation::kMethodExited | instrumentation::Instrumentation::kMethodUnwind); // TODO: In full-PIC mode, we don't need to fully deopt. - runtime->GetInstrumentation()->EnableMethodTracing(); + runtime->GetInstrumentation()->EnableMethodTracing(kTracerInstrumentationKey); } } } @@ -440,7 +443,7 @@ void Trace::StopTracing(bool finish_tracing, bool flush_file) { MutexLock mu(Thread::Current(), *Locks::thread_list_lock_); runtime->GetThreadList()->ForEach(ClearThreadStackTraceAndClockBase, nullptr); } else { - runtime->GetInstrumentation()->DisableMethodTracing(); + runtime->GetInstrumentation()->DisableMethodTracing(kTracerInstrumentationKey); runtime->GetInstrumentation()->RemoveListener( the_trace, instrumentation::Instrumentation::kMethodEntered | instrumentation::Instrumentation::kMethodExited | @@ -522,7 +525,7 @@ void Trace::Pause() { MutexLock mu(Thread::Current(), *Locks::thread_list_lock_); runtime->GetThreadList()->ForEach(ClearThreadStackTraceAndClockBase, nullptr); } else { - runtime->GetInstrumentation()->DisableMethodTracing(); + runtime->GetInstrumentation()->DisableMethodTracing(kTracerInstrumentationKey); runtime->GetInstrumentation()->RemoveListener(the_trace, instrumentation::Instrumentation::kMethodEntered | instrumentation::Instrumentation::kMethodExited | @@ -566,7 +569,7 @@ void Trace::Resume() { instrumentation::Instrumentation::kMethodExited | instrumentation::Instrumentation::kMethodUnwind); // TODO: In full-PIC mode, we don't need to fully deopt. - runtime->GetInstrumentation()->EnableMethodTracing(); + runtime->GetInstrumentation()->EnableMethodTracing(kTracerInstrumentationKey); } runtime->GetThreadList()->ResumeAll(); diff --git a/test/Instrumentation/Instrumentation.java b/test/Instrumentation/Instrumentation.java new file mode 100644 index 000000000..09d434213 --- /dev/null +++ b/test/Instrumentation/Instrumentation.java @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2015 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 Instrumentation { + // Direct method + private void instanceMethod() { + System.out.println("instanceMethod"); + } +} -- 2.11.0