diff options
| -rw-r--r-- | runtime/openjdkjvmti/OpenjdkJvmTi.cc | 2 | ||||
| -rw-r--r-- | runtime/openjdkjvmti/ti_thread.cc | 76 | ||||
| -rw-r--r-- | runtime/openjdkjvmti/ti_thread.h | 6 | ||||
| -rw-r--r-- | runtime/thread.cc | 86 | ||||
| -rw-r--r-- | runtime/thread.h | 9 | ||||
| -rw-r--r-- | test/931-agent-thread/agent_thread.cc | 132 | ||||
| -rwxr-xr-x | test/931-agent-thread/build | 17 | ||||
| -rw-r--r-- | test/931-agent-thread/expected.txt | 1 | ||||
| -rw-r--r-- | test/931-agent-thread/info.txt | 1 | ||||
| -rwxr-xr-x | test/931-agent-thread/run | 23 | ||||
| -rw-r--r-- | test/931-agent-thread/src/Main.java | 29 | ||||
| -rw-r--r-- | test/Android.bp | 1 | ||||
| -rw-r--r-- | test/Android.run-test.mk | 1 |
13 files changed, 355 insertions, 29 deletions
diff --git a/runtime/openjdkjvmti/OpenjdkJvmTi.cc b/runtime/openjdkjvmti/OpenjdkJvmTi.cc index 32e3948e3e..e9b7cf5b10 100644 --- a/runtime/openjdkjvmti/OpenjdkJvmTi.cc +++ b/runtime/openjdkjvmti/OpenjdkJvmTi.cc @@ -194,7 +194,7 @@ class JvmtiFunctions { jvmtiStartFunction proc, const void* arg, jint priority) { - return ERR(NOT_IMPLEMENTED); + return ThreadUtil::RunAgentThread(env, thread, proc, arg, priority); } static jvmtiError SetThreadLocalStorage(jvmtiEnv* env, jthread thread, const void* data) { diff --git a/runtime/openjdkjvmti/ti_thread.cc b/runtime/openjdkjvmti/ti_thread.cc index 2bcdd8cda1..970cc24f12 100644 --- a/runtime/openjdkjvmti/ti_thread.cc +++ b/runtime/openjdkjvmti/ti_thread.cc @@ -443,4 +443,80 @@ jvmtiError ThreadUtil::GetThreadLocalStorage(jvmtiEnv* env ATTRIBUTE_UNUSED, return ERR(NONE); } +struct AgentData { + const void* arg; + jvmtiStartFunction proc; + jthread thread; + JavaVM* java_vm; + jvmtiEnv* jvmti_env; + jint priority; +}; + +static void* AgentCallback(void* arg) { + std::unique_ptr<AgentData> data(reinterpret_cast<AgentData*>(arg)); + CHECK(data->thread != nullptr); + + // We already have a peer. So call our special Attach function. + art::Thread* self = art::Thread::Attach("JVMTI Agent thread", true, data->thread); + CHECK(self != nullptr); + // The name in Attach() is only for logging. Set the thread name. This is important so + // that the thread is no longer seen as starting up. + { + art::ScopedObjectAccess soa(self); + self->SetThreadName("JVMTI Agent thread"); + } + + // Release the peer. + JNIEnv* env = self->GetJniEnv(); + env->DeleteGlobalRef(data->thread); + data->thread = nullptr; + + // Run the agent code. + data->proc(data->jvmti_env, env, const_cast<void*>(data->arg)); + + // Detach the thread. + int detach_result = data->java_vm->DetachCurrentThread(); + CHECK_EQ(detach_result, 0); + + return nullptr; +} + +jvmtiError ThreadUtil::RunAgentThread(jvmtiEnv* jvmti_env, + jthread thread, + jvmtiStartFunction proc, + const void* arg, + jint priority) { + if (priority < JVMTI_THREAD_MIN_PRIORITY || priority > JVMTI_THREAD_MAX_PRIORITY) { + return ERR(INVALID_PRIORITY); + } + JNIEnv* env = art::Thread::Current()->GetJniEnv(); + if (thread == nullptr || !env->IsInstanceOf(thread, art::WellKnownClasses::java_lang_Thread)) { + return ERR(INVALID_THREAD); + } + if (proc == nullptr) { + return ERR(NULL_POINTER); + } + + std::unique_ptr<AgentData> data(new AgentData); + data->arg = arg; + data->proc = proc; + // We need a global ref for Java objects, as local refs will be invalid. + data->thread = env->NewGlobalRef(thread); + data->java_vm = art::Runtime::Current()->GetJavaVM(); + data->jvmti_env = jvmti_env; + data->priority = priority; + + pthread_t pthread; + int pthread_create_result = pthread_create(&pthread, + nullptr, + &AgentCallback, + reinterpret_cast<void*>(data.get())); + if (pthread_create_result != 0) { + return ERR(INTERNAL); + } + data.release(); + + return ERR(NONE); +} + } // namespace openjdkjvmti diff --git a/runtime/openjdkjvmti/ti_thread.h b/runtime/openjdkjvmti/ti_thread.h index 290e9d49b2..5aaec583da 100644 --- a/runtime/openjdkjvmti/ti_thread.h +++ b/runtime/openjdkjvmti/ti_thread.h @@ -49,6 +49,12 @@ class ThreadUtil { static jvmtiError SetThreadLocalStorage(jvmtiEnv* env, jthread thread, const void* data); static jvmtiError GetThreadLocalStorage(jvmtiEnv* env, jthread thread, void** data_ptr); + + static jvmtiError RunAgentThread(jvmtiEnv* env, + jthread thread, + jvmtiStartFunction proc, + const void* arg, + jint priority); }; } // namespace openjdkjvmti diff --git a/runtime/thread.cc b/runtime/thread.cc index ebf14c19b1..d47e62be15 100644 --- a/runtime/thread.cc +++ b/runtime/thread.cc @@ -723,8 +723,8 @@ bool Thread::Init(ThreadList* thread_list, JavaVMExt* java_vm, JNIEnvExt* jni_en return true; } -Thread* Thread::Attach(const char* thread_name, bool as_daemon, jobject thread_group, - bool create_peer) { +template <typename PeerAction> +Thread* Thread::Attach(const char* thread_name, bool as_daemon, PeerAction peer_action) { Runtime* runtime = Runtime::Current(); if (runtime == nullptr) { LOG(ERROR) << "Thread attaching to non-existent runtime: " << thread_name; @@ -753,32 +753,11 @@ Thread* Thread::Attach(const char* thread_name, bool as_daemon, jobject thread_g CHECK_NE(self->GetState(), kRunnable); self->SetState(kNative); - // If we're the main thread, ClassLinker won't be created until after we're attached, - // so that thread needs a two-stage attach. Regular threads don't need this hack. - // In the compiler, all threads need this hack, because no-one's going to be getting - // a native peer! - if (create_peer) { - self->CreatePeer(thread_name, as_daemon, thread_group); - if (self->IsExceptionPending()) { - // We cannot keep the exception around, as we're deleting self. Try to be helpful and log it. - { - ScopedObjectAccess soa(self); - LOG(ERROR) << "Exception creating thread peer:"; - LOG(ERROR) << self->GetException()->Dump(); - self->ClearException(); - } - runtime->GetThreadList()->Unregister(self); - // Unregister deletes self, no need to do this here. - return nullptr; - } - } else { - // These aren't necessary, but they improve diagnostics for unit tests & command-line tools. - if (thread_name != nullptr) { - self->tlsPtr_.name->assign(thread_name); - ::art::SetThreadName(thread_name); - } else if (self->GetJniEnv()->check_jni) { - LOG(WARNING) << *Thread::Current() << " attached without supplying a name"; - } + // Run the action that is acting on the peer. + if (!peer_action(self)) { + runtime->GetThreadList()->Unregister(self); + // Unregister deletes self, no need to do this here. + return nullptr; } if (VLOG_IS_ON(threads)) { @@ -799,6 +778,57 @@ Thread* Thread::Attach(const char* thread_name, bool as_daemon, jobject thread_g return self; } +Thread* Thread::Attach(const char* thread_name, + bool as_daemon, + jobject thread_group, + bool create_peer) { + auto create_peer_action = [&](Thread* self) { + // If we're the main thread, ClassLinker won't be created until after we're attached, + // so that thread needs a two-stage attach. Regular threads don't need this hack. + // In the compiler, all threads need this hack, because no-one's going to be getting + // a native peer! + if (create_peer) { + self->CreatePeer(thread_name, as_daemon, thread_group); + if (self->IsExceptionPending()) { + // We cannot keep the exception around, as we're deleting self. Try to be helpful and log it. + { + ScopedObjectAccess soa(self); + LOG(ERROR) << "Exception creating thread peer:"; + LOG(ERROR) << self->GetException()->Dump(); + self->ClearException(); + } + return false; + } + } else { + // These aren't necessary, but they improve diagnostics for unit tests & command-line tools. + if (thread_name != nullptr) { + self->tlsPtr_.name->assign(thread_name); + ::art::SetThreadName(thread_name); + } else if (self->GetJniEnv()->check_jni) { + LOG(WARNING) << *Thread::Current() << " attached without supplying a name"; + } + } + return true; + }; + return Attach(thread_name, as_daemon, create_peer_action); +} + +Thread* Thread::Attach(const char* thread_name, bool as_daemon, jobject thread_peer) { + auto set_peer_action = [&](Thread* self) { + // Install the given peer. + { + DCHECK(self == Thread::Current()); + ScopedObjectAccess soa(self); + self->tlsPtr_.opeer = soa.Decode<mirror::Object>(thread_peer).Ptr(); + } + self->GetJniEnv()->SetLongField(thread_peer, + WellKnownClasses::java_lang_Thread_nativePeer, + reinterpret_cast<jlong>(self)); + return true; + }; + return Attach(thread_name, as_daemon, set_peer_action); +} + void Thread::CreatePeer(const char* name, bool as_daemon, jobject thread_group) { Runtime* runtime = Runtime::Current(); CHECK(runtime->IsStarted()); diff --git a/runtime/thread.h b/runtime/thread.h index 2b451bcaee..8c736341c8 100644 --- a/runtime/thread.h +++ b/runtime/thread.h @@ -158,6 +158,8 @@ class Thread { // Used to implement JNI AttachCurrentThread and AttachCurrentThreadAsDaemon calls. static Thread* Attach(const char* thread_name, bool as_daemon, jobject thread_group, bool create_peer); + // Attaches the calling native thread to the runtime, returning the new native peer. + static Thread* Attach(const char* thread_name, bool as_daemon, jobject thread_peer); // Reset internal state of child thread after fork. void InitAfterFork(); @@ -1166,6 +1168,13 @@ class Thread { ~Thread() REQUIRES(!Locks::mutator_lock_, !Locks::thread_suspend_count_lock_); void Destroy(); + // Attaches the calling native thread to the runtime, returning the new native peer. + // Used to implement JNI AttachCurrentThread and AttachCurrentThreadAsDaemon calls. + template <typename PeerAction> + static Thread* Attach(const char* thread_name, + bool as_daemon, + PeerAction p); + void CreatePeer(const char* name, bool as_daemon, jobject thread_group); template<bool kTransactionActive> diff --git a/test/931-agent-thread/agent_thread.cc b/test/931-agent-thread/agent_thread.cc new file mode 100644 index 0000000000..6ace4cea68 --- /dev/null +++ b/test/931-agent-thread/agent_thread.cc @@ -0,0 +1,132 @@ +/* + * 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 "barrier.h" +#include "base/logging.h" +#include "base/macros.h" +#include "jni.h" +#include "openjdkjvmti/jvmti.h" +#include "runtime.h" +#include "ScopedLocalRef.h" +#include "thread-inl.h" +#include "well_known_classes.h" + +#include "ti-agent/common_helper.h" +#include "ti-agent/common_load.h" + +namespace art { +namespace Test930AgentThread { + +struct AgentData { + AgentData() : main_thread(nullptr), + jvmti_env(nullptr), + b(2) { + } + + jthread main_thread; + jvmtiEnv* jvmti_env; + Barrier 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); + CHECK(!JvmtiErrorToException(env, 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); + CHECK(!JvmtiErrorToException(env, info_result)); + CHECK(info.is_daemon); + + // 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); + CHECK(!JvmtiErrorToException(env, 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. + data->b.Pass(Thread::Current()); +} + +extern "C" JNIEXPORT void JNICALL Java_Main_testAgentThread( + JNIEnv* env, jclass Main_klass ATTRIBUTE_UNUSED) { + // Create a Thread object. + ScopedLocalRef<jobject> thread_name(env, + env->NewStringUTF("Agent Thread")); + if (thread_name.get() == nullptr) { + return; + } + + ScopedLocalRef<jobject> thread(env, env->AllocObject(WellKnownClasses::java_lang_Thread)); + if (thread.get() == nullptr) { + return; + } + + env->CallNonvirtualVoidMethod(thread.get(), + WellKnownClasses::java_lang_Thread, + WellKnownClasses::java_lang_Thread_init, + Runtime::Current()->GetMainThreadGroup(), + thread_name.get(), + kMinThreadPriority, + JNI_FALSE); + if (env->ExceptionCheck()) { + return; + } + + jthread main_thread; + jvmtiError main_thread_result = jvmti_env->GetCurrentThread(&main_thread); + if (JvmtiErrorToException(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; + + jvmtiError result = jvmti_env->RunAgentThread(thread.get(), AgentMain, &data, data.priority); + if (JvmtiErrorToException(env, result)) { + return; + } + + data.b.Wait(Thread::Current()); + + env->DeleteGlobalRef(data.main_thread); +} + +} // namespace Test930AgentThread +} // namespace art diff --git a/test/931-agent-thread/build b/test/931-agent-thread/build new file mode 100755 index 0000000000..898e2e54a2 --- /dev/null +++ b/test/931-agent-thread/build @@ -0,0 +1,17 @@ +#!/bin/bash +# +# Copyright 2016 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. + +./default-build "$@" --experimental agents diff --git a/test/931-agent-thread/expected.txt b/test/931-agent-thread/expected.txt new file mode 100644 index 0000000000..a965a70ed4 --- /dev/null +++ b/test/931-agent-thread/expected.txt @@ -0,0 +1 @@ +Done diff --git a/test/931-agent-thread/info.txt b/test/931-agent-thread/info.txt new file mode 100644 index 0000000000..875a5f6ec1 --- /dev/null +++ b/test/931-agent-thread/info.txt @@ -0,0 +1 @@ +Tests basic functions in the jvmti plugin. diff --git a/test/931-agent-thread/run b/test/931-agent-thread/run new file mode 100755 index 0000000000..0a8d0672f6 --- /dev/null +++ b/test/931-agent-thread/run @@ -0,0 +1,23 @@ +#!/bin/bash +# +# Copyright 2016 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. + +# This test checks whether dex files can be injected into parent classloaders. App images preload +# classes, which will make the injection moot. Turn off app images to avoid the issue. + +./default-run "$@" --experimental agents \ + --experimental runtime-plugins \ + --jvmti \ + --no-app-image diff --git a/test/931-agent-thread/src/Main.java b/test/931-agent-thread/src/Main.java new file mode 100644 index 0000000000..6471bc8437 --- /dev/null +++ b/test/931-agent-thread/src/Main.java @@ -0,0 +1,29 @@ +/* + * 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. + */ + +import java.util.Arrays; + +public class Main { + public static void main(String[] args) throws Exception { + System.loadLibrary(args[1]); + + testAgentThread(); + + System.out.println("Done"); + } + + private static native void testAgentThread(); +} diff --git a/test/Android.bp b/test/Android.bp index 965d07aa43..be5bc59e5c 100644 --- a/test/Android.bp +++ b/test/Android.bp @@ -269,6 +269,7 @@ art_cc_defaults { "927-timers/timers.cc", "928-jni-table/jni_table.cc", "929-search/search.cc", + "931-agent-thread/agent_thread.cc", ], shared_libs: [ "libbase", diff --git a/test/Android.run-test.mk b/test/Android.run-test.mk index 38b88e44a6..c8e2185891 100644 --- a/test/Android.run-test.mk +++ b/test/Android.run-test.mk @@ -310,6 +310,7 @@ TEST_ART_BROKEN_TARGET_TESTS += \ 928-jni-table \ 929-search \ 930-hello-retransform \ + 931-agent-thread \ ifneq (,$(filter target,$(TARGET_TYPES))) ART_TEST_KNOWN_BROKEN += $(call all-run-test-names,target,$(RUN_TYPES),$(PREBUILD_TYPES), \ |