/* * Copyright (C) 2017 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "runtime_callbacks.h" #include #include #include #include #include #include #include #include "jni.h" #include "art_method-inl.h" #include "base/mem_map.h" #include "base/mutex.h" #include "class_linker.h" #include "common_runtime_test.h" #include "dex/class_reference.h" #include "handle.h" #include "handle_scope-inl.h" #include "mirror/class-inl.h" #include "mirror/class_loader.h" #include "monitor-inl.h" #include "nativehelper/scoped_local_ref.h" #include "obj_ptr-inl.h" #include "runtime.h" #include "scoped_thread_state_change-inl.h" #include "thread-inl.h" #include "thread_list.h" #include "well_known_classes.h" namespace art { class RuntimeCallbacksTest : public CommonRuntimeTest { protected: void SetUp() override { CommonRuntimeTest::SetUp(); Thread* self = Thread::Current(); ScopedObjectAccess soa(self); ScopedThreadSuspension sts(self, kWaitingForDebuggerToAttach); ScopedSuspendAll ssa("RuntimeCallbacksTest SetUp"); AddListener(); } void TearDown() override { { Thread* self = Thread::Current(); ScopedObjectAccess soa(self); ScopedThreadSuspension sts(self, kWaitingForDebuggerToAttach); ScopedSuspendAll ssa("RuntimeCallbacksTest TearDown"); RemoveListener(); } CommonRuntimeTest::TearDown(); } virtual void AddListener() REQUIRES(Locks::mutator_lock_) = 0; virtual void RemoveListener() REQUIRES(Locks::mutator_lock_) = 0; void MakeExecutable(ObjPtr klass) REQUIRES_SHARED(Locks::mutator_lock_) { CHECK(klass != nullptr); PointerSize pointer_size = class_linker_->GetImagePointerSize(); for (auto& m : klass->GetMethods(pointer_size)) { if (!m.IsAbstract()) { class_linker_->SetEntryPointsToInterpreter(&m); } } } }; class ThreadLifecycleCallbackRuntimeCallbacksTest : public RuntimeCallbacksTest { public: static void* PthreadsCallback(void* arg ATTRIBUTE_UNUSED) { // Attach. Runtime* runtime = Runtime::Current(); CHECK(runtime->AttachCurrentThread("ThreadLifecycle test thread", true, nullptr, false)); // Detach. runtime->DetachCurrentThread(); // Die... return nullptr; } protected: void AddListener() override REQUIRES(Locks::mutator_lock_) { Runtime::Current()->GetRuntimeCallbacks()->AddThreadLifecycleCallback(&cb_); } void RemoveListener() override REQUIRES(Locks::mutator_lock_) { Runtime::Current()->GetRuntimeCallbacks()->RemoveThreadLifecycleCallback(&cb_); } enum CallbackState { kBase, kStarted, kDied, kWrongStart, kWrongDeath, }; struct Callback : public ThreadLifecycleCallback { void ThreadStart(Thread* self) override { { ScopedObjectAccess soa(self); LOG(DEBUG) << "ThreadStart callback for thread: " << self->GetThreadName(); } if (state == CallbackState::kBase) { state = CallbackState::kStarted; stored_self = self; } else { state = CallbackState::kWrongStart; } } void ThreadDeath(Thread* self) override { { ScopedObjectAccess soa(self); LOG(DEBUG) << "ThreadDeath callback for thread: " << self->GetThreadName(); } if (state == CallbackState::kStarted && self == stored_self) { state = CallbackState::kDied; } else { state = CallbackState::kWrongDeath; } } Thread* stored_self; CallbackState state = CallbackState::kBase; }; Callback cb_; }; TEST_F(ThreadLifecycleCallbackRuntimeCallbacksTest, ThreadLifecycleCallbackJava) { Thread* self = Thread::Current(); self->TransitionFromSuspendedToRunnable(); bool started = runtime_->Start(); ASSERT_TRUE(started); // Make sure the workers are done starting so we don't get callbacks for them. runtime_->WaitForThreadPoolWorkersToStart(); // The metrics reporting thread will sometimes be slow to start. Synchronously requesting a // metrics report forces us to wait until the thread has started. runtime_->RequestMetricsReport(/*synchronous=*/true); cb_.state = CallbackState::kBase; // Ignore main thread attach. { ScopedObjectAccess soa(self); MakeExecutable(soa.Decode(WellKnownClasses::java_lang_Thread)); } JNIEnv* env = self->GetJniEnv(); ScopedLocalRef thread_name(env, env->NewStringUTF("ThreadLifecycleCallback test thread")); ASSERT_TRUE(thread_name.get() != nullptr); ScopedLocalRef thread(env, env->AllocObject(WellKnownClasses::java_lang_Thread)); ASSERT_TRUE(thread.get() != nullptr); env->CallNonvirtualVoidMethod(thread.get(), WellKnownClasses::java_lang_Thread, WellKnownClasses::java_lang_Thread_init, runtime_->GetMainThreadGroup(), thread_name.get(), kMinThreadPriority, JNI_FALSE); ASSERT_FALSE(env->ExceptionCheck()); jmethodID start_id = env->GetMethodID(WellKnownClasses::java_lang_Thread, "start", "()V"); ASSERT_TRUE(start_id != nullptr); env->CallVoidMethod(thread.get(), start_id); ASSERT_FALSE(env->ExceptionCheck()); jmethodID join_id = env->GetMethodID(WellKnownClasses::java_lang_Thread, "join", "()V"); ASSERT_TRUE(join_id != nullptr); env->CallVoidMethod(thread.get(), join_id); ASSERT_FALSE(env->ExceptionCheck()); EXPECT_EQ(cb_.state, CallbackState::kDied); } TEST_F(ThreadLifecycleCallbackRuntimeCallbacksTest, ThreadLifecycleCallbackAttach) { std::string error_msg; MemMap stack = MemMap::MapAnonymous("ThreadLifecycleCallback Thread", 128 * kPageSize, // Just some small stack. PROT_READ | PROT_WRITE, /*low_4gb=*/ false, &error_msg); ASSERT_TRUE(stack.IsValid()) << error_msg; const char* reason = "ThreadLifecycleCallback test thread"; pthread_attr_t attr; CHECK_PTHREAD_CALL(pthread_attr_init, (&attr), reason); CHECK_PTHREAD_CALL(pthread_attr_setstack, (&attr, stack.Begin(), stack.Size()), reason); pthread_t pthread; CHECK_PTHREAD_CALL(pthread_create, (&pthread, &attr, &ThreadLifecycleCallbackRuntimeCallbacksTest::PthreadsCallback, this), reason); CHECK_PTHREAD_CALL(pthread_attr_destroy, (&attr), reason); CHECK_PTHREAD_CALL(pthread_join, (pthread, nullptr), "ThreadLifecycleCallback test shutdown"); // Detach is not a ThreadDeath event, so we expect to be in state Started. EXPECT_TRUE(cb_.state == CallbackState::kStarted) << static_cast(cb_.state); } class ClassLoadCallbackRuntimeCallbacksTest : public RuntimeCallbacksTest { protected: void AddListener() override REQUIRES(Locks::mutator_lock_) { Runtime::Current()->GetRuntimeCallbacks()->AddClassLoadCallback(&cb_); } void RemoveListener() override REQUIRES(Locks::mutator_lock_) { Runtime::Current()->GetRuntimeCallbacks()->RemoveClassLoadCallback(&cb_); } bool Expect(std::initializer_list list) { if (cb_.data.size() != list.size()) { PrintError(list); return false; } if (!std::equal(cb_.data.begin(), cb_.data.end(), list.begin())) { PrintError(list); return false; } return true; } void PrintError(std::initializer_list list) { LOG(ERROR) << "Expected:"; for (const char* expected : list) { LOG(ERROR) << " " << expected; } LOG(ERROR) << "Found:"; for (const auto& s : cb_.data) { LOG(ERROR) << " " << s; } } struct Callback : public ClassLoadCallback { void ClassPreDefine(const char* descriptor, Handle klass ATTRIBUTE_UNUSED, Handle class_loader ATTRIBUTE_UNUSED, const DexFile& initial_dex_file, const dex::ClassDef& initial_class_def ATTRIBUTE_UNUSED, /*out*/DexFile const** final_dex_file ATTRIBUTE_UNUSED, /*out*/dex::ClassDef const** final_class_def ATTRIBUTE_UNUSED) override REQUIRES_SHARED(Locks::mutator_lock_) { const std::string& location = initial_dex_file.GetLocation(); std::string event = std::string("PreDefine:") + descriptor + " <" + location.substr(location.rfind('/') + 1, location.size()) + ">"; data.push_back(event); } void ClassLoad(Handle klass) override REQUIRES_SHARED(Locks::mutator_lock_) { std::string tmp; std::string event = std::string("Load:") + klass->GetDescriptor(&tmp); data.push_back(event); } void ClassPrepare(Handle temp_klass, Handle klass) override REQUIRES_SHARED(Locks::mutator_lock_) { std::string tmp, tmp2; std::string event = std::string("Prepare:") + klass->GetDescriptor(&tmp) + "[" + temp_klass->GetDescriptor(&tmp2) + "]"; data.push_back(event); } std::vector data; }; Callback cb_; }; TEST_F(ClassLoadCallbackRuntimeCallbacksTest, ClassLoadCallback) { ScopedObjectAccess soa(Thread::Current()); jobject jclass_loader = LoadDex("XandY"); VariableSizedHandleScope hs(soa.Self()); Handle class_loader(hs.NewHandle( soa.Decode(jclass_loader))); const char* descriptor_y = "LY;"; Handle h_Y( hs.NewHandle(class_linker_->FindClass(soa.Self(), descriptor_y, class_loader))); ASSERT_TRUE(h_Y != nullptr); bool expect1 = Expect({ "PreDefine:LY; ", "PreDefine:LX; ", "Load:LX;", "Prepare:LX;[LX;]", "Load:LY;", "Prepare:LY;[LY;]" }); EXPECT_TRUE(expect1); cb_.data.clear(); ASSERT_TRUE(class_linker_->EnsureInitialized(Thread::Current(), h_Y, true, true)); bool expect2 = Expect({ "PreDefine:LY$Z; ", "Load:LY$Z;", "Prepare:LY$Z;[LY$Z;]" }); EXPECT_TRUE(expect2); } class RuntimeSigQuitCallbackRuntimeCallbacksTest : public RuntimeCallbacksTest { protected: void AddListener() override REQUIRES(Locks::mutator_lock_) { Runtime::Current()->GetRuntimeCallbacks()->AddRuntimeSigQuitCallback(&cb_); } void RemoveListener() override REQUIRES(Locks::mutator_lock_) { Runtime::Current()->GetRuntimeCallbacks()->RemoveRuntimeSigQuitCallback(&cb_); } struct Callback : public RuntimeSigQuitCallback { void SigQuit() override { ++sigquit_count; } size_t sigquit_count = 0; }; Callback cb_; }; TEST_F(RuntimeSigQuitCallbackRuntimeCallbacksTest, SigQuit) { // The runtime needs to be started for the signal handler. Thread* self = Thread::Current(); self->TransitionFromSuspendedToRunnable(); bool started = runtime_->Start(); ASSERT_TRUE(started); EXPECT_EQ(0u, cb_.sigquit_count); kill(getpid(), SIGQUIT); // Try a few times. for (size_t i = 0; i != 30; ++i) { if (cb_.sigquit_count == 0) { sleep(1); } else { break; } } EXPECT_EQ(1u, cb_.sigquit_count); } class RuntimePhaseCallbackRuntimeCallbacksTest : public RuntimeCallbacksTest { protected: void AddListener() override REQUIRES(Locks::mutator_lock_) { Runtime::Current()->GetRuntimeCallbacks()->AddRuntimePhaseCallback(&cb_); } void RemoveListener() override REQUIRES(Locks::mutator_lock_) { Runtime::Current()->GetRuntimeCallbacks()->RemoveRuntimePhaseCallback(&cb_); } void TearDown() override { // Bypass RuntimeCallbacksTest::TearDown, as the runtime is already gone. CommonRuntimeTest::TearDown(); } struct Callback : public RuntimePhaseCallback { void NextRuntimePhase(RuntimePhaseCallback::RuntimePhase p) override { if (p == RuntimePhaseCallback::RuntimePhase::kInitialAgents) { if (start_seen > 0 || init_seen > 0 || death_seen > 0) { LOG(FATAL) << "Unexpected order"; } ++initial_agents_seen; } else if (p == RuntimePhaseCallback::RuntimePhase::kStart) { if (init_seen > 0 || death_seen > 0) { LOG(FATAL) << "Init seen before start."; } ++start_seen; } else if (p == RuntimePhaseCallback::RuntimePhase::kInit) { ++init_seen; } else if (p == RuntimePhaseCallback::RuntimePhase::kDeath) { ++death_seen; } else { LOG(FATAL) << "Unknown phase " << static_cast(p); } } size_t initial_agents_seen = 0; size_t start_seen = 0; size_t init_seen = 0; size_t death_seen = 0; }; Callback cb_; }; TEST_F(RuntimePhaseCallbackRuntimeCallbacksTest, Phases) { ASSERT_EQ(0u, cb_.initial_agents_seen); ASSERT_EQ(0u, cb_.start_seen); ASSERT_EQ(0u, cb_.init_seen); ASSERT_EQ(0u, cb_.death_seen); // Start the runtime. { Thread* self = Thread::Current(); self->TransitionFromSuspendedToRunnable(); bool started = runtime_->Start(); ASSERT_TRUE(started); } ASSERT_EQ(0u, cb_.initial_agents_seen); ASSERT_EQ(1u, cb_.start_seen); ASSERT_EQ(1u, cb_.init_seen); ASSERT_EQ(0u, cb_.death_seen); // Delete the runtime. runtime_.reset(); ASSERT_EQ(0u, cb_.initial_agents_seen); ASSERT_EQ(1u, cb_.start_seen); ASSERT_EQ(1u, cb_.init_seen); ASSERT_EQ(1u, cb_.death_seen); } class MonitorWaitCallbacksTest : public RuntimeCallbacksTest { protected: void AddListener() override REQUIRES(Locks::mutator_lock_) { Runtime::Current()->GetRuntimeCallbacks()->AddMonitorCallback(&cb_); } void RemoveListener() override REQUIRES(Locks::mutator_lock_) { Runtime::Current()->GetRuntimeCallbacks()->RemoveMonitorCallback(&cb_); } struct Callback : public MonitorCallback { bool IsInterestingObject(ObjPtr obj) REQUIRES_SHARED(art::Locks::mutator_lock_) { if (!obj->IsClass()) { return false; } std::lock_guard lock(ref_guard_); ObjPtr k = obj->AsClass(); ClassReference test = { &k->GetDexFile(), k->GetDexClassDefIndex() }; return ref_ == test; } void SetInterestingObject(ObjPtr obj) REQUIRES_SHARED(art::Locks::mutator_lock_) { std::lock_guard lock(ref_guard_); ObjPtr k = obj->AsClass(); ref_ = { &k->GetDexFile(), k->GetDexClassDefIndex() }; } void MonitorContendedLocking(Monitor* mon ATTRIBUTE_UNUSED) override REQUIRES_SHARED(Locks::mutator_lock_) { } void MonitorContendedLocked(Monitor* mon ATTRIBUTE_UNUSED) override REQUIRES_SHARED(Locks::mutator_lock_) { } void ObjectWaitStart(Handle obj, int64_t millis ATTRIBUTE_UNUSED) override REQUIRES_SHARED(Locks::mutator_lock_) { if (IsInterestingObject(obj.Get())) { saw_wait_start_ = true; } } void MonitorWaitFinished(Monitor* m, bool timed_out ATTRIBUTE_UNUSED) override REQUIRES_SHARED(Locks::mutator_lock_) { if (IsInterestingObject(m->GetObject())) { saw_wait_finished_ = true; } } std::mutex ref_guard_; ClassReference ref_ = {nullptr, 0}; bool saw_wait_start_ = false; bool saw_wait_finished_ = false; }; Callback cb_; }; // TODO It would be good to have more tests for this but due to the multi-threaded nature of the // callbacks this is difficult. For now the run-tests 1931 & 1932 should be sufficient. TEST_F(MonitorWaitCallbacksTest, WaitUnlocked) { ASSERT_FALSE(cb_.saw_wait_finished_); ASSERT_FALSE(cb_.saw_wait_start_); { Thread* self = Thread::Current(); self->TransitionFromSuspendedToRunnable(); bool started = runtime_->Start(); ASSERT_TRUE(started); { ScopedObjectAccess soa(self); cb_.SetInterestingObject( soa.Decode(WellKnownClasses::java_util_Collections)); Monitor::Wait( self, // Just a random class soa.Decode(WellKnownClasses::java_util_Collections), /*ms=*/0, /*ns=*/0, /*interruptShouldThrow=*/false, /*why=*/kWaiting); } } ASSERT_TRUE(cb_.saw_wait_start_); ASSERT_FALSE(cb_.saw_wait_finished_); } } // namespace art