summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--runtime/openjdkjvmti/OpenjdkJvmTi.cc2
-rw-r--r--runtime/openjdkjvmti/ti_class.cc107
-rw-r--r--runtime/openjdkjvmti/ti_class.h5
-rw-r--r--runtime/openjdkjvmti/ti_phase.cc17
-rw-r--r--runtime/openjdkjvmti/ti_thread.cc1
-rw-r--r--test/912-classes/classes.cc116
-rw-r--r--test/912-classes/expected.txt32
-rw-r--r--test/912-classes/src-ex/A.java2
-rw-r--r--test/912-classes/src-ex/C.java18
-rw-r--r--test/912-classes/src/B.java2
-rw-r--r--test/912-classes/src/Main.java60
11 files changed, 354 insertions, 8 deletions
diff --git a/runtime/openjdkjvmti/OpenjdkJvmTi.cc b/runtime/openjdkjvmti/OpenjdkJvmTi.cc
index be10378dec..33550d1365 100644
--- a/runtime/openjdkjvmti/OpenjdkJvmTi.cc
+++ b/runtime/openjdkjvmti/OpenjdkJvmTi.cc
@@ -1310,6 +1310,7 @@ extern "C" bool ArtPlugin_Initialize() {
}
PhaseUtil::Register(&gEventHandler);
ThreadUtil::Register(&gEventHandler);
+ ClassUtil::Register(&gEventHandler);
runtime->GetJavaVM()->AddEnvironmentHook(GetEnvHandler);
runtime->AddSystemWeakHolder(&gObjectTagTable);
@@ -1320,6 +1321,7 @@ extern "C" bool ArtPlugin_Initialize() {
extern "C" bool ArtPlugin_Deinitialize() {
PhaseUtil::Unregister();
ThreadUtil::Unregister();
+ ClassUtil::Unregister();
return true;
}
diff --git a/runtime/openjdkjvmti/ti_class.cc b/runtime/openjdkjvmti/ti_class.cc
index abcc84925b..450f75a813 100644
--- a/runtime/openjdkjvmti/ti_class.cc
+++ b/runtime/openjdkjvmti/ti_class.cc
@@ -31,16 +31,119 @@
#include "ti_class.h"
+#include <mutex>
+#include <unordered_set>
+
#include "art_jvmti.h"
+#include "base/macros.h"
#include "class_table-inl.h"
#include "class_linker.h"
+#include "events-inl.h"
+#include "handle.h"
+#include "jni_env_ext-inl.h"
#include "jni_internal.h"
#include "runtime.h"
+#include "runtime_callbacks.h"
+#include "ScopedLocalRef.h"
#include "scoped_thread_state_change-inl.h"
#include "thread-inl.h"
+#include "thread_list.h"
namespace openjdkjvmti {
+struct ClassCallback : public art::ClassLoadCallback {
+ void ClassLoad(art::Handle<art::mirror::Class> klass) REQUIRES_SHARED(art::Locks::mutator_lock_) {
+ if (event_handler->IsEventEnabledAnywhere(ArtJvmtiEvent::kClassLoad)) {
+ art::Thread* thread = art::Thread::Current();
+ ScopedLocalRef<jclass> jklass(thread->GetJniEnv(),
+ thread->GetJniEnv()->AddLocalReference<jclass>(klass.Get()));
+ ScopedLocalRef<jclass> jthread(
+ thread->GetJniEnv(), thread->GetJniEnv()->AddLocalReference<jclass>(thread->GetPeer()));
+ {
+ art::ScopedThreadSuspension sts(thread, art::ThreadState::kNative);
+ event_handler->DispatchEvent(thread,
+ ArtJvmtiEvent::kClassLoad,
+ reinterpret_cast<JNIEnv*>(thread->GetJniEnv()),
+ jthread.get(),
+ jklass.get());
+ }
+ AddTempClass(thread, jklass.get());
+ }
+ }
+
+ void ClassPrepare(art::Handle<art::mirror::Class> temp_klass ATTRIBUTE_UNUSED,
+ art::Handle<art::mirror::Class> klass)
+ REQUIRES_SHARED(art::Locks::mutator_lock_) {
+ if (event_handler->IsEventEnabledAnywhere(ArtJvmtiEvent::kClassPrepare)) {
+ art::Thread* thread = art::Thread::Current();
+ ScopedLocalRef<jclass> jklass(thread->GetJniEnv(),
+ thread->GetJniEnv()->AddLocalReference<jclass>(klass.Get()));
+ ScopedLocalRef<jclass> jthread(
+ thread->GetJniEnv(), thread->GetJniEnv()->AddLocalReference<jclass>(thread->GetPeer()));
+ art::ScopedThreadSuspension sts(thread, art::ThreadState::kNative);
+ event_handler->DispatchEvent(thread,
+ ArtJvmtiEvent::kClassPrepare,
+ reinterpret_cast<JNIEnv*>(thread->GetJniEnv()),
+ jthread.get(),
+ jklass.get());
+ }
+ }
+
+ void AddTempClass(art::Thread* self, jclass klass) {
+ std::unique_lock<std::mutex> mu(temp_classes_lock);
+ temp_classes.push_back(reinterpret_cast<jclass>(self->GetJniEnv()->NewGlobalRef(klass)));
+ }
+
+ void HandleTempClass(art::Handle<art::mirror::Class> temp_klass,
+ art::Handle<art::mirror::Class> klass)
+ REQUIRES_SHARED(art::Locks::mutator_lock_) {
+ std::unique_lock<std::mutex> mu(temp_classes_lock);
+ if (temp_classes.empty()) {
+ return;
+ }
+
+ art::Thread* self = art::Thread::Current();
+ for (auto it = temp_classes.begin(); it != temp_classes.end(); ++it) {
+ if (temp_klass.Get() == art::ObjPtr<art::mirror::Class>::DownCast(self->DecodeJObject(*it))) {
+ temp_classes.erase(it);
+ FixupTempClass(temp_klass, klass);
+ }
+ }
+ }
+
+ void FixupTempClass(art::Handle<art::mirror::Class> temp_klass ATTRIBUTE_UNUSED,
+ art::Handle<art::mirror::Class> klass ATTRIBUTE_UNUSED)
+ REQUIRES_SHARED(art::Locks::mutator_lock_) {
+ // TODO: Implement.
+ }
+
+ // A set of all the temp classes we have handed out. We have to fix up references to these.
+ // For simplicity, we store the temp classes as JNI global references in a vector. Normally a
+ // Prepare event will closely follow, so the vector should be small.
+ std::mutex temp_classes_lock;
+ std::vector<jclass> temp_classes;
+
+ EventHandler* event_handler = nullptr;
+};
+
+ClassCallback gClassCallback;
+
+void ClassUtil::Register(EventHandler* handler) {
+ gClassCallback.event_handler = handler;
+ art::ScopedThreadStateChange stsc(art::Thread::Current(),
+ art::ThreadState::kWaitingForDebuggerToAttach);
+ art::ScopedSuspendAll ssa("Add load callback");
+ art::Runtime::Current()->GetRuntimeCallbacks()->AddClassLoadCallback(&gClassCallback);
+}
+
+void ClassUtil::Unregister() {
+ art::ScopedThreadStateChange stsc(art::Thread::Current(),
+ art::ThreadState::kWaitingForDebuggerToAttach);
+ art::ScopedSuspendAll ssa("Remove thread callback");
+ art::Runtime* runtime = art::Runtime::Current();
+ runtime->GetRuntimeCallbacks()->RemoveClassLoadCallback(&gClassCallback);
+}
+
jvmtiError ClassUtil::GetClassFields(jvmtiEnv* env,
jclass jklass,
jint* field_count_ptr,
@@ -200,7 +303,9 @@ jvmtiError ClassUtil::GetClassSignature(jvmtiEnv* env,
}
// TODO: Support generic signature.
- *generic_ptr = nullptr;
+ if (generic_ptr != nullptr) {
+ *generic_ptr = nullptr;
+ }
// Everything is fine, release the buffers.
sig_copy.release();
diff --git a/runtime/openjdkjvmti/ti_class.h b/runtime/openjdkjvmti/ti_class.h
index 9558894deb..aa2260f035 100644
--- a/runtime/openjdkjvmti/ti_class.h
+++ b/runtime/openjdkjvmti/ti_class.h
@@ -37,8 +37,13 @@
namespace openjdkjvmti {
+class EventHandler;
+
class ClassUtil {
public:
+ static void Register(EventHandler* event_handler);
+ static void Unregister();
+
static jvmtiError GetClassFields(jvmtiEnv* env,
jclass klass,
jint* field_count_ptr,
diff --git a/runtime/openjdkjvmti/ti_phase.cc b/runtime/openjdkjvmti/ti_phase.cc
index 62c3ebe830..154406a5db 100644
--- a/runtime/openjdkjvmti/ti_phase.cc
+++ b/runtime/openjdkjvmti/ti_phase.cc
@@ -55,19 +55,23 @@ struct PhaseUtil::PhaseCallback : public art::RuntimePhaseCallback {
return soa.AddLocalReference<jthread>(soa.Self()->GetPeer());
}
- void NextRuntimePhase(RuntimePhase phase) OVERRIDE {
+ void NextRuntimePhase(RuntimePhase phase) REQUIRES_SHARED(art::Locks::mutator_lock_) OVERRIDE {
// TODO: Events.
switch (phase) {
case RuntimePhase::kInitialAgents:
PhaseUtil::current_phase_ = JVMTI_PHASE_PRIMORDIAL;
break;
case RuntimePhase::kStart:
- event_handler->DispatchEvent(nullptr, ArtJvmtiEvent::kVmStart, GetJniEnv());
- PhaseUtil::current_phase_ = JVMTI_PHASE_START;
+ {
+ art::ScopedThreadSuspension sts(art::Thread::Current(), art::ThreadState::kNative);
+ event_handler->DispatchEvent(nullptr, ArtJvmtiEvent::kVmStart, GetJniEnv());
+ PhaseUtil::current_phase_ = JVMTI_PHASE_START;
+ }
break;
case RuntimePhase::kInit:
{
ScopedLocalRef<jthread> thread(GetJniEnv(), GetCurrentJThread());
+ art::ScopedThreadSuspension sts(art::Thread::Current(), art::ThreadState::kNative);
event_handler->DispatchEvent(nullptr,
ArtJvmtiEvent::kVmInit,
GetJniEnv(),
@@ -76,8 +80,11 @@ struct PhaseUtil::PhaseCallback : public art::RuntimePhaseCallback {
}
break;
case RuntimePhase::kDeath:
- event_handler->DispatchEvent(nullptr, ArtJvmtiEvent::kVmDeath, GetJniEnv());
- PhaseUtil::current_phase_ = JVMTI_PHASE_DEAD;
+ {
+ art::ScopedThreadSuspension sts(art::Thread::Current(), art::ThreadState::kNative);
+ event_handler->DispatchEvent(nullptr, ArtJvmtiEvent::kVmDeath, GetJniEnv());
+ PhaseUtil::current_phase_ = JVMTI_PHASE_DEAD;
+ }
// TODO: Block events now.
break;
}
diff --git a/runtime/openjdkjvmti/ti_thread.cc b/runtime/openjdkjvmti/ti_thread.cc
index bf795702d5..9f81d6ba97 100644
--- a/runtime/openjdkjvmti/ti_thread.cc
+++ b/runtime/openjdkjvmti/ti_thread.cc
@@ -64,6 +64,7 @@ struct ThreadCallback : public art::ThreadLifecycleCallback, public art::Runtime
void Post(art::Thread* self, ArtJvmtiEvent type) REQUIRES_SHARED(art::Locks::mutator_lock_) {
DCHECK_EQ(self, art::Thread::Current());
ScopedLocalRef<jthread> thread(self->GetJniEnv(), GetThreadObject(self));
+ art::ScopedThreadSuspension sts(self, art::ThreadState::kNative);
event_handler->DispatchEvent(self, type, self->GetJniEnv(), thread.get());
}
diff --git a/test/912-classes/classes.cc b/test/912-classes/classes.cc
index 29eeff6694..d13436ebf6 100644
--- a/test/912-classes/classes.cc
+++ b/test/912-classes/classes.cc
@@ -20,6 +20,7 @@
#include "jni.h"
#include "openjdkjvmti/jvmti.h"
#include "ScopedLocalRef.h"
+#include "thread-inl.h"
#include "ti-agent/common_helper.h"
#include "ti-agent/common_load.h"
@@ -259,5 +260,120 @@ extern "C" JNIEXPORT jintArray JNICALL Java_Main_getClassVersion(
return int_array;
}
+static std::string GetClassName(jvmtiEnv* jenv, JNIEnv* jni_env, jclass klass) {
+ char* name;
+ jvmtiError result = jenv->GetClassSignature(klass, &name, nullptr);
+ if (result != JVMTI_ERROR_NONE) {
+ if (jni_env != nullptr) {
+ JvmtiErrorToException(jni_env, result);
+ } else {
+ printf("Failed to get class signature.\n");
+ }
+ return "";
+ }
+
+ std::string tmp(name);
+ jenv->Deallocate(reinterpret_cast<unsigned char*>(name));
+
+ return tmp;
+}
+
+static std::string GetThreadName(jvmtiEnv* jenv, JNIEnv* jni_env, jthread thread) {
+ jvmtiThreadInfo info;
+ jvmtiError result = jenv->GetThreadInfo(thread, &info);
+ if (result != JVMTI_ERROR_NONE) {
+ if (jni_env != nullptr) {
+ JvmtiErrorToException(jni_env, result);
+ } else {
+ printf("Failed to get thread name.\n");
+ }
+ return "";
+ }
+
+ std::string tmp(info.name);
+ jenv->Deallocate(reinterpret_cast<unsigned char*>(info.name));
+ jni_env->DeleteLocalRef(info.context_class_loader);
+ jni_env->DeleteLocalRef(info.thread_group);
+
+ return tmp;
+}
+
+static std::string GetThreadName(Thread* thread) {
+ std::string tmp;
+ thread->GetThreadName(tmp);
+ return tmp;
+}
+
+static void JNICALL ClassPrepareCallback(jvmtiEnv* jenv,
+ JNIEnv* jni_env,
+ jthread thread,
+ jclass klass) {
+ std::string name = GetClassName(jenv, jni_env, klass);
+ if (name == "") {
+ return;
+ }
+ std::string thread_name = GetThreadName(jenv, jni_env, thread);
+ if (thread_name == "") {
+ return;
+ }
+ std::string cur_thread_name = GetThreadName(Thread::Current());
+ printf("Prepare: %s on %s (cur=%s)\n",
+ name.c_str(),
+ thread_name.c_str(),
+ cur_thread_name.c_str());
+}
+
+static void JNICALL ClassLoadCallback(jvmtiEnv* jenv,
+ JNIEnv* jni_env,
+ jthread thread,
+ jclass klass) {
+ std::string name = GetClassName(jenv, jni_env, klass);
+ if (name == "") {
+ return;
+ }
+ std::string thread_name = GetThreadName(jenv, jni_env, thread);
+ if (thread_name == "") {
+ return;
+ }
+ printf("Load: %s on %s\n", name.c_str(), thread_name.c_str());
+}
+
+extern "C" JNIEXPORT void JNICALL Java_Main_enableClassLoadEvents(
+ JNIEnv* env, jclass Main_klass ATTRIBUTE_UNUSED, jboolean b) {
+ if (b == JNI_FALSE) {
+ jvmtiError ret = jvmti_env->SetEventNotificationMode(JVMTI_DISABLE,
+ JVMTI_EVENT_CLASS_LOAD,
+ nullptr);
+ if (JvmtiErrorToException(env, ret)) {
+ return;
+ }
+ ret = jvmti_env->SetEventNotificationMode(JVMTI_DISABLE,
+ JVMTI_EVENT_CLASS_PREPARE,
+ nullptr);
+ JvmtiErrorToException(env, ret);
+ return;
+ }
+
+ jvmtiEventCallbacks callbacks;
+ memset(&callbacks, 0, sizeof(jvmtiEventCallbacks));
+ callbacks.ClassLoad = ClassLoadCallback;
+ callbacks.ClassPrepare = ClassPrepareCallback;
+ jvmtiError ret = jvmti_env->SetEventCallbacks(&callbacks, sizeof(callbacks));
+ if (JvmtiErrorToException(env, ret)) {
+ return;
+ }
+
+ ret = jvmti_env->SetEventNotificationMode(JVMTI_ENABLE,
+ JVMTI_EVENT_CLASS_LOAD,
+ nullptr);
+ if (JvmtiErrorToException(env, ret)) {
+ return;
+ }
+ ret = jvmti_env->SetEventNotificationMode(JVMTI_ENABLE,
+ JVMTI_EVENT_CLASS_PREPARE,
+ nullptr);
+ JvmtiErrorToException(env, ret);
+}
+
} // namespace Test912Classes
} // namespace art
diff --git a/test/912-classes/expected.txt b/test/912-classes/expected.txt
index f3cb261480..4976553148 100644
--- a/test/912-classes/expected.txt
+++ b/test/912-classes/expected.txt
@@ -61,3 +61,35 @@ boot <- src+src-ex (A,B)
[class A, class B, class java.lang.Object]
[37, 0]
+
+B, false
+Load: LB; on main
+Prepare: LB; on main (cur=main)
+B, true
+Load: LB; on main
+Prepare: LB; on main (cur=main)
+C, false
+Load: LA; on main
+Prepare: LA; on main (cur=main)
+Load: LC; on main
+Prepare: LC; on main (cur=main)
+A, false
+C, true
+Load: LA; on main
+Prepare: LA; on main (cur=main)
+Load: LC; on main
+Prepare: LC; on main (cur=main)
+A, true
+A, true
+Load: LA; on main
+Prepare: LA; on main (cur=main)
+C, true
+Load: LC; on main
+Prepare: LC; on main (cur=main)
+Load: LMain$1; on main
+Prepare: LMain$1; on main (cur=main)
+C, true
+Load: LA; on TestRunner
+Prepare: LA; on TestRunner (cur=TestRunner)
+Load: LC; on TestRunner
+Prepare: LC; on TestRunner (cur=TestRunner)
diff --git a/test/912-classes/src-ex/A.java b/test/912-classes/src-ex/A.java
index 64acb2fcfe..2c43cfbd79 100644
--- a/test/912-classes/src-ex/A.java
+++ b/test/912-classes/src-ex/A.java
@@ -15,4 +15,4 @@
*/
public class A {
-} \ No newline at end of file
+}
diff --git a/test/912-classes/src-ex/C.java b/test/912-classes/src-ex/C.java
new file mode 100644
index 0000000000..97f8021486
--- /dev/null
+++ b/test/912-classes/src-ex/C.java
@@ -0,0 +1,18 @@
+/*
+ * 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.
+ */
+
+public class C extends A {
+}
diff --git a/test/912-classes/src/B.java b/test/912-classes/src/B.java
index f1458c3bca..52ce4dd58e 100644
--- a/test/912-classes/src/B.java
+++ b/test/912-classes/src/B.java
@@ -15,4 +15,4 @@
*/
public class B {
-} \ No newline at end of file
+}
diff --git a/test/912-classes/src/Main.java b/test/912-classes/src/Main.java
index 8c78d71d4d..859f80cd80 100644
--- a/test/912-classes/src/Main.java
+++ b/test/912-classes/src/Main.java
@@ -82,6 +82,10 @@ public class Main {
System.out.println();
testClassVersion();
+
+ System.out.println();
+
+ testClassEvents();
}
private static Class<?> proxyClass = null;
@@ -208,6 +212,60 @@ public class Main {
System.out.println(Arrays.toString(getClassVersion(Main.class)));
}
+ private static void testClassEvents() throws Exception {
+ ClassLoader cl = Main.class.getClassLoader();
+ while (cl.getParent() != null) {
+ cl = cl.getParent();
+ }
+ final ClassLoader boot = cl;
+
+ enableClassLoadEvents(true);
+
+ ClassLoader cl1 = create(boot, DEX1, DEX2);
+ System.out.println("B, false");
+ Class.forName("B", false, cl1);
+
+ ClassLoader cl2 = create(boot, DEX1, DEX2);
+ System.out.println("B, true");
+ Class.forName("B", true, cl2);
+
+ ClassLoader cl3 = create(boot, DEX1, DEX2);
+ System.out.println("C, false");
+ Class.forName("C", false, cl3);
+ System.out.println("A, false");
+ Class.forName("A", false, cl3);
+
+ ClassLoader cl4 = create(boot, DEX1, DEX2);
+ System.out.println("C, true");
+ Class.forName("C", true, cl4);
+ System.out.println("A, true");
+ Class.forName("A", true, cl4);
+
+ ClassLoader cl5 = create(boot, DEX1, DEX2);
+ System.out.println("A, true");
+ Class.forName("A", true, cl5);
+ System.out.println("C, true");
+ Class.forName("C", true, cl5);
+
+ Runnable r = new Runnable() {
+ @Override
+ public void run() {
+ try {
+ ClassLoader cl6 = create(boot, DEX1, DEX2);
+ System.out.println("C, true");
+ Class.forName("C", true, cl6);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+ };
+ Thread t = new Thread(r, "TestRunner");
+ t.start();
+ t.join();
+
+ enableClassLoadEvents(false);
+ }
+
private static void printClassLoaderClasses(ClassLoader cl) {
for (;;) {
if (cl == null || !cl.getClass().getName().startsWith("dalvik.system")) {
@@ -270,6 +328,8 @@ public class Main {
private static native int[] getClassVersion(Class<?> c);
+ private static native void enableClassLoadEvents(boolean b);
+
private static class TestForNonInit {
public static double dummy = Math.random(); // So it can't be compile-time initialized.
}