/* * 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 "art_method-inl.h" #include "base/enums.h" #include "class_linker-inl.h" #include "common_runtime_test.h" #include "common_throws.h" #include "dex/dex_file.h" #include "gc/scoped_gc_critical_section.h" #include "handle_scope-inl.h" #include "jni/jni_internal.h" #include "jvalue.h" #include "runtime.h" #include "scoped_thread_state_change-inl.h" #include "interpreter/shadow_frame.h" #include "thread-inl.h" #include "thread_list.h" #include "well_known_classes.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_exit_object_event(false), received_method_unwind_event(false), received_dex_pc_moved_event(false), received_field_read_event(false), received_field_written_event(false), received_field_written_object_event(false), received_exception_thrown_event(false), received_exception_handled_event(false), received_branch_event(false), received_watched_frame_pop(false) {} virtual ~TestInstrumentationListener() {} void MethodEntered(Thread* thread ATTRIBUTE_UNUSED, Handle<mirror::Object> this_object ATTRIBUTE_UNUSED, ArtMethod* method ATTRIBUTE_UNUSED, uint32_t dex_pc ATTRIBUTE_UNUSED) override REQUIRES_SHARED(Locks::mutator_lock_) { received_method_enter_event = true; } void MethodExited(Thread* thread ATTRIBUTE_UNUSED, Handle<mirror::Object> this_object ATTRIBUTE_UNUSED, ArtMethod* method ATTRIBUTE_UNUSED, uint32_t dex_pc ATTRIBUTE_UNUSED, Handle<mirror::Object> return_value ATTRIBUTE_UNUSED) override REQUIRES_SHARED(Locks::mutator_lock_) { received_method_exit_object_event = true; } void MethodExited(Thread* thread ATTRIBUTE_UNUSED, Handle<mirror::Object> this_object ATTRIBUTE_UNUSED, ArtMethod* method ATTRIBUTE_UNUSED, uint32_t dex_pc ATTRIBUTE_UNUSED, const JValue& return_value ATTRIBUTE_UNUSED) override REQUIRES_SHARED(Locks::mutator_lock_) { received_method_exit_event = true; } void MethodUnwind(Thread* thread ATTRIBUTE_UNUSED, Handle<mirror::Object> this_object ATTRIBUTE_UNUSED, ArtMethod* method ATTRIBUTE_UNUSED, uint32_t dex_pc ATTRIBUTE_UNUSED) override REQUIRES_SHARED(Locks::mutator_lock_) { received_method_unwind_event = true; } void DexPcMoved(Thread* thread ATTRIBUTE_UNUSED, Handle<mirror::Object> this_object ATTRIBUTE_UNUSED, ArtMethod* method ATTRIBUTE_UNUSED, uint32_t new_dex_pc ATTRIBUTE_UNUSED) override REQUIRES_SHARED(Locks::mutator_lock_) { received_dex_pc_moved_event = true; } void FieldRead(Thread* thread ATTRIBUTE_UNUSED, Handle<mirror::Object> this_object ATTRIBUTE_UNUSED, ArtMethod* method ATTRIBUTE_UNUSED, uint32_t dex_pc ATTRIBUTE_UNUSED, ArtField* field ATTRIBUTE_UNUSED) override REQUIRES_SHARED(Locks::mutator_lock_) { received_field_read_event = true; } void FieldWritten(Thread* thread ATTRIBUTE_UNUSED, Handle<mirror::Object> this_object ATTRIBUTE_UNUSED, ArtMethod* method ATTRIBUTE_UNUSED, uint32_t dex_pc ATTRIBUTE_UNUSED, ArtField* field ATTRIBUTE_UNUSED, Handle<mirror::Object> field_value ATTRIBUTE_UNUSED) override REQUIRES_SHARED(Locks::mutator_lock_) { received_field_written_object_event = true; } void FieldWritten(Thread* thread ATTRIBUTE_UNUSED, Handle<mirror::Object> this_object ATTRIBUTE_UNUSED, ArtMethod* method ATTRIBUTE_UNUSED, uint32_t dex_pc ATTRIBUTE_UNUSED, ArtField* field ATTRIBUTE_UNUSED, const JValue& field_value ATTRIBUTE_UNUSED) override REQUIRES_SHARED(Locks::mutator_lock_) { received_field_written_event = true; } void ExceptionThrown(Thread* thread ATTRIBUTE_UNUSED, Handle<mirror::Throwable> exception_object ATTRIBUTE_UNUSED) override REQUIRES_SHARED(Locks::mutator_lock_) { received_exception_thrown_event = true; } void ExceptionHandled(Thread* self ATTRIBUTE_UNUSED, Handle<mirror::Throwable> throwable ATTRIBUTE_UNUSED) override REQUIRES_SHARED(Locks::mutator_lock_) { received_exception_handled_event = true; } void Branch(Thread* thread ATTRIBUTE_UNUSED, ArtMethod* method ATTRIBUTE_UNUSED, uint32_t dex_pc ATTRIBUTE_UNUSED, int32_t dex_pc_offset ATTRIBUTE_UNUSED) override REQUIRES_SHARED(Locks::mutator_lock_) { received_branch_event = true; } void WatchedFramePop(Thread* thread ATTRIBUTE_UNUSED, const ShadowFrame& frame ATTRIBUTE_UNUSED) override REQUIRES_SHARED(Locks::mutator_lock_) { received_watched_frame_pop = true; } void Reset() { received_method_enter_event = false; received_method_exit_event = false; received_method_exit_object_event = false; received_method_unwind_event = false; received_dex_pc_moved_event = false; received_field_read_event = false; received_field_written_event = false; received_field_written_object_event = false; received_exception_thrown_event = false; received_exception_handled_event = false; received_branch_event = false; received_watched_frame_pop = false; } bool received_method_enter_event; bool received_method_exit_event; bool received_method_exit_object_event; bool received_method_unwind_event; bool received_dex_pc_moved_event; bool received_field_read_event; bool received_field_written_event; bool received_field_written_object_event; bool received_exception_thrown_event; bool received_exception_handled_event; bool received_branch_event; bool received_watched_frame_pop; 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(); ScopedThreadSuspension sts(soa.Self(), kSuspended); gc::ScopedGCCriticalSection gcs(soa.Self(), gc::kGcCauseInstrumentation, gc::kCollectorTypeInstrumentation); ScopedSuspendAll ssa("Instrumentation::ConfigureStubs"); instr->ConfigureStubs(key, level); } 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) { TestEvent(instrumentation_event, nullptr, nullptr, false); } void TestEvent(uint32_t instrumentation_event, ArtMethod* event_method, ArtField* event_field, bool with_object) { ScopedObjectAccess soa(Thread::Current()); instrumentation::Instrumentation* instr = Runtime::Current()->GetInstrumentation(); TestInstrumentationListener listener; { ScopedThreadSuspension sts(soa.Self(), kSuspended); ScopedSuspendAll ssa("Add instrumentation listener"); instr->AddListener(&listener, instrumentation_event); } mirror::Object* const event_obj = nullptr; const uint32_t event_dex_pc = 0; ShadowFrameAllocaUniquePtr test_frame = CREATE_SHADOW_FRAME(0, nullptr, event_method, 0); // Check the listener is registered and is notified of the event. EXPECT_TRUE(HasEventListener(instr, instrumentation_event)); EXPECT_FALSE(DidListenerReceiveEvent(listener, instrumentation_event, with_object)); ReportEvent(instr, instrumentation_event, soa.Self(), event_method, event_obj, event_field, event_dex_pc, *test_frame); EXPECT_TRUE(DidListenerReceiveEvent(listener, instrumentation_event, with_object)); listener.Reset(); { ScopedThreadSuspension sts(soa.Self(), kSuspended); ScopedSuspendAll ssa("Remove instrumentation listener"); instr->RemoveListener(&listener, instrumentation_event); } // 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, with_object)); ReportEvent(instr, instrumentation_event, soa.Self(), event_method, event_obj, event_field, event_dex_pc, *test_frame); EXPECT_FALSE(DidListenerReceiveEvent(listener, instrumentation_event, with_object)); } void DeoptimizeMethod(Thread* self, ArtMethod* method, bool enable_deoptimization) REQUIRES_SHARED(Locks::mutator_lock_) { Runtime* runtime = Runtime::Current(); instrumentation::Instrumentation* instrumentation = runtime->GetInstrumentation(); ScopedThreadSuspension sts(self, kSuspended); gc::ScopedGCCriticalSection gcs(self, gc::kGcCauseInstrumentation, gc::kCollectorTypeInstrumentation); ScopedSuspendAll ssa("Single method deoptimization"); if (enable_deoptimization) { instrumentation->EnableDeoptimization(); } instrumentation->Deoptimize(method); } void UndeoptimizeMethod(Thread* self, ArtMethod* method, const char* key, bool disable_deoptimization) REQUIRES_SHARED(Locks::mutator_lock_) { Runtime* runtime = Runtime::Current(); instrumentation::Instrumentation* instrumentation = runtime->GetInstrumentation(); ScopedThreadSuspension sts(self, kSuspended); gc::ScopedGCCriticalSection gcs(self, gc::kGcCauseInstrumentation, gc::kCollectorTypeInstrumentation); ScopedSuspendAll ssa("Single method undeoptimization"); instrumentation->Undeoptimize(method); if (disable_deoptimization) { instrumentation->DisableDeoptimization(key); } } void DeoptimizeEverything(Thread* self, const char* key, bool enable_deoptimization) REQUIRES_SHARED(Locks::mutator_lock_) { Runtime* runtime = Runtime::Current(); instrumentation::Instrumentation* instrumentation = runtime->GetInstrumentation(); ScopedThreadSuspension sts(self, kSuspended); gc::ScopedGCCriticalSection gcs(self, gc::kGcCauseInstrumentation, gc::kCollectorTypeInstrumentation); ScopedSuspendAll ssa("Full deoptimization"); if (enable_deoptimization) { instrumentation->EnableDeoptimization(); } instrumentation->DeoptimizeEverything(key); } void UndeoptimizeEverything(Thread* self, const char* key, bool disable_deoptimization) REQUIRES_SHARED(Locks::mutator_lock_) { Runtime* runtime = Runtime::Current(); instrumentation::Instrumentation* instrumentation = runtime->GetInstrumentation(); ScopedThreadSuspension sts(self, kSuspended); gc::ScopedGCCriticalSection gcs(self, gc::kGcCauseInstrumentation, gc::kCollectorTypeInstrumentation); ScopedSuspendAll ssa("Full undeoptimization"); instrumentation->UndeoptimizeEverything(key); if (disable_deoptimization) { instrumentation->DisableDeoptimization(key); } } void EnableMethodTracing(Thread* self, const char* key, bool needs_interpreter) REQUIRES_SHARED(Locks::mutator_lock_) { Runtime* runtime = Runtime::Current(); instrumentation::Instrumentation* instrumentation = runtime->GetInstrumentation(); ScopedThreadSuspension sts(self, kSuspended); gc::ScopedGCCriticalSection gcs(self, gc::kGcCauseInstrumentation, gc::kCollectorTypeInstrumentation); ScopedSuspendAll ssa("EnableMethodTracing"); instrumentation->EnableMethodTracing(key, needs_interpreter); } void DisableMethodTracing(Thread* self, const char* key) REQUIRES_SHARED(Locks::mutator_lock_) { Runtime* runtime = Runtime::Current(); instrumentation::Instrumentation* instrumentation = runtime->GetInstrumentation(); ScopedThreadSuspension sts(self, kSuspended); gc::ScopedGCCriticalSection gcs(self, gc::kGcCauseInstrumentation, gc::kCollectorTypeInstrumentation); ScopedSuspendAll ssa("EnableMethodTracing"); instrumentation->DisableMethodTracing(key); } private: static bool HasEventListener(const instrumentation::Instrumentation* instr, uint32_t event_type) REQUIRES_SHARED(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::kExceptionThrown: return instr->HasExceptionThrownListeners(); case instrumentation::Instrumentation::kExceptionHandled: return instr->HasExceptionHandledListeners(); case instrumentation::Instrumentation::kBranch: return instr->HasBranchListeners(); case instrumentation::Instrumentation::kWatchedFramePop: return instr->HasWatchedFramePopListeners(); default: LOG(FATAL) << "Unknown instrumentation event " << event_type; UNREACHABLE(); } } static void ReportEvent(const instrumentation::Instrumentation* instr, uint32_t event_type, Thread* self, ArtMethod* method, mirror::Object* obj, ArtField* field, uint32_t dex_pc, const ShadowFrame& frame) REQUIRES_SHARED(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, field); break; case instrumentation::Instrumentation::kFieldWritten: { JValue value; instr->FieldWriteEvent(self, obj, method, dex_pc, field, value); break; } case instrumentation::Instrumentation::kExceptionThrown: { ThrowArithmeticExceptionDivideByZero(); mirror::Throwable* event_exception = self->GetException(); instr->ExceptionThrownEvent(self, event_exception); self->ClearException(); break; } case instrumentation::Instrumentation::kBranch: instr->Branch(self, method, dex_pc, -1); break; case instrumentation::Instrumentation::kWatchedFramePop: instr->WatchedFramePopped(self, frame); break; case instrumentation::Instrumentation::kExceptionHandled: { ThrowArithmeticExceptionDivideByZero(); mirror::Throwable* event_exception = self->GetException(); self->ClearException(); instr->ExceptionHandledEvent(self, event_exception); break; } default: LOG(FATAL) << "Unknown instrumentation event " << event_type; UNREACHABLE(); } } static bool DidListenerReceiveEvent(const TestInstrumentationListener& listener, uint32_t event_type, bool with_object) { switch (event_type) { case instrumentation::Instrumentation::kMethodEntered: return listener.received_method_enter_event; case instrumentation::Instrumentation::kMethodExited: return (!with_object && listener.received_method_exit_event) || (with_object && listener.received_method_exit_object_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 (!with_object && listener.received_field_written_event) || (with_object && listener.received_field_written_object_event); case instrumentation::Instrumentation::kExceptionThrown: return listener.received_exception_thrown_event; case instrumentation::Instrumentation::kExceptionHandled: return listener.received_exception_handled_event; case instrumentation::Instrumentation::kBranch: return listener.received_branch_event; case instrumentation::Instrumentation::kWatchedFramePop: return listener.received_watched_frame_pop; 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->HasExceptionThrownListeners()); EXPECT_FALSE(instr->HasExceptionHandledListeners()); 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) { ScopedObjectAccess soa(Thread::Current()); jobject class_loader = LoadDex("Instrumentation"); Runtime* const runtime = Runtime::Current(); ClassLinker* class_linker = runtime->GetClassLinker(); StackHandleScope<1> hs(soa.Self()); Handle<mirror::ClassLoader> loader(hs.NewHandle(soa.Decode<mirror::ClassLoader>(class_loader))); ObjPtr<mirror::Class> klass = class_linker->FindClass(soa.Self(), "LInstrumentation;", loader); ASSERT_TRUE(klass != nullptr); ArtMethod* method = klass->FindClassMethod("returnReference", "()Ljava/lang/Object;", kRuntimePointerSize); ASSERT_TRUE(method != nullptr); ASSERT_TRUE(method->IsDirect()); ASSERT_TRUE(method->GetDeclaringClass() == klass); TestEvent(instrumentation::Instrumentation::kMethodEntered, /*event_method=*/ method, /*event_field=*/ nullptr, /*with_object=*/ true); } TEST_F(InstrumentationTest, MethodExitObjectEvent) { ScopedObjectAccess soa(Thread::Current()); jobject class_loader = LoadDex("Instrumentation"); Runtime* const runtime = Runtime::Current(); ClassLinker* class_linker = runtime->GetClassLinker(); StackHandleScope<1> hs(soa.Self()); Handle<mirror::ClassLoader> loader(hs.NewHandle(soa.Decode<mirror::ClassLoader>(class_loader))); ObjPtr<mirror::Class> klass = class_linker->FindClass(soa.Self(), "LInstrumentation;", loader); ASSERT_TRUE(klass != nullptr); ArtMethod* method = klass->FindClassMethod("returnReference", "()Ljava/lang/Object;", kRuntimePointerSize); ASSERT_TRUE(method != nullptr); ASSERT_TRUE(method->IsDirect()); ASSERT_TRUE(method->GetDeclaringClass() == klass); TestEvent(instrumentation::Instrumentation::kMethodExited, /*event_method=*/ method, /*event_field=*/ nullptr, /*with_object=*/ true); } TEST_F(InstrumentationTest, MethodExitPrimEvent) { ScopedObjectAccess soa(Thread::Current()); jobject class_loader = LoadDex("Instrumentation"); Runtime* const runtime = Runtime::Current(); ClassLinker* class_linker = runtime->GetClassLinker(); StackHandleScope<1> hs(soa.Self()); Handle<mirror::ClassLoader> loader(hs.NewHandle(soa.Decode<mirror::ClassLoader>(class_loader))); ObjPtr<mirror::Class> klass = class_linker->FindClass(soa.Self(), "LInstrumentation;", loader); ASSERT_TRUE(klass != nullptr); ArtMethod* method = klass->FindClassMethod("returnPrimitive", "()I", kRuntimePointerSize); ASSERT_TRUE(method != nullptr); ASSERT_TRUE(method->IsDirect()); ASSERT_TRUE(method->GetDeclaringClass() == klass); TestEvent(instrumentation::Instrumentation::kMethodExited, /*event_method=*/ method, /*event_field=*/ nullptr, /*with_object=*/ false); } 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, WatchedFramePop) { TestEvent(instrumentation::Instrumentation::kWatchedFramePop); } TEST_F(InstrumentationTest, FieldWriteObjectEvent) { ScopedObjectAccess soa(Thread::Current()); jobject class_loader = LoadDex("Instrumentation"); Runtime* const runtime = Runtime::Current(); ClassLinker* class_linker = runtime->GetClassLinker(); StackHandleScope<1> hs(soa.Self()); Handle<mirror::ClassLoader> loader(hs.NewHandle(soa.Decode<mirror::ClassLoader>(class_loader))); ObjPtr<mirror::Class> klass = class_linker->FindClass(soa.Self(), "LInstrumentation;", loader); ASSERT_TRUE(klass != nullptr); ArtField* field = klass->FindDeclaredStaticField("referenceField", "Ljava/lang/Object;"); ASSERT_TRUE(field != nullptr); TestEvent(instrumentation::Instrumentation::kFieldWritten, /*event_method=*/ nullptr, /*event_field=*/ field, /*with_object=*/ true); } TEST_F(InstrumentationTest, FieldWritePrimEvent) { ScopedObjectAccess soa(Thread::Current()); jobject class_loader = LoadDex("Instrumentation"); Runtime* const runtime = Runtime::Current(); ClassLinker* class_linker = runtime->GetClassLinker(); StackHandleScope<1> hs(soa.Self()); Handle<mirror::ClassLoader> loader(hs.NewHandle(soa.Decode<mirror::ClassLoader>(class_loader))); ObjPtr<mirror::Class> klass = class_linker->FindClass(soa.Self(), "LInstrumentation;", loader); ASSERT_TRUE(klass != nullptr); ArtField* field = klass->FindDeclaredStaticField("primitiveField", "I"); ASSERT_TRUE(field != nullptr); TestEvent(instrumentation::Instrumentation::kFieldWritten, /*event_method=*/ nullptr, /*event_field=*/ field, /*with_object=*/ false); } TEST_F(InstrumentationTest, ExceptionHandledEvent) { TestEvent(instrumentation::Instrumentation::kExceptionHandled); } TEST_F(InstrumentationTest, ExceptionThrownEvent) { TestEvent(instrumentation::Instrumentation::kExceptionThrown); } TEST_F(InstrumentationTest, BranchEvent) { TestEvent(instrumentation::Instrumentation::kBranch); } 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<1> hs(soa.Self()); Handle<mirror::ClassLoader> loader(hs.NewHandle(soa.Decode<mirror::ClassLoader>(class_loader))); ObjPtr<mirror::Class> klass = class_linker->FindClass(soa.Self(), "LInstrumentation;", loader); ASSERT_TRUE(klass != nullptr); ArtMethod* method_to_deoptimize = klass->FindClassMethod("instanceMethod", "()V", kRuntimePointerSize); ASSERT_TRUE(method_to_deoptimize != nullptr); ASSERT_TRUE(method_to_deoptimize->IsDirect()); ASSERT_TRUE(method_to_deoptimize->GetDeclaringClass() == klass); EXPECT_FALSE(instr->AreAllMethodsDeoptimized()); EXPECT_FALSE(instr->IsDeoptimized(method_to_deoptimize)); DeoptimizeMethod(soa.Self(), method_to_deoptimize, true); EXPECT_FALSE(instr->AreAllMethodsDeoptimized()); EXPECT_TRUE(instr->AreExitStubsInstalled()); EXPECT_TRUE(instr->IsDeoptimized(method_to_deoptimize)); 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)); } 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<1> hs(soa.Self()); Handle<mirror::ClassLoader> loader(hs.NewHandle(soa.Decode<mirror::ClassLoader>(class_loader))); ObjPtr<mirror::Class> klass = class_linker->FindClass(soa.Self(), "LInstrumentation;", loader); ASSERT_TRUE(klass != nullptr); ArtMethod* method_to_deoptimize = klass->FindClassMethod("instanceMethod", "()V", kRuntimePointerSize); ASSERT_TRUE(method_to_deoptimize != nullptr); ASSERT_TRUE(method_to_deoptimize->IsDirect()); ASSERT_TRUE(method_to_deoptimize->GetDeclaringClass() == klass); EXPECT_FALSE(instr->AreAllMethodsDeoptimized()); EXPECT_FALSE(instr->IsDeoptimized(method_to_deoptimize)); 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)); 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)); 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)); 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)); } 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); // Make sure we are still interpreter since going from interpreter->instrumentation is dangerous. 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_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::kInstrumentWithInterpreter, 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. Since we already got interpreter stubs we need to stay there. 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); } } // namespace instrumentation } // namespace art