diff options
40 files changed, 1157 insertions, 26 deletions
diff --git a/runtime/Android.bp b/runtime/Android.bp index 86019bf71c..dd91249e25 100644 --- a/runtime/Android.bp +++ b/runtime/Android.bp @@ -184,6 +184,7 @@ cc_defaults { "reference_table.cc", "reflection.cc", "runtime.cc", + "runtime_callbacks.cc", "runtime_options.cc", "signal_catcher.cc", "stack.cc", @@ -563,6 +564,7 @@ art_cc_test { "parsed_options_test.cc", "prebuilt_tools_test.cc", "reference_table_test.cc", + "runtime_callbacks_test.cc", "thread_pool_test.cc", "transaction_test.cc", "type_lookup_table_test.cc", diff --git a/runtime/base/mutex.cc b/runtime/base/mutex.cc index 9116097604..fcc92dd96d 100644 --- a/runtime/base/mutex.cc +++ b/runtime/base/mutex.cc @@ -46,6 +46,7 @@ Mutex* Locks::deoptimization_lock_ = nullptr; ReaderWriterMutex* Locks::heap_bitmap_lock_ = nullptr; Mutex* Locks::instrument_entrypoints_lock_ = nullptr; Mutex* Locks::intern_table_lock_ = nullptr; +Mutex* Locks::jni_function_table_lock_ = nullptr; Mutex* Locks::jni_libraries_lock_ = nullptr; Mutex* Locks::logging_lock_ = nullptr; Mutex* Locks::mem_maps_lock_ = nullptr; @@ -61,6 +62,7 @@ Mutex* Locks::reference_queue_finalizer_references_lock_ = nullptr; Mutex* Locks::reference_queue_phantom_references_lock_ = nullptr; Mutex* Locks::reference_queue_soft_references_lock_ = nullptr; Mutex* Locks::reference_queue_weak_references_lock_ = nullptr; +ReaderWriterMutex* Locks::runtime_callbacks_lock_ = nullptr; Mutex* Locks::runtime_shutdown_lock_ = nullptr; Mutex* Locks::cha_lock_ = nullptr; Mutex* Locks::thread_list_lock_ = nullptr; @@ -957,6 +959,7 @@ void Locks::Init() { DCHECK(verifier_deps_lock_ != nullptr); DCHECK(host_dlopen_handles_lock_ != nullptr); DCHECK(intern_table_lock_ != nullptr); + DCHECK(jni_function_table_lock_ != nullptr); DCHECK(jni_libraries_lock_ != nullptr); DCHECK(logging_lock_ != nullptr); DCHECK(mutator_lock_ != nullptr); @@ -967,6 +970,7 @@ void Locks::Init() { DCHECK(trace_lock_ != nullptr); DCHECK(unexpected_signal_lock_ != nullptr); DCHECK(dex_lock_ != nullptr); + DCHECK(runtime_callbacks_lock_ != nullptr); } else { // Create global locks in level order from highest lock level to lowest. LockLevel current_lock_level = kInstrumentEntrypointsLock; @@ -998,6 +1002,10 @@ void Locks::Init() { DCHECK(runtime_shutdown_lock_ == nullptr); runtime_shutdown_lock_ = new Mutex("runtime shutdown lock", current_lock_level); + UPDATE_CURRENT_LOCK_LEVEL(kRuntimeCallbacksLock); + DCHECK(runtime_callbacks_lock_ == nullptr); + runtime_callbacks_lock_ = new ReaderWriterMutex("runtime callbacks lock", current_lock_level); + UPDATE_CURRENT_LOCK_LEVEL(kProfilerLock); DCHECK(profiler_lock_ == nullptr); profiler_lock_ = new Mutex("profiler lock", current_lock_level); @@ -1098,6 +1106,10 @@ void Locks::Init() { DCHECK(jni_weak_globals_lock_ == nullptr); jni_weak_globals_lock_ = new Mutex("JNI weak global reference table lock", current_lock_level); + UPDATE_CURRENT_LOCK_LEVEL(kJniFunctionTableLock); + DCHECK(jni_function_table_lock_ == nullptr); + jni_function_table_lock_ = new Mutex("JNI function table lock", current_lock_level); + UPDATE_CURRENT_LOCK_LEVEL(kAbortLock); DCHECK(abort_lock_ == nullptr); abort_lock_ = new Mutex("abort lock", current_lock_level, true); diff --git a/runtime/base/mutex.h b/runtime/base/mutex.h index 2adeb8cc97..3867b1b810 100644 --- a/runtime/base/mutex.h +++ b/runtime/base/mutex.h @@ -68,6 +68,7 @@ enum LockLevel { kRosAllocBulkFreeLock, kMarkSweepMarkStackLock, kTransactionLogLock, + kJniFunctionTableLock, kJniWeakGlobalsLock, kJniGlobalsLock, kReferenceQueueSoftReferencesLock, @@ -111,6 +112,7 @@ enum LockLevel { kJdwpEventListLock, kJdwpAttachLock, kJdwpStartLock, + kRuntimeCallbacksLock, kRuntimeShutdownLock, kTraceLock, kHeapBitmapLock, @@ -615,8 +617,11 @@ class Locks { // Guards shutdown of the runtime. static Mutex* runtime_shutdown_lock_ ACQUIRED_AFTER(heap_bitmap_lock_); + // Guards accesses to runtime callback lists. + static ReaderWriterMutex* runtime_callbacks_lock_ ACQUIRED_AFTER(runtime_shutdown_lock_); + // Guards background profiler global state. - static Mutex* profiler_lock_ ACQUIRED_AFTER(runtime_shutdown_lock_); + static Mutex* profiler_lock_ ACQUIRED_AFTER(runtime_callbacks_lock_); // Guards trace (ie traceview) requests. static Mutex* trace_lock_ ACQUIRED_AFTER(profiler_lock_); @@ -698,8 +703,11 @@ class Locks { // Guard accesses to the JNI Weak Global Reference table. static Mutex* jni_weak_globals_lock_ ACQUIRED_AFTER(jni_globals_lock_); + // Guard accesses to the JNI function table override. + static Mutex* jni_function_table_lock_ ACQUIRED_AFTER(jni_weak_globals_lock_); + // Have an exclusive aborting thread. - static Mutex* abort_lock_ ACQUIRED_AFTER(jni_weak_globals_lock_); + static Mutex* abort_lock_ ACQUIRED_AFTER(jni_function_table_lock_); // Allow mutual exclusion when manipulating Thread::suspend_count_. // TODO: Does the trade-off of a per-thread lock make sense? diff --git a/runtime/class_linker.h b/runtime/class_linker.h index 823848b3bc..9b98671cb4 100644 --- a/runtime/class_linker.h +++ b/runtime/class_linker.h @@ -649,6 +649,10 @@ class ClassLinker { ClassTable* ClassTableForClassLoader(ObjPtr<mirror::ClassLoader> class_loader) REQUIRES_SHARED(Locks::mutator_lock_); + void AppendToBootClassPath(Thread* self, const DexFile& dex_file) + REQUIRES_SHARED(Locks::mutator_lock_) + REQUIRES(!Locks::dex_lock_); + struct DexCacheData { // Weak root to the DexCache. Note: Do not decode this unnecessarily or else class unloading may // not work properly. @@ -744,9 +748,6 @@ class ClassLinker { REQUIRES_SHARED(Locks::mutator_lock_) REQUIRES(!Locks::dex_lock_, !Roles::uninterruptible_); - void AppendToBootClassPath(Thread* self, const DexFile& dex_file) - REQUIRES_SHARED(Locks::mutator_lock_) - REQUIRES(!Locks::dex_lock_); void AppendToBootClassPath(const DexFile& dex_file, Handle<mirror::DexCache> dex_cache) REQUIRES_SHARED(Locks::mutator_lock_) REQUIRES(!Locks::dex_lock_); diff --git a/runtime/debugger.cc b/runtime/debugger.cc index 006476bafc..6da7e3a94e 100644 --- a/runtime/debugger.cc +++ b/runtime/debugger.cc @@ -320,6 +320,8 @@ size_t Dbg::field_write_event_ref_count_ = 0; size_t Dbg::exception_catch_event_ref_count_ = 0; uint32_t Dbg::instrumentation_events_ = 0; +Dbg::DbgThreadLifecycleCallback Dbg::thread_lifecycle_callback_; + // Breakpoints. static std::vector<Breakpoint> gBreakpoints GUARDED_BY(Locks::breakpoint_lock_); @@ -5135,4 +5137,12 @@ void Dbg::VisitRoots(RootVisitor* visitor) { } } +void Dbg::DbgThreadLifecycleCallback::ThreadStart(Thread* self) { + Dbg::PostThreadStart(self); +} + +void Dbg::DbgThreadLifecycleCallback::ThreadDeath(Thread* self) { + Dbg::PostThreadDeath(self); +} + } // namespace art diff --git a/runtime/debugger.h b/runtime/debugger.h index 3b4a5e16b0..01359907d9 100644 --- a/runtime/debugger.h +++ b/runtime/debugger.h @@ -502,10 +502,6 @@ class Dbg { REQUIRES_SHARED(Locks::mutator_lock_); static void PostException(mirror::Throwable* exception) REQUIRES_SHARED(Locks::mutator_lock_); - static void PostThreadStart(Thread* t) - REQUIRES_SHARED(Locks::mutator_lock_); - static void PostThreadDeath(Thread* t) - REQUIRES_SHARED(Locks::mutator_lock_); static void PostClassPrepare(mirror::Class* c) REQUIRES_SHARED(Locks::mutator_lock_); @@ -707,6 +703,10 @@ class Dbg { return instrumentation_events_; } + static ThreadLifecycleCallback* GetThreadLifecycleCallback() { + return &thread_lifecycle_callback_; + } + private: static void ExecuteMethodWithoutPendingException(ScopedObjectAccess& soa, DebugInvokeReq* pReq) REQUIRES_SHARED(Locks::mutator_lock_); @@ -725,6 +725,11 @@ class Dbg { REQUIRES(!Locks::thread_list_lock_) REQUIRES_SHARED(Locks::mutator_lock_); static void DdmBroadcast(bool connect) REQUIRES_SHARED(Locks::mutator_lock_); + + static void PostThreadStart(Thread* t) + REQUIRES_SHARED(Locks::mutator_lock_); + static void PostThreadDeath(Thread* t) + REQUIRES_SHARED(Locks::mutator_lock_); static void PostThreadStartOrStop(Thread*, uint32_t) REQUIRES_SHARED(Locks::mutator_lock_); @@ -789,6 +794,14 @@ class Dbg { static size_t exception_catch_event_ref_count_ GUARDED_BY(Locks::deoptimization_lock_); static uint32_t instrumentation_events_ GUARDED_BY(Locks::mutator_lock_); + class DbgThreadLifecycleCallback : public ThreadLifecycleCallback { + public: + void ThreadStart(Thread* self) OVERRIDE REQUIRES_SHARED(Locks::mutator_lock_); + void ThreadDeath(Thread* self) OVERRIDE REQUIRES_SHARED(Locks::mutator_lock_); + }; + + static DbgThreadLifecycleCallback thread_lifecycle_callback_; + DISALLOW_COPY_AND_ASSIGN(Dbg); }; diff --git a/runtime/gc/collector/concurrent_copying.cc b/runtime/gc/collector/concurrent_copying.cc index 7b86339663..6044053b4f 100644 --- a/runtime/gc/collector/concurrent_copying.cc +++ b/runtime/gc/collector/concurrent_copying.cc @@ -846,7 +846,7 @@ void ConcurrentCopying::IssueEmptyCheckpoint() { // Some threads in 'runnable_thread_ids' are probably stuck. Try to dump their stacks. // Avoid using ThreadList::Dump() initially because it is likely to get stuck as well. { - ReaderMutexLock mu0(self, *Locks::mutator_lock_); + ScopedObjectAccess soa(self); MutexLock mu1(self, *Locks::thread_list_lock_); for (Thread* thread : thread_list->GetList()) { uint32_t tid = thread->GetThreadId(); diff --git a/runtime/jni_env_ext.cc b/runtime/jni_env_ext.cc index 5a3fafa726..0148a1c3b0 100644 --- a/runtime/jni_env_ext.cc +++ b/runtime/jni_env_ext.cc @@ -29,6 +29,7 @@ #include "mirror/object-inl.h" #include "nth_caller_visitor.h" #include "thread-inl.h" +#include "thread_list.h" namespace art { @@ -37,6 +38,8 @@ using android::base::StringPrintf; static constexpr size_t kMonitorsInitial = 32; // Arbitrary. static constexpr size_t kMonitorsMax = 4096; // Arbitrary sanity check. +const JNINativeInterface* JNIEnvExt::table_override_ = nullptr; + // Checking "locals" requires the mutator lock, but at creation time we're really only interested // in validity, which isn't changing. To avoid grabbing the mutator lock, factored out and tagged // with NO_THREAD_SAFETY_ANALYSIS. @@ -78,10 +81,10 @@ JNIEnvExt::JNIEnvExt(Thread* self_in, JavaVMExt* vm_in, std::string* error_msg) runtime_deleted(false), critical(0), monitors("monitors", kMonitorsInitial, kMonitorsMax) { - functions = unchecked_functions = GetJniNativeInterface(); - if (vm->IsCheckJniEnabled()) { - SetCheckJniEnabled(true); - } + MutexLock mu(Thread::Current(), *Locks::jni_function_table_lock_); + check_jni = vm->IsCheckJniEnabled(); + functions = GetFunctionTable(check_jni); + unchecked_functions = GetJniNativeInterface(); } void JNIEnvExt::SetFunctionsToRuntimeShutdownFunctions() { @@ -107,7 +110,12 @@ void JNIEnvExt::DeleteLocalRef(jobject obj) { void JNIEnvExt::SetCheckJniEnabled(bool enabled) { check_jni = enabled; - functions = enabled ? GetCheckJniNativeInterface() : GetJniNativeInterface(); + MutexLock mu(Thread::Current(), *Locks::jni_function_table_lock_); + functions = GetFunctionTable(enabled); + // Check whether this is a no-op because of override. + if (enabled && JNIEnvExt::table_override_ != nullptr) { + LOG(WARNING) << "Enabling CheckJNI after a JNIEnv function table override is not functional."; + } } void JNIEnvExt::DumpReferenceTables(std::ostream& os) { @@ -269,4 +277,33 @@ void JNIEnvExt::CheckNoHeldMonitors() { } } +static void ThreadResetFunctionTable(Thread* thread, void* arg ATTRIBUTE_UNUSED) + REQUIRES(Locks::jni_function_table_lock_) { + JNIEnvExt* env = thread->GetJniEnv(); + bool check_jni = env->check_jni; + env->functions = JNIEnvExt::GetFunctionTable(check_jni); +} + +void JNIEnvExt::SetTableOverride(const JNINativeInterface* table_override) { + MutexLock mu(Thread::Current(), *Locks::thread_list_lock_); + MutexLock mu2(Thread::Current(), *Locks::jni_function_table_lock_); + + JNIEnvExt::table_override_ = table_override; + + // See if we have a runtime. Note: we cannot run other code (like JavaVMExt's CheckJNI install + // code), as we'd have to recursively lock the mutex. + Runtime* runtime = Runtime::Current(); + if (runtime != nullptr) { + runtime->GetThreadList()->ForEach(ThreadResetFunctionTable, nullptr); + } +} + +const JNINativeInterface* JNIEnvExt::GetFunctionTable(bool check_jni) { + const JNINativeInterface* override = JNIEnvExt::table_override_; + if (override != nullptr) { + return override; + } + return check_jni ? GetCheckJniNativeInterface() : GetJniNativeInterface(); +} + } // namespace art diff --git a/runtime/jni_env_ext.h b/runtime/jni_env_ext.h index 5cca0aef9b..4004c457b5 100644 --- a/runtime/jni_env_ext.h +++ b/runtime/jni_env_ext.h @@ -43,7 +43,7 @@ struct JNIEnvExt : public JNIEnv { void DumpReferenceTables(std::ostream& os) REQUIRES_SHARED(Locks::mutator_lock_); - void SetCheckJniEnabled(bool enabled); + void SetCheckJniEnabled(bool enabled) REQUIRES(!Locks::jni_function_table_lock_); void PushFrame(int capacity) REQUIRES_SHARED(Locks::mutator_lock_); void PopFrame() REQUIRES_SHARED(Locks::mutator_lock_); @@ -104,10 +104,27 @@ struct JNIEnvExt : public JNIEnv { // Set the functions to the runtime shutdown functions. void SetFunctionsToRuntimeShutdownFunctions(); + // Set the function table override. This will install the override (or original table, if null) + // to all threads. + // Note: JNI function table overrides are sensitive to the order of operations wrt/ CheckJNI. + // After overriding the JNI function table, CheckJNI toggling is ignored. + static void SetTableOverride(const JNINativeInterface* table_override) + REQUIRES(!Locks::thread_list_lock_, !Locks::jni_function_table_lock_); + + // Return either the regular, or the CheckJNI function table. Will return table_override_ instead + // if it is not null. + static const JNINativeInterface* GetFunctionTable(bool check_jni) + REQUIRES(Locks::jni_function_table_lock_); + private: + // Override of function tables. This applies to both default as well as instrumented (CheckJNI) + // function tables. + static const JNINativeInterface* table_override_ GUARDED_BY(Locks::jni_function_table_lock_); + // The constructor should not be called directly. It may leave the object in an erroneous state, // and the result needs to be checked. - JNIEnvExt(Thread* self, JavaVMExt* vm, std::string* error_msg); + JNIEnvExt(Thread* self, JavaVMExt* vm, std::string* error_msg) + REQUIRES(!Locks::jni_function_table_lock_); // All locked objects, with the (Java caller) stack frame that locked them. Used in CheckJNI // to ensure that only monitors locked in this native frame are being unlocked, and that at diff --git a/runtime/jni_internal_test.cc b/runtime/jni_internal_test.cc index 4da5e23502..08d1eeb95d 100644 --- a/runtime/jni_internal_test.cc +++ b/runtime/jni_internal_test.cc @@ -2346,4 +2346,39 @@ TEST_F(JniInternalTest, JNIEnvExtOffsets) { EXPECT_EQ(segment_state_now, segment_state_computed); } +static size_t gGlobalRefCount = 0; +static const JNINativeInterface* gOriginalEnv = nullptr; + +static jobject CountNewGlobalRef(JNIEnv* env, jobject o) { + ++gGlobalRefCount; + return gOriginalEnv->NewGlobalRef(env, o); +} + +// Test the table override. +TEST_F(JniInternalTest, JNIEnvExtTableOverride) { + JNINativeInterface env_override; + memcpy(&env_override, env_->functions, sizeof(JNINativeInterface)); + + gOriginalEnv = env_->functions; + env_override.NewGlobalRef = CountNewGlobalRef; + gGlobalRefCount = 0; + + jclass local = env_->FindClass("java/lang/Object"); + ASSERT_TRUE(local != nullptr); + + // Set the table, add a global ref, see whether the counter increases. + JNIEnvExt::SetTableOverride(&env_override); + + jobject global = env_->NewGlobalRef(local); + EXPECT_EQ(1u, gGlobalRefCount); + env_->DeleteGlobalRef(global); + + // Reset + JNIEnvExt::SetTableOverride(nullptr); + + jobject global2 = env_->NewGlobalRef(local); + EXPECT_EQ(1u, gGlobalRefCount); + env_->DeleteGlobalRef(global2); +} + } // namespace art diff --git a/runtime/openjdkjvmti/Android.bp b/runtime/openjdkjvmti/Android.bp index af027f6ba6..d5c652035a 100644 --- a/runtime/openjdkjvmti/Android.bp +++ b/runtime/openjdkjvmti/Android.bp @@ -23,10 +23,12 @@ cc_defaults { "ti_class.cc", "ti_field.cc", "ti_heap.cc", + "ti_jni.cc", "ti_method.cc", "ti_monitor.cc", "ti_object.cc", "ti_properties.cc", + "ti_search.cc", "ti_stack.cc", "ti_redefine.cc", "ti_thread.cc", diff --git a/runtime/openjdkjvmti/OpenjdkJvmTi.cc b/runtime/openjdkjvmti/OpenjdkJvmTi.cc index 3cba81771c..90467db8f6 100644 --- a/runtime/openjdkjvmti/OpenjdkJvmTi.cc +++ b/runtime/openjdkjvmti/OpenjdkJvmTi.cc @@ -51,11 +51,13 @@ #include "ti_class.h" #include "ti_field.h" #include "ti_heap.h" +#include "ti_jni.h" #include "ti_method.h" #include "ti_monitor.h" #include "ti_object.h" #include "ti_properties.h" #include "ti_redefine.h" +#include "ti_search.h" #include "ti_stack.h" #include "ti_thread.h" #include "ti_threadgroup.h" @@ -801,11 +803,11 @@ class JvmtiFunctions { } static jvmtiError SetJNIFunctionTable(jvmtiEnv* env, const jniNativeInterface* function_table) { - return ERR(NOT_IMPLEMENTED); + return JNIUtil::SetJNIFunctionTable(env, function_table); } static jvmtiError GetJNIFunctionTable(jvmtiEnv* env, jniNativeInterface** function_table) { - return ERR(NOT_IMPLEMENTED); + return JNIUtil::GetJNIFunctionTable(env, function_table); } // TODO: This will require locking, so that an agent can't remove callbacks when we're dispatching @@ -1067,11 +1069,11 @@ class JvmtiFunctions { } static jvmtiError AddToBootstrapClassLoaderSearch(jvmtiEnv* env, const char* segment) { - return ERR(NOT_IMPLEMENTED); + return SearchUtil::AddToBootstrapClassLoaderSearch(env, segment); } static jvmtiError AddToSystemClassLoaderSearch(jvmtiEnv* env, const char* segment) { - return ERR(NOT_IMPLEMENTED); + return SearchUtil::AddToSystemClassLoaderSearch(env, segment); } static jvmtiError GetSystemProperties(jvmtiEnv* env, jint* count_ptr, char*** property_ptr) { @@ -1246,7 +1248,12 @@ class JvmtiFunctions { } static jvmtiError GetJLocationFormat(jvmtiEnv* env, jvmtiJlocationFormat* format_ptr) { - return ERR(NOT_IMPLEMENTED); + // Report BCI as jlocation format. We report dex bytecode indices. + if (format_ptr == nullptr) { + return ERR(NULL_POINTER); + } + *format_ptr = jvmtiJlocationFormat::JVMTI_JLOCATION_JVMBCI; + return ERR(NONE); } // TODO Remove this once events are working. diff --git a/runtime/openjdkjvmti/jvmti.h b/runtime/openjdkjvmti/jvmti.h index ee708cb193..de07c163fc 100644 --- a/runtime/openjdkjvmti/jvmti.h +++ b/runtime/openjdkjvmti/jvmti.h @@ -74,7 +74,7 @@ typedef jobject jthreadGroup; typedef jlong jlocation; struct _jrawMonitorID; typedef struct _jrawMonitorID *jrawMonitorID; -typedef struct JNINativeInterface_ jniNativeInterface; +typedef struct JNINativeInterface jniNativeInterface; /* Constants */ diff --git a/runtime/openjdkjvmti/ti_jni.cc b/runtime/openjdkjvmti/ti_jni.cc new file mode 100644 index 0000000000..88f0395ba5 --- /dev/null +++ b/runtime/openjdkjvmti/ti_jni.cc @@ -0,0 +1,91 @@ +/* Copyright (C) 2017 The Android Open Source Project + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This file implements interfaces from the file jvmti.h. This implementation + * is licensed under the same terms as the file jvmti.h. The + * copyright and license information for the file jvmti.h follows. + * + * Copyright (c) 2003, 2011, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +#include "ti_jni.h" + +#include "jni.h" + +#include "art_jvmti.h" +#include "base/mutex.h" +#include "java_vm_ext.h" +#include "jni_env_ext.h" +#include "runtime.h" +#include "thread-inl.h" + +namespace openjdkjvmti { + +jvmtiError JNIUtil::SetJNIFunctionTable(jvmtiEnv* env ATTRIBUTE_UNUSED, + const jniNativeInterface* function_table) { + // While we supporting setting null (which will reset the table), the spec says no. + if (function_table == nullptr) { + return ERR(NULL_POINTER); + } + + art::JNIEnvExt::SetTableOverride(function_table); + return ERR(NONE); +} + +jvmtiError JNIUtil::GetJNIFunctionTable(jvmtiEnv* env, jniNativeInterface** function_table) { + if (function_table == nullptr) { + return ERR(NULL_POINTER); + } + + // We use the generic JNIEnvExt::GetFunctionTable instead of querying a specific JNIEnv, as + // this has to work in the start phase. + + // Figure out which table is current. Conservatively assume check-jni is off. + bool check_jni = false; + art::Runtime* runtime = art::Runtime::Current(); + if (runtime != nullptr && runtime->GetJavaVM() != nullptr) { + check_jni = runtime->GetJavaVM()->IsCheckJniEnabled(); + } + + // Get that table. + const JNINativeInterface* current_table; + { + art::MutexLock mu(art::Thread::Current(), *art::Locks::jni_function_table_lock_); + current_table = art::JNIEnvExt::GetFunctionTable(check_jni); + } + + // Allocate memory and copy the table. + unsigned char* data; + jvmtiError data_result = env->Allocate(sizeof(JNINativeInterface), &data); + if (data_result != ERR(NONE)) { + return data_result; + } + memcpy(data, current_table, sizeof(JNINativeInterface)); + + *function_table = reinterpret_cast<JNINativeInterface*>(data); + + return ERR(NONE); +} + +} // namespace openjdkjvmti diff --git a/runtime/openjdkjvmti/ti_jni.h b/runtime/openjdkjvmti/ti_jni.h new file mode 100644 index 0000000000..906aab0667 --- /dev/null +++ b/runtime/openjdkjvmti/ti_jni.h @@ -0,0 +1,58 @@ +/* Copyright (C) 2017 The Android Open Source Project + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This file implements interfaces from the file jvmti.h. This implementation + * is licensed under the same terms as the file jvmti.h. The + * copyright and license information for the file jvmti.h follows. + * + * Copyright (c) 2003, 2011, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +#ifndef ART_RUNTIME_OPENJDKJVMTI_TI_JNI_H_ +#define ART_RUNTIME_OPENJDKJVMTI_TI_JNI_H_ + +#include "jni.h" +#include "jvmti.h" + +namespace openjdkjvmti { + +// Note: Currently, JNI function table changes are sensitive to the order of operations wrt/ +// CheckJNI. If an agent sets the function table, and a program than late-enables CheckJNI, +// CheckJNI will not be working (as the agent will forward to the non-CheckJNI table). +// +// This behavior results from our usage of the function table to avoid a check of the +// CheckJNI flag. A future implementation may install on loading of this plugin an +// intermediate function table that explicitly checks the flag, so that switching CheckJNI +// is transparently handled. + +class JNIUtil { + public: + static jvmtiError SetJNIFunctionTable(jvmtiEnv* env, const jniNativeInterface* function_table); + + static jvmtiError GetJNIFunctionTable(jvmtiEnv* env, jniNativeInterface** function_table); +}; + +} // namespace openjdkjvmti + +#endif // ART_RUNTIME_OPENJDKJVMTI_TI_JNI_H_ diff --git a/runtime/openjdkjvmti/ti_search.cc b/runtime/openjdkjvmti/ti_search.cc new file mode 100644 index 0000000000..913d2b6a74 --- /dev/null +++ b/runtime/openjdkjvmti/ti_search.cc @@ -0,0 +1,122 @@ +/* Copyright (C) 2017 The Android Open Source Project + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This file implements interfaces from the file jvmti.h. This implementation + * is licensed under the same terms as the file jvmti.h. The + * copyright and license information for the file jvmti.h follows. + * + * Copyright (c) 2003, 2011, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +#include "ti_search.h" + +#include "jni.h" + +#include "art_jvmti.h" +#include "base/macros.h" +#include "class_linker.h" +#include "dex_file.h" +#include "runtime.h" +#include "scoped_thread_state_change-inl.h" +#include "ScopedLocalRef.h" + +namespace openjdkjvmti { + +jvmtiError SearchUtil::AddToBootstrapClassLoaderSearch(jvmtiEnv* env ATTRIBUTE_UNUSED, + const char* segment) { + art::Runtime* current = art::Runtime::Current(); + if (current == nullptr) { + return ERR(WRONG_PHASE); + } + if (current->GetClassLinker() == nullptr) { + // TODO: Support boot classpath change in OnLoad. + return ERR(WRONG_PHASE); + } + if (segment == nullptr) { + return ERR(NULL_POINTER); + } + + std::string error_msg; + std::vector<std::unique_ptr<const art::DexFile>> dex_files; + if (!art::DexFile::Open(segment, segment, true, &error_msg, &dex_files)) { + LOG(WARNING) << "Could not open " << segment << " for boot classpath extension: " << error_msg; + return ERR(ILLEGAL_ARGUMENT); + } + + art::ScopedObjectAccess soa(art::Thread::Current()); + for (std::unique_ptr<const art::DexFile>& dex_file : dex_files) { + current->GetClassLinker()->AppendToBootClassPath(art::Thread::Current(), *dex_file.release()); + } + + return ERR(NONE); +} + +jvmtiError SearchUtil::AddToSystemClassLoaderSearch(jvmtiEnv* jvmti_env ATTRIBUTE_UNUSED, + const char* segment) { + if (segment == nullptr) { + return ERR(NULL_POINTER); + } + + art::Runtime* current = art::Runtime::Current(); + if (current == nullptr) { + return ERR(WRONG_PHASE); + } + jobject sys_class_loader = current->GetSystemClassLoader(); + if (sys_class_loader == nullptr) { + // TODO: Support classpath change in OnLoad. + return ERR(WRONG_PHASE); + } + + // We'll use BaseDexClassLoader.addDexPath, as it takes care of array resizing etc. As a downside, + // exceptions are swallowed. + + art::Thread* self = art::Thread::Current(); + JNIEnv* env = self->GetJniEnv(); + if (!env->IsInstanceOf(sys_class_loader, + art::WellKnownClasses::dalvik_system_BaseDexClassLoader)) { + return ERR(INTERNAL); + } + + jmethodID add_dex_path_id = env->GetMethodID( + art::WellKnownClasses::dalvik_system_BaseDexClassLoader, + "addDexPath", + "(Ljava/lang/String;)V"); + if (add_dex_path_id == nullptr) { + return ERR(INTERNAL); + } + + ScopedLocalRef<jstring> dex_path(env, env->NewStringUTF(segment)); + if (dex_path.get() == nullptr) { + return ERR(INTERNAL); + } + env->CallVoidMethod(sys_class_loader, add_dex_path_id, dex_path.get()); + + if (env->ExceptionCheck()) { + env->ExceptionClear(); + return ERR(ILLEGAL_ARGUMENT); + } + return ERR(NONE); +} + +} // namespace openjdkjvmti diff --git a/runtime/openjdkjvmti/ti_search.h b/runtime/openjdkjvmti/ti_search.h new file mode 100644 index 0000000000..6a52e80405 --- /dev/null +++ b/runtime/openjdkjvmti/ti_search.h @@ -0,0 +1,48 @@ +/* Copyright (C) 2017 The Android Open Source Project + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This file implements interfaces from the file jvmti.h. This implementation + * is licensed under the same terms as the file jvmti.h. The + * copyright and license information for the file jvmti.h follows. + * + * Copyright (c) 2003, 2011, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +#ifndef ART_RUNTIME_OPENJDKJVMTI_TI_SEARCH_H_ +#define ART_RUNTIME_OPENJDKJVMTI_TI_SEARCH_H_ + +#include "jvmti.h" + +namespace openjdkjvmti { + +class SearchUtil { + public: + static jvmtiError AddToBootstrapClassLoaderSearch(jvmtiEnv* env, const char* segment); + + static jvmtiError AddToSystemClassLoaderSearch(jvmtiEnv* env, const char* segment); +}; + +} // namespace openjdkjvmti + +#endif // ART_RUNTIME_OPENJDKJVMTI_TI_SEARCH_H_ diff --git a/runtime/runtime.cc b/runtime/runtime.cc index 55e1852c0c..6ef5f26ce5 100644 --- a/runtime/runtime.cc +++ b/runtime/runtime.cc @@ -1100,6 +1100,7 @@ bool Runtime::Init(RuntimeArgumentMap&& runtime_options_in) { if (runtime_options.Exists(Opt::JdwpOptions)) { Dbg::ConfigureJdwp(runtime_options.GetOrDefault(Opt::JdwpOptions)); } + callbacks_.AddThreadLifecycleCallback(Dbg::GetThreadLifecycleCallback()); jit_options_.reset(jit::JitOptions::CreateFromRuntimeArguments(runtime_options)); if (IsAotCompiler()) { diff --git a/runtime/runtime.h b/runtime/runtime.h index a87e1c136b..0c5de4eedf 100644 --- a/runtime/runtime.h +++ b/runtime/runtime.h @@ -28,6 +28,7 @@ #include "arch/instruction_set.h" #include "base/macros.h" +#include "base/mutex.h" #include "dex_file_types.h" #include "experimental_flags.h" #include "gc_root.h" @@ -39,6 +40,7 @@ #include "offsets.h" #include "process_state.h" #include "quick/quick_method_frame_info.h" +#include "runtime_callbacks.h" #include "runtime_stats.h" #include "safe_map.h" @@ -660,6 +662,10 @@ class Runtime { void AttachAgent(const std::string& agent_arg); + RuntimeCallbacks& GetRuntimeCallbacks() { + return callbacks_; + } + private: static void InitPlatformSignalHandlers(); @@ -917,6 +923,8 @@ class Runtime { ClassHierarchyAnalysis* cha_; + RuntimeCallbacks callbacks_; + DISALLOW_COPY_AND_ASSIGN(Runtime); }; std::ostream& operator<<(std::ostream& os, const Runtime::CalleeSaveType& rhs); diff --git a/runtime/runtime_callbacks.cc b/runtime/runtime_callbacks.cc new file mode 100644 index 0000000000..a523ddfa44 --- /dev/null +++ b/runtime/runtime_callbacks.cc @@ -0,0 +1,48 @@ +/* + * 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 "runtime_callbacks.h" + +#include <algorithm> + +#include "thread.h" + +namespace art { + +void RuntimeCallbacks::AddThreadLifecycleCallback(ThreadLifecycleCallback* cb) { + thread_callbacks_.push_back(cb); +} + +void RuntimeCallbacks::RemoveThreadLifecycleCallback(ThreadLifecycleCallback* cb) { + auto it = std::find(thread_callbacks_.begin(), thread_callbacks_.end(), cb); + if (it != thread_callbacks_.end()) { + thread_callbacks_.erase(it); + } +} + +void RuntimeCallbacks::ThreadStart(Thread* self) { + for (ThreadLifecycleCallback* cb : thread_callbacks_) { + cb->ThreadStart(self); + } +} + +void RuntimeCallbacks::ThreadDeath(Thread* self) { + for (ThreadLifecycleCallback* cb : thread_callbacks_) { + cb->ThreadDeath(self); + } +} + +} // namespace art diff --git a/runtime/runtime_callbacks.h b/runtime/runtime_callbacks.h new file mode 100644 index 0000000000..39eef3bb20 --- /dev/null +++ b/runtime/runtime_callbacks.h @@ -0,0 +1,49 @@ +/* + * 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. + */ + +#ifndef ART_RUNTIME_RUNTIME_CALLBACKS_H_ +#define ART_RUNTIME_RUNTIME_CALLBACKS_H_ + +#include <vector> + +#include "base/macros.h" +#include "base/mutex.h" + +namespace art { + +class Thread; +class ThreadLifecycleCallback; + +class RuntimeCallbacks { + public: + void AddThreadLifecycleCallback(ThreadLifecycleCallback* cb) + REQUIRES(Locks::runtime_callbacks_lock_); + void RemoveThreadLifecycleCallback(ThreadLifecycleCallback* cb) + REQUIRES(Locks::runtime_callbacks_lock_); + + void ThreadStart(Thread* self) + REQUIRES_SHARED(Locks::mutator_lock_, Locks::runtime_callbacks_lock_); + void ThreadDeath(Thread* self) + REQUIRES_SHARED(Locks::mutator_lock_, Locks::runtime_callbacks_lock_); + + private: + std::vector<ThreadLifecycleCallback*> thread_callbacks_ + GUARDED_BY(Locks::runtime_callbacks_lock_); +}; + +} // namespace art + +#endif // ART_RUNTIME_RUNTIME_CALLBACKS_H_ diff --git a/runtime/runtime_callbacks_test.cc b/runtime/runtime_callbacks_test.cc new file mode 100644 index 0000000000..00a4062065 --- /dev/null +++ b/runtime/runtime_callbacks_test.cc @@ -0,0 +1,201 @@ +/* + * 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 "runtime_callbacks.h" + +#include "jni.h" +#include <memory> +#include <string> + +#include "art_method-inl.h" +#include "base/mutex.h" +#include "mirror/class-inl.h" +#include "common_runtime_test.h" +#include "mem_map.h" +#include "obj_ptr.h" +#include "runtime.h" +#include "ScopedLocalRef.h" +#include "thread-inl.h" +#include "well_known_classes.h" + +namespace art { + +class RuntimeCallbacksTest : public CommonRuntimeTest { + protected: + void SetUp() OVERRIDE { + CommonRuntimeTest::SetUp(); + + WriterMutexLock mu(Thread::Current(), *Locks::runtime_callbacks_lock_); + AddListener(); + } + + void TearDown() OVERRIDE { + { + WriterMutexLock mu(Thread::Current(), *Locks::runtime_callbacks_lock_); + RemoveListener(); + } + + CommonRuntimeTest::TearDown(); + } + + virtual void AddListener() REQUIRES(Locks::runtime_callbacks_lock_) = 0; + virtual void RemoveListener() REQUIRES(Locks::runtime_callbacks_lock_) = 0; + + void MakeExecutable(ObjPtr<mirror::Class> klass) REQUIRES_SHARED(Locks::mutator_lock_) { + CHECK(klass != nullptr); + PointerSize pointer_size = class_linker_->GetImagePointerSize(); + for (auto& m : klass->GetMethods(pointer_size)) { + if (!m.IsAbstract()) { + class_linker_->SetEntryPointsToInterpreter(&m); + } + } + } +}; + +class ThreadLifecycleCallbackRuntimeCallbacksTest : public RuntimeCallbacksTest { + public: + static void* PthreadsCallback(void* arg ATTRIBUTE_UNUSED) { + // Attach. + Runtime* runtime = Runtime::Current(); + CHECK(runtime->AttachCurrentThread("ThreadLifecycle test thread", true, nullptr, false)); + + // Detach. + runtime->DetachCurrentThread(); + + // Die... + return nullptr; + } + + protected: + void AddListener() OVERRIDE REQUIRES(Locks::runtime_callbacks_lock_) { + Runtime::Current()->GetRuntimeCallbacks().AddThreadLifecycleCallback(&cb_); + } + void RemoveListener() OVERRIDE REQUIRES(Locks::runtime_callbacks_lock_) { + Runtime::Current()->GetRuntimeCallbacks().RemoveThreadLifecycleCallback(&cb_); + } + + enum CallbackState { + kBase, + kStarted, + kDied, + kWrongStart, + kWrongDeath, + }; + + struct Callback : public ThreadLifecycleCallback { + void ThreadStart(Thread* self) OVERRIDE { + if (state == CallbackState::kBase) { + state = CallbackState::kStarted; + stored_self = self; + } else { + state = CallbackState::kWrongStart; + } + } + + void ThreadDeath(Thread* self) OVERRIDE { + if (state == CallbackState::kStarted && self == stored_self) { + state = CallbackState::kDied; + } else { + state = CallbackState::kWrongDeath; + } + } + + Thread* stored_self; + CallbackState state = CallbackState::kBase; + }; + + Callback cb_; +}; + + +TEST_F(ThreadLifecycleCallbackRuntimeCallbacksTest, ThreadLifecycleCallbackJava) { + Thread* self = Thread::Current(); + + self->TransitionFromSuspendedToRunnable(); + bool started = runtime_->Start(); + ASSERT_TRUE(started); + + cb_.state = CallbackState::kBase; // Ignore main thread attach. + + { + ScopedObjectAccess soa(self); + MakeExecutable(soa.Decode<mirror::Class>(WellKnownClasses::java_lang_Thread)); + } + + JNIEnv* env = self->GetJniEnv(); + + ScopedLocalRef<jobject> thread_name(env, + env->NewStringUTF("ThreadLifecycleCallback test thread")); + ASSERT_TRUE(thread_name.get() != nullptr); + + ScopedLocalRef<jobject> thread(env, env->AllocObject(WellKnownClasses::java_lang_Thread)); + ASSERT_TRUE(thread.get() != nullptr); + + env->CallNonvirtualVoidMethod(thread.get(), + WellKnownClasses::java_lang_Thread, + WellKnownClasses::java_lang_Thread_init, + runtime_->GetMainThreadGroup(), + thread_name.get(), + kMinThreadPriority, + JNI_FALSE); + ASSERT_FALSE(env->ExceptionCheck()); + + jmethodID start_id = env->GetMethodID(WellKnownClasses::java_lang_Thread, "start", "()V"); + ASSERT_TRUE(start_id != nullptr); + + env->CallVoidMethod(thread.get(), start_id); + ASSERT_FALSE(env->ExceptionCheck()); + + jmethodID join_id = env->GetMethodID(WellKnownClasses::java_lang_Thread, "join", "()V"); + ASSERT_TRUE(join_id != nullptr); + + env->CallVoidMethod(thread.get(), join_id); + ASSERT_FALSE(env->ExceptionCheck()); + + EXPECT_TRUE(cb_.state == CallbackState::kDied) << static_cast<int>(cb_.state); +} + +TEST_F(ThreadLifecycleCallbackRuntimeCallbacksTest, ThreadLifecycleCallbackAttach) { + std::string error_msg; + std::unique_ptr<MemMap> stack(MemMap::MapAnonymous("ThreadLifecycleCallback Thread", + nullptr, + 128 * kPageSize, // Just some small stack. + PROT_READ | PROT_WRITE, + false, + false, + &error_msg)); + ASSERT_FALSE(stack == nullptr) << error_msg; + + const char* reason = "ThreadLifecycleCallback test thread"; + pthread_attr_t attr; + CHECK_PTHREAD_CALL(pthread_attr_init, (&attr), reason); + CHECK_PTHREAD_CALL(pthread_attr_setstack, (&attr, stack->Begin(), stack->Size()), reason); + pthread_t pthread; + CHECK_PTHREAD_CALL(pthread_create, + (&pthread, + &attr, + &ThreadLifecycleCallbackRuntimeCallbacksTest::PthreadsCallback, + this), + reason); + CHECK_PTHREAD_CALL(pthread_attr_destroy, (&attr), reason); + + CHECK_PTHREAD_CALL(pthread_join, (pthread, nullptr), "ThreadLifecycleCallback test shutdown"); + + // Detach is not a ThreadDeath event, so we expect to be in state Started. + EXPECT_TRUE(cb_.state == CallbackState::kStarted) << static_cast<int>(cb_.state); +} + +} // namespace art diff --git a/runtime/thread.cc b/runtime/thread.cc index 016a379437..8c8afdc913 100644 --- a/runtime/thread.cc +++ b/runtime/thread.cc @@ -431,7 +431,10 @@ void* Thread::CreateCallback(void* arg) { ArtField* priorityField = jni::DecodeArtField(WellKnownClasses::java_lang_Thread_priority); self->SetNativePriority(priorityField->GetInt(self->tlsPtr_.opeer)); - Dbg::PostThreadStart(self); + { + ReaderMutexLock mu(self, *Locks::runtime_callbacks_lock_); + runtime->GetRuntimeCallbacks().ThreadStart(self); + } // Invoke the 'run' method of our java.lang.Thread. ObjPtr<mirror::Object> receiver = self->tlsPtr_.opeer; @@ -793,7 +796,8 @@ Thread* Thread::Attach(const char* thread_name, bool as_daemon, jobject thread_g { ScopedObjectAccess soa(self); - Dbg::PostThreadStart(self); + ReaderMutexLock mu(self, *Locks::runtime_callbacks_lock_); + runtime->GetRuntimeCallbacks().ThreadStart(self); } return self; @@ -1929,7 +1933,12 @@ void Thread::Destroy() { jni::DecodeArtField(WellKnownClasses::java_lang_Thread_nativePeer) ->SetLong<false>(tlsPtr_.opeer, 0); } - Dbg::PostThreadDeath(self); + Runtime* runtime = Runtime::Current(); + if (runtime != nullptr) { + ReaderMutexLock mu(self, *Locks::runtime_callbacks_lock_); + runtime->GetRuntimeCallbacks().ThreadDeath(self); + } + // Thread.join() is implemented as an Object.wait() on the Thread.lock object. Signal anyone // who is waiting. diff --git a/runtime/thread.h b/runtime/thread.h index 2b451bcaee..166ed217d0 100644 --- a/runtime/thread.h +++ b/runtime/thread.h @@ -1704,6 +1704,14 @@ class ScopedTransitioningToRunnable : public ValueObject { Thread* const self_; }; +class ThreadLifecycleCallback { + public: + virtual ~ThreadLifecycleCallback() {} + + virtual void ThreadStart(Thread* self) REQUIRES_SHARED(Locks::mutator_lock_) = 0; + virtual void ThreadDeath(Thread* self) REQUIRES_SHARED(Locks::mutator_lock_) = 0; +}; + std::ostream& operator<<(std::ostream& os, const Thread& thread); std::ostream& operator<<(std::ostream& os, const StackedShadowFrameType& thread); diff --git a/test/928-jni-table/build b/test/928-jni-table/build new file mode 100755 index 0000000000..898e2e54a2 --- /dev/null +++ b/test/928-jni-table/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/928-jni-table/expected.txt b/test/928-jni-table/expected.txt new file mode 100644 index 0000000000..a965a70ed4 --- /dev/null +++ b/test/928-jni-table/expected.txt @@ -0,0 +1 @@ +Done diff --git a/test/928-jni-table/info.txt b/test/928-jni-table/info.txt new file mode 100644 index 0000000000..875a5f6ec1 --- /dev/null +++ b/test/928-jni-table/info.txt @@ -0,0 +1 @@ +Tests basic functions in the jvmti plugin. diff --git a/test/928-jni-table/jni_table.cc b/test/928-jni-table/jni_table.cc new file mode 100644 index 0000000000..5123d3a43f --- /dev/null +++ b/test/928-jni-table/jni_table.cc @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2013 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 <stdio.h> + +#include "jni.h" +#include "openjdkjvmti/jvmti.h" + +#include "base/logging.h" +#include "base/macros.h" + +#include "ti-agent/common_helper.h" +#include "ti-agent/common_load.h" + +namespace art { +namespace Test927JNITable { + +// This test is equivalent to the jni_internal_test JNIEnvExtTableOverride. + +static size_t gGlobalRefCount = 0; +static JNINativeInterface* gOriginalEnv = nullptr; + +static jobject CountNewGlobalRef(JNIEnv* env, jobject o) { + ++gGlobalRefCount; + return gOriginalEnv->NewGlobalRef(env, o); +} + +extern "C" JNIEXPORT void JNICALL Java_Main_doJNITableTest( + JNIEnv* env, jclass klass) { + // Get the current table, as the delegate. + jvmtiError getorig_result = jvmti_env->GetJNIFunctionTable(&gOriginalEnv); + if (JvmtiErrorToException(env, getorig_result)) { + return; + } + + // Get the current table, as the override we'll install. + JNINativeInterface* env_override; + jvmtiError getoverride_result = jvmti_env->GetJNIFunctionTable(&env_override); + if (JvmtiErrorToException(env, getoverride_result)) { + return; + } + + env_override->NewGlobalRef = CountNewGlobalRef; + gGlobalRefCount = 0; + + // Install the override. + jvmtiError setoverride_result = jvmti_env->SetJNIFunctionTable(env_override); + if (JvmtiErrorToException(env, setoverride_result)) { + return; + } + + jobject global = env->NewGlobalRef(klass); + CHECK_EQ(1u, gGlobalRefCount); + env->DeleteGlobalRef(global); + + // Install the "original." There is no real reset. + jvmtiError setoverride2_result = jvmti_env->SetJNIFunctionTable(gOriginalEnv); + if (JvmtiErrorToException(env, setoverride2_result)) { + return; + } + + jobject global2 = env->NewGlobalRef(klass); + CHECK_EQ(1u, gGlobalRefCount); + env->DeleteGlobalRef(global2); + + // Try to install null. Should return NULL_POINTER error. + jvmtiError setoverride3_result = jvmti_env->SetJNIFunctionTable(nullptr); + if (setoverride3_result != JVMTI_ERROR_NULL_POINTER) { + LOG(FATAL) << "Didn't receive NULL_POINTER"; + } + + jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(env_override)); +} + +} // namespace Test927JNITable +} // namespace art diff --git a/test/928-jni-table/run b/test/928-jni-table/run new file mode 100755 index 0000000000..4379349cb2 --- /dev/null +++ b/test/928-jni-table/run @@ -0,0 +1,19 @@ +#!/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-run "$@" --experimental agents \ + --experimental runtime-plugins \ + --jvmti diff --git a/test/928-jni-table/src/Main.java b/test/928-jni-table/src/Main.java new file mode 100644 index 0000000000..b0baea1f9d --- /dev/null +++ b/test/928-jni-table/src/Main.java @@ -0,0 +1,27 @@ +/* + * 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 Main { + public static void main(String[] args) throws Exception { + System.loadLibrary(args[1]); + + doJNITableTest(); + + System.out.println("Done"); + } + + public static native void doJNITableTest(); +} diff --git a/test/929-search/build b/test/929-search/build new file mode 100755 index 0000000000..898e2e54a2 --- /dev/null +++ b/test/929-search/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/929-search/expected.txt b/test/929-search/expected.txt new file mode 100644 index 0000000000..a965a70ed4 --- /dev/null +++ b/test/929-search/expected.txt @@ -0,0 +1 @@ +Done diff --git a/test/929-search/info.txt b/test/929-search/info.txt new file mode 100644 index 0000000000..875a5f6ec1 --- /dev/null +++ b/test/929-search/info.txt @@ -0,0 +1 @@ +Tests basic functions in the jvmti plugin. diff --git a/test/929-search/run b/test/929-search/run new file mode 100755 index 0000000000..0a8d0672f6 --- /dev/null +++ b/test/929-search/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/929-search/search.cc b/test/929-search/search.cc new file mode 100644 index 0000000000..d1c698491e --- /dev/null +++ b/test/929-search/search.cc @@ -0,0 +1,53 @@ +/* + * 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 "android-base/stringprintf.h" +#include "base/logging.h" +#include "base/macros.h" +#include "jni.h" +#include "openjdkjvmti/jvmti.h" +#include "ScopedUtfChars.h" + +#include "ti-agent/common_helper.h" +#include "ti-agent/common_load.h" + +namespace art { +namespace Test929Search { + +extern "C" JNIEXPORT void JNICALL Java_Main_addToBootClassLoader( + JNIEnv* env, jclass Main_klass ATTRIBUTE_UNUSED, jstring segment) { + ScopedUtfChars utf(env, segment); + if (utf.c_str() == nullptr) { + return; + } + jvmtiError result = jvmti_env->AddToBootstrapClassLoaderSearch(utf.c_str()); + JvmtiErrorToException(env, result); +} + +extern "C" JNIEXPORT void JNICALL Java_Main_addToSystemClassLoader( + JNIEnv* env, jclass Main_klass ATTRIBUTE_UNUSED, jstring segment) { + ScopedUtfChars utf(env, segment); + if (utf.c_str() == nullptr) { + return; + } + jvmtiError result = jvmti_env->AddToSystemClassLoaderSearch(utf.c_str()); + JvmtiErrorToException(env, result); +} + +} // namespace Test929Search +} // namespace art diff --git a/test/929-search/src-ex/A.java b/test/929-search/src-ex/A.java new file mode 100644 index 0000000000..64acb2fcfe --- /dev/null +++ b/test/929-search/src-ex/A.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 A { +}
\ No newline at end of file diff --git a/test/929-search/src/B.java b/test/929-search/src/B.java new file mode 100644 index 0000000000..f1458c3bca --- /dev/null +++ b/test/929-search/src/B.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 B { +}
\ No newline at end of file diff --git a/test/929-search/src/Main.java b/test/929-search/src/Main.java new file mode 100644 index 0000000000..d253e6fdf6 --- /dev/null +++ b/test/929-search/src/Main.java @@ -0,0 +1,54 @@ +/* + * 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]); + + doTest(); + } + + private static void doTest() throws Exception { + doTest(true, DEX1, "B"); + doTest(false, DEX2, "A"); + System.out.println("Done"); + } + + private static void doTest(boolean boot, String segment, String className) throws Exception { + ClassLoader expectedClassLoader; + if (boot) { + expectedClassLoader = Object.class.getClassLoader(); + addToBootClassLoader(segment); + } else { + expectedClassLoader = ClassLoader.getSystemClassLoader(); + addToSystemClassLoader(segment); + } + + Class<?> c = Class.forName(className); + if (c.getClassLoader() != expectedClassLoader) { + throw new RuntimeException(className + "(" + boot + "/" + segment + "): " + + c.getClassLoader() + " vs " + expectedClassLoader); + } + } + + private static native void addToBootClassLoader(String s); + private static native void addToSystemClassLoader(String s); + + private static final String DEX1 = System.getenv("DEX_LOCATION") + "/929-search.jar"; + private static final String DEX2 = System.getenv("DEX_LOCATION") + "/929-search-ex.jar"; +} diff --git a/test/Android.bp b/test/Android.bp index c551b9de45..965d07aa43 100644 --- a/test/Android.bp +++ b/test/Android.bp @@ -267,6 +267,8 @@ art_cc_defaults { "924-threads/threads.cc", "925-threadgroups/threadgroups.cc", "927-timers/timers.cc", + "928-jni-table/jni_table.cc", + "929-search/search.cc", ], shared_libs: [ "libbase", diff --git a/test/Android.run-test.mk b/test/Android.run-test.mk index 9da96de6a6..e604c93c72 100644 --- a/test/Android.run-test.mk +++ b/test/Android.run-test.mk @@ -280,6 +280,7 @@ TEST_ART_BROKEN_TARGET_TESTS := \ # These 9** tests are not supported in current form due to linker # restrictions. See b/31681198 TEST_ART_BROKEN_TARGET_TESTS += \ + 901-hello-ti-agent \ 902-hello-transformation \ 903-hello-tagging \ 904-object-allocation \ @@ -306,6 +307,8 @@ TEST_ART_BROKEN_TARGET_TESTS += \ 925-threadgroups \ 926-multi-obsolescence \ 927-timers \ + 928-jni-table \ + 929-search \ ifneq (,$(filter target,$(TARGET_TYPES))) ART_TEST_KNOWN_BROKEN += $(call all-run-test-names,target,$(RUN_TYPES),$(PREBUILD_TYPES), \ |