| /* |
| * 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 <inttypes.h> |
| #include <pthread.h> |
| #include <sched.h> |
| |
| #include "android-base/logging.h" |
| #include "android-base/macros.h" |
| #include "jni.h" |
| #include "jvmti.h" |
| #include "scoped_local_ref.h" |
| |
| // Test infrastructure |
| #include "jvmti_helper.h" |
| #include "test_env.h" |
| |
| namespace art { |
| namespace Test930AgentThread { |
| |
| struct AgentData { |
| AgentData() : main_thread(nullptr), |
| jvmti_env(nullptr), |
| priority(0) { |
| } |
| |
| jthread main_thread; |
| jvmtiEnv* jvmti_env; |
| pthread_barrier_t b; |
| jint priority; |
| }; |
| |
| static void AgentMain(jvmtiEnv* jenv, JNIEnv* env, void* arg) { |
| AgentData* data = reinterpret_cast<AgentData*>(arg); |
| |
| // Check some basics. |
| // This thread is not the main thread. |
| jthread this_thread; |
| jvmtiError this_thread_result = jenv->GetCurrentThread(&this_thread); |
| CheckJvmtiError(jenv, this_thread_result); |
| CHECK(!env->IsSameObject(this_thread, data->main_thread)); |
| |
| // The thread is a daemon. |
| jvmtiThreadInfo info; |
| jvmtiError info_result = jenv->GetThreadInfo(this_thread, &info); |
| CheckJvmtiError(jenv, info_result); |
| CHECK(info.is_daemon); |
| CheckJvmtiError(jenv, jenv->Deallocate(reinterpret_cast<unsigned char*>(info.name))); |
| if (info.thread_group != nullptr) { |
| env->DeleteLocalRef(info.thread_group); |
| } |
| if (info.context_class_loader != nullptr) { |
| env->DeleteLocalRef(info.context_class_loader); |
| } |
| |
| // The thread has the requested priority. |
| // TODO: Our thread priorities do not work on the host. |
| // CHECK_EQ(info.priority, data->priority); |
| |
| // Check further parts of the thread: |
| jint thread_count; |
| jthread* threads; |
| jvmtiError threads_result = jenv->GetAllThreads(&thread_count, &threads); |
| CheckJvmtiError(jenv, threads_result); |
| bool found = false; |
| for (jint i = 0; i != thread_count; ++i) { |
| if (env->IsSameObject(threads[i], this_thread)) { |
| found = true; |
| break; |
| } |
| } |
| CHECK(found); |
| |
| // Done, let the main thread progress. |
| int wait_result = pthread_barrier_wait(&data->b); |
| CHECK(wait_result == PTHREAD_BARRIER_SERIAL_THREAD || wait_result == 0); |
| } |
| |
| extern "C" JNIEXPORT void JNICALL Java_art_Test931_testAgentThread( |
| JNIEnv* env, [[maybe_unused]] jclass Main_klass) { |
| // Create a Thread object. |
| ScopedLocalRef<jobject> thread_name(env, env->NewStringUTF("Agent Thread")); |
| if (thread_name.get() == nullptr) { |
| return; |
| } |
| |
| ScopedLocalRef<jclass> thread_klass(env, env->FindClass("java/lang/Thread")); |
| if (thread_klass.get() == nullptr) { |
| return; |
| } |
| ScopedLocalRef<jobject> thread(env, env->AllocObject(thread_klass.get())); |
| if (thread.get() == nullptr) { |
| return; |
| } |
| |
| // Get a ThreadGroup from the current thread. We need a non-null one as we're gonna call a |
| // runtime-only constructor (so we can set priority and daemon state). |
| jvmtiThreadInfo cur_thread_info; |
| jvmtiError info_result = jvmti_env->GetThreadInfo(nullptr, &cur_thread_info); |
| if (JvmtiErrorToException(env, jvmti_env, info_result)) { |
| return; |
| } |
| CheckJvmtiError(jvmti_env, |
| jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(cur_thread_info.name))); |
| ScopedLocalRef<jobject> thread_group(env, cur_thread_info.thread_group); |
| if (cur_thread_info.context_class_loader != nullptr) { |
| env->DeleteLocalRef(cur_thread_info.context_class_loader); |
| } |
| |
| jmethodID initID = env->GetMethodID( |
| thread_klass.get(), "<init>", "(Ljava/lang/ThreadGroup;Ljava/lang/String;)V"); |
| if (initID == nullptr) { |
| return; |
| } |
| env->CallNonvirtualVoidMethod( |
| thread.get(), thread_klass.get(), initID, thread_group.get(), thread_name.get()); |
| if (env->ExceptionCheck()) { |
| return; |
| } |
| |
| jthread main_thread; |
| jvmtiError main_thread_result = jvmti_env->GetCurrentThread(&main_thread); |
| if (JvmtiErrorToException(env, jvmti_env, main_thread_result)) { |
| return; |
| } |
| |
| AgentData data; |
| data.main_thread = env->NewGlobalRef(main_thread); |
| data.jvmti_env = jvmti_env; |
| data.priority = JVMTI_THREAD_MIN_PRIORITY; |
| CHECK_EQ(0, pthread_barrier_init(&data.b, nullptr, 2)); |
| |
| jvmtiError result = jvmti_env->RunAgentThread(thread.get(), AgentMain, &data, data.priority); |
| if (JvmtiErrorToException(env, jvmti_env, result)) { |
| return; |
| } |
| |
| int wait_result = pthread_barrier_wait(&data.b); |
| CHECK(wait_result == PTHREAD_BARRIER_SERIAL_THREAD || wait_result == 0); |
| |
| // Scheduling may mean that the agent thread is put to sleep. Wait until it's dead in an effort |
| // to not unload the plugin and crash. |
| for (;;) { |
| sleep(1); |
| jint thread_state; |
| jvmtiError state_result = jvmti_env->GetThreadState(thread.get(), &thread_state); |
| if (JvmtiErrorToException(env, jvmti_env, state_result)) { |
| return; |
| } |
| if (thread_state == 0 || // Was never alive. |
| (thread_state & JVMTI_THREAD_STATE_TERMINATED) != 0) { // Was alive and died. |
| break; |
| } |
| } |
| // Yield and sleep a bit more, to give the plugin time to tear down the native thread structure. |
| sched_yield(); |
| sleep(1); |
| |
| env->DeleteGlobalRef(data.main_thread); |
| |
| pthread_barrier_destroy(&data.b); |
| } |
| |
| } // namespace Test930AgentThread |
| } // namespace art |