/* * 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 "jni.h" #include #include #include #include #include #include #include "art_method-inl.h" #include "base/mutex.h" #include "class_linker.h" #include "common_runtime_test.h" #include "handle.h" #include "handle_scope-inl.h" #include "mem_map.h" #include "mirror/class-inl.h" #include "mirror/class_loader.h" #include "obj_ptr.h" #include "runtime.h" #include "scoped_thread_state_change-inl.h" #include "ScopedLocalRef.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 { if (state == CallbackState::kBase) { state = CallbackState::kStarted; stored_self = self; } else { state = CallbackState::kWrongStart; } } void ThreadDeath(Thread* self) OVERRIDE { 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); 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_TRUE(cb_.state == CallbackState::kDied) << static_cast(cb_.state); } TEST_F(ThreadLifecycleCallbackRuntimeCallbacksTest, ThreadLifecycleCallbackAttach) { std::string error_msg; std::unique_ptr stack(MemMap::MapAnonymous("ThreadLifecycleCallback Thread", nullptr, 128 * kPageSize, // Just some small stack. PROT_READ | PROT_WRITE, false, false, &error_msg)); ASSERT_FALSE(stack == nullptr) << 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 { virtual void ClassPreDefine(const char* descriptor, Handle klass ATTRIBUTE_UNUSED, Handle class_loader ATTRIBUTE_UNUSED, const DexFile& initial_dex_file, const DexFile::ClassDef& initial_class_def ATTRIBUTE_UNUSED, /*out*/DexFile const** final_dex_file ATTRIBUTE_UNUSED, /*out*/DexFile::ClassDef const** final_class_def ATTRIBUTE_UNUSED) OVERRIDE REQUIRES_SHARED(Locks::mutator_lock_) { 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); } } // namespace art