diff options
| -rw-r--r-- | runtime/openjdkjvmti/OpenjdkJvmTi.cc | 2 | ||||
| -rw-r--r-- | runtime/openjdkjvmti/ti_class.cc | 107 | ||||
| -rw-r--r-- | runtime/openjdkjvmti/ti_class.h | 5 | ||||
| -rw-r--r-- | runtime/openjdkjvmti/ti_phase.cc | 17 | ||||
| -rw-r--r-- | runtime/openjdkjvmti/ti_thread.cc | 1 | ||||
| -rw-r--r-- | test/912-classes/classes.cc | 116 | ||||
| -rw-r--r-- | test/912-classes/expected.txt | 32 | ||||
| -rw-r--r-- | test/912-classes/src-ex/A.java | 2 | ||||
| -rw-r--r-- | test/912-classes/src-ex/C.java | 18 | ||||
| -rw-r--r-- | test/912-classes/src/B.java | 2 | ||||
| -rw-r--r-- | test/912-classes/src/Main.java | 60 |
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. } |