Prevent agents from delaying system daemon threads
The runtime starts several important daemon threads that are
responsible for various GC related tasks (i.e. FinalizerDaemon, etc).
The runtime cannot be considered fully started until these threads
have been started or will be started soon.
It was possible for agents to delay the startup of these daemons
arbitrarily (while calling arbitrary java code) by never leaving the
VMInit event or by never returning from the daemons ThreadStart
events. This could cause deadlocks or other errors when using some
agents, such as libjdwp.
In order to prevent these issues and more closely match other java
language runtimes we launch these threads before sending the VMInit
event and suppress their ThreadStart events. The openjdkjvmti plugin
then waits for the Daemon threads to actually begin executing before
calling the VMInit event or any other agent code. This prevents the
agents from seeing threads appear from nowhere.
Bug: 123696564
Test: while atest CtsJdwpTestCases; do; done;
Change-Id: I7e07187f3e8670f32df0490cb419585c13d81765
diff --git a/openjdkjvmti/ti_thread.cc b/openjdkjvmti/ti_thread.cc
index 051db4c..1021648 100644
--- a/openjdkjvmti/ti_thread.cc
+++ b/openjdkjvmti/ti_thread.cc
@@ -47,6 +47,7 @@
#include "mirror/class.h"
#include "mirror/object-inl.h"
#include "mirror/string.h"
+#include "mirror/throwable.h"
#include "nativehelper/scoped_local_ref.h"
#include "nativehelper/scoped_utf_chars.h"
#include "obj_ptr.h"
@@ -83,6 +84,17 @@
}
void ThreadStart(art::Thread* self) override REQUIRES_SHARED(art::Locks::mutator_lock_) {
+ // Needs to be checked first because we might start these threads before we actually send the
+ // VMInit event.
+ if (self->IsSystemDaemon()) {
+ // System daemon threads are things like the finalizer or gc thread. It would be dangerous to
+ // allow agents to get in the way of these threads starting up. These threads include things
+ // like the HeapTaskDaemon and the finalizer daemon.
+ //
+ // This event can happen during the time before VMInit or just after zygote fork. Since the
+ // second is hard to distinguish we unfortunately cannot really check the state here.
+ return;
+ }
if (!started) {
// Runtime isn't started. We only expect at most the signal handler or JIT threads to be
// started here.
@@ -132,16 +144,35 @@
gThreadCallback.Post<ArtJvmtiEvent::kThreadStart>(art::Thread::Current());
}
+
+static void WaitForSystemDaemonStart(art::Thread* self) REQUIRES_SHARED(art::Locks::mutator_lock_) {
+ {
+ art::ScopedThreadStateChange strc(self, art::kNative);
+ JNIEnv* jni = self->GetJniEnv();
+ jni->CallStaticVoidMethod(art::WellKnownClasses::java_lang_Daemons,
+ art::WellKnownClasses::java_lang_Daemons_waitForDaemonStart);
+ }
+ if (self->IsExceptionPending()) {
+ LOG(WARNING) << "Exception occured when waiting for system daemons to start: "
+ << self->GetException()->Dump();
+ self->ClearException();
+ }
+}
+
void ThreadUtil::CacheData() {
// We must have started since it is now safe to cache our data;
gThreadCallback.started = true;
- art::ScopedObjectAccess soa(art::Thread::Current());
+ art::Thread* self = art::Thread::Current();
+ art::ScopedObjectAccess soa(self);
art::ObjPtr<art::mirror::Class> thread_class =
soa.Decode<art::mirror::Class>(art::WellKnownClasses::java_lang_Thread);
CHECK(thread_class != nullptr);
context_class_loader_ = thread_class->FindDeclaredInstanceField("contextClassLoader",
"Ljava/lang/ClassLoader;");
CHECK(context_class_loader_ != nullptr);
+ // Now wait for all required system threads to come up before allowing the rest of loading to
+ // continue.
+ WaitForSystemDaemonStart(self);
}
void ThreadUtil::Unregister() {
diff --git a/runtime/runtime.cc b/runtime/runtime.cc
index 1465b14..7ecfdc7 100644
--- a/runtime/runtime.cc
+++ b/runtime/runtime.cc
@@ -869,15 +869,22 @@
GetInstructionSetString(kRuntimeISA));
}
- // Send the initialized phase event. Send it before starting daemons, as otherwise
- // sending thread events becomes complicated.
+ StartDaemonThreads();
+
+ // Make sure the environment is still clean (no lingering local refs from starting daemon
+ // threads).
+ {
+ ScopedObjectAccess soa(self);
+ self->GetJniEnv()->AssertLocalsEmpty();
+ }
+
+ // Send the initialized phase event. Send it after starting the Daemon threads so that agents
+ // cannot delay the daemon threads from starting forever.
{
ScopedObjectAccess soa(self);
callbacks_->NextRuntimePhase(RuntimePhaseCallback::RuntimePhase::kInit);
}
- StartDaemonThreads();
-
{
ScopedObjectAccess soa(self);
self->GetJniEnv()->AssertLocalsEmpty();
diff --git a/runtime/thread.cc b/runtime/thread.cc
index 4828aae..44b45cf 100644
--- a/runtime/thread.cc
+++ b/runtime/thread.cc
@@ -4258,4 +4258,12 @@
return priority;
}
+bool Thread::IsSystemDaemon() const {
+ if (GetPeer() == nullptr) {
+ return false;
+ }
+ return jni::DecodeArtField(
+ WellKnownClasses::java_lang_Thread_systemDaemon)->GetBoolean(GetPeer());
+}
+
} // namespace art
diff --git a/runtime/thread.h b/runtime/thread.h
index 7a14fd7..ec276b5 100644
--- a/runtime/thread.h
+++ b/runtime/thread.h
@@ -1230,6 +1230,8 @@
return this == jit_sensitive_thread_;
}
+ bool IsSystemDaemon() const REQUIRES_SHARED(Locks::mutator_lock_);
+
// Returns true if StrictMode events are traced for the current thread.
static bool IsSensitiveThread() {
if (is_sensitive_thread_hook_ != nullptr) {
diff --git a/runtime/well_known_classes.cc b/runtime/well_known_classes.cc
index 955a455..19fbf63 100644
--- a/runtime/well_known_classes.cc
+++ b/runtime/well_known_classes.cc
@@ -92,6 +92,7 @@
jmethodID WellKnownClasses::java_lang_ClassNotFoundException_init;
jmethodID WellKnownClasses::java_lang_Daemons_start;
jmethodID WellKnownClasses::java_lang_Daemons_stop;
+jmethodID WellKnownClasses::java_lang_Daemons_waitForDaemonStart;
jmethodID WellKnownClasses::java_lang_Double_valueOf;
jmethodID WellKnownClasses::java_lang_Float_valueOf;
jmethodID WellKnownClasses::java_lang_Integer_valueOf;
@@ -132,6 +133,7 @@
jfieldID WellKnownClasses::java_lang_Thread_name;
jfieldID WellKnownClasses::java_lang_Thread_priority;
jfieldID WellKnownClasses::java_lang_Thread_nativePeer;
+jfieldID WellKnownClasses::java_lang_Thread_systemDaemon;
jfieldID WellKnownClasses::java_lang_Thread_unparkedBeforeStart;
jfieldID WellKnownClasses::java_lang_ThreadGroup_groups;
jfieldID WellKnownClasses::java_lang_ThreadGroup_ngroups;
@@ -351,6 +353,7 @@
java_lang_Daemons_start = CacheMethod(env, java_lang_Daemons, true, "start", "()V");
java_lang_Daemons_stop = CacheMethod(env, java_lang_Daemons, true, "stop", "()V");
+ java_lang_Daemons_waitForDaemonStart = CacheMethod(env, java_lang_Daemons, true, "waitForDaemonStart", "()V");
java_lang_invoke_MethodHandles_lookup = CacheMethod(env, "java/lang/invoke/MethodHandles", true, "lookup", "()Ljava/lang/invoke/MethodHandles$Lookup;");
java_lang_invoke_MethodHandles_Lookup_findConstructor = CacheMethod(env, "java/lang/invoke/MethodHandles$Lookup", false, "findConstructor", "(Ljava/lang/Class;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/MethodHandle;");
@@ -385,6 +388,7 @@
java_lang_Thread_name = CacheField(env, java_lang_Thread, false, "name", "Ljava/lang/String;");
java_lang_Thread_priority = CacheField(env, java_lang_Thread, false, "priority", "I");
java_lang_Thread_nativePeer = CacheField(env, java_lang_Thread, false, "nativePeer", "J");
+ java_lang_Thread_systemDaemon = CacheField(env, java_lang_Thread, false, "systemDaemon", "Z");
java_lang_Thread_unparkedBeforeStart = CacheField(env, java_lang_Thread, false, "unparkedBeforeStart", "Z");
java_lang_ThreadGroup_groups = CacheField(env, java_lang_ThreadGroup, false, "groups", "[Ljava/lang/ThreadGroup;");
java_lang_ThreadGroup_ngroups = CacheField(env, java_lang_ThreadGroup, false, "ngroups", "I");
diff --git a/runtime/well_known_classes.h b/runtime/well_known_classes.h
index 872b562..3c5144f 100644
--- a/runtime/well_known_classes.h
+++ b/runtime/well_known_classes.h
@@ -101,6 +101,7 @@
static jmethodID java_lang_ClassNotFoundException_init;
static jmethodID java_lang_Daemons_start;
static jmethodID java_lang_Daemons_stop;
+ static jmethodID java_lang_Daemons_waitForDaemonStart;
static jmethodID java_lang_Double_valueOf;
static jmethodID java_lang_Float_valueOf;
static jmethodID java_lang_Integer_valueOf;
@@ -141,6 +142,7 @@
static jfieldID java_lang_Thread_name;
static jfieldID java_lang_Thread_priority;
static jfieldID java_lang_Thread_nativePeer;
+ static jfieldID java_lang_Thread_systemDaemon;
static jfieldID java_lang_Thread_unparkedBeforeStart;
static jfieldID java_lang_ThreadGroup_groups;
static jfieldID java_lang_ThreadGroup_ngroups;