ART: Add Agent Thread API
Add support for RunAgentThread. Add test.
Bug: 31684593
Test: m test-art-host-run-test-931-agent-thread
Change-Id: I5deb213fb06eedc5ee78a340458cf0dff615d0ac
diff --git a/runtime/openjdkjvmti/OpenjdkJvmTi.cc b/runtime/openjdkjvmti/OpenjdkJvmTi.cc
index 32e3948..e9b7cf5 100644
--- a/runtime/openjdkjvmti/OpenjdkJvmTi.cc
+++ b/runtime/openjdkjvmti/OpenjdkJvmTi.cc
@@ -194,7 +194,7 @@
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 2bcdd8c..970cc24 100644
--- a/runtime/openjdkjvmti/ti_thread.cc
+++ b/runtime/openjdkjvmti/ti_thread.cc
@@ -443,4 +443,80 @@
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 290e9d4..5aaec58 100644
--- a/runtime/openjdkjvmti/ti_thread.h
+++ b/runtime/openjdkjvmti/ti_thread.h
@@ -49,6 +49,12 @@
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 ebf14c1..d47e62b 100644
--- a/runtime/thread.cc
+++ b/runtime/thread.cc
@@ -723,8 +723,8 @@
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 @@
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 @@
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 2b451bc..8c73634 100644
--- a/runtime/thread.h
+++ b/runtime/thread.h
@@ -158,6 +158,8 @@
// 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 @@
~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>