diff options
author | Ian Rogers <irogers@google.com> | 2014-07-17 11:09:10 -0700 |
---|---|---|
committer | Ian Rogers <irogers@google.com> | 2014-08-08 08:29:53 -0700 |
commit | 68d8b42ddec39ec0174162d90d4abaa004d1983e (patch) | |
tree | 0bb6ccf3b996bb8363c10d07aa39cde221980602 /runtime/java_vm_ext.cc | |
parent | 9c522c2cbbf50dc687728747b37ad59985750b65 (diff) | |
download | art-68d8b42ddec39ec0174162d90d4abaa004d1983e.tar.gz art-68d8b42ddec39ec0174162d90d4abaa004d1983e.tar.bz2 art-68d8b42ddec39ec0174162d90d4abaa004d1983e.zip |
Wire up check JNI force copy mode.
Increase check JNI checks.
Break apart jni_internal.h in to jni_env_ext.h and java_vm_ext.h.
Fix the abuse of ScopedObjectAccess/annotalysis by ScopedCheck in the case
of VM routines.
Make class loader override and shared library class loader JNI global
references rather than mirror pointers.
Clean-ups to native bridge.
Change-Id: If7c6110b5aade7a402bfb67534af86a7b2cdeb55
Diffstat (limited to 'runtime/java_vm_ext.cc')
-rw-r--r-- | runtime/java_vm_ext.cc | 829 |
1 files changed, 829 insertions, 0 deletions
diff --git a/runtime/java_vm_ext.cc b/runtime/java_vm_ext.cc new file mode 100644 index 0000000000..9eab3fde13 --- /dev/null +++ b/runtime/java_vm_ext.cc @@ -0,0 +1,829 @@ +/* + * Copyright (C) 2011 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 "jni_internal.h" + +#include <dlfcn.h> + +#include "base/mutex.h" +#include "base/stl_util.h" +#include "check_jni.h" +#include "indirect_reference_table-inl.h" +#include "mirror/art_method.h" +#include "mirror/class-inl.h" +#include "mirror/class_loader.h" +#include "native_bridge.h" +#include "java_vm_ext.h" +#include "parsed_options.h" +#include "ScopedLocalRef.h" +#include "scoped_thread_state_change.h" +#include "thread-inl.h" +#include "thread_list.h" + +namespace art { + +static const size_t kPinTableInitial = 16; // Arbitrary. +static const size_t kPinTableMax = 1024; // Arbitrary sanity check. + +static size_t gGlobalsInitial = 512; // Arbitrary. +static size_t gGlobalsMax = 51200; // Arbitrary sanity check. (Must fit in 16 bits.) + +static const size_t kWeakGlobalsInitial = 16; // Arbitrary. +static const size_t kWeakGlobalsMax = 51200; // Arbitrary sanity check. (Must fit in 16 bits.) + +static bool IsBadJniVersion(int version) { + // We don't support JNI_VERSION_1_1. These are the only other valid versions. + return version != JNI_VERSION_1_2 && version != JNI_VERSION_1_4 && version != JNI_VERSION_1_6; +} + +class SharedLibrary { + public: + SharedLibrary(JNIEnv* env, Thread* self, const std::string& path, void* handle, + jobject class_loader) + : path_(path), + handle_(handle), + needs_native_bridge_(false), + class_loader_(env->NewGlobalRef(class_loader)), + jni_on_load_lock_("JNI_OnLoad lock"), + jni_on_load_cond_("JNI_OnLoad condition variable", jni_on_load_lock_), + jni_on_load_thread_id_(self->GetThreadId()), + jni_on_load_result_(kPending) { + } + + ~SharedLibrary() { + Thread* self = Thread::Current(); + if (self != nullptr) { + self->GetJniEnv()->DeleteGlobalRef(class_loader_); + } + } + + jobject GetClassLoader() const { + return class_loader_; + } + + const std::string& GetPath() const { + return path_; + } + + /* + * Check the result of an earlier call to JNI_OnLoad on this library. + * If the call has not yet finished in another thread, wait for it. + */ + bool CheckOnLoadResult() + LOCKS_EXCLUDED(jni_on_load_lock_) { + Thread* self = Thread::Current(); + bool okay; + { + MutexLock mu(self, jni_on_load_lock_); + + if (jni_on_load_thread_id_ == self->GetThreadId()) { + // Check this so we don't end up waiting for ourselves. We need to return "true" so the + // caller can continue. + LOG(INFO) << *self << " recursive attempt to load library " << "\"" << path_ << "\""; + okay = true; + } else { + while (jni_on_load_result_ == kPending) { + VLOG(jni) << "[" << *self << " waiting for \"" << path_ << "\" " << "JNI_OnLoad...]"; + jni_on_load_cond_.Wait(self); + } + + okay = (jni_on_load_result_ == kOkay); + VLOG(jni) << "[Earlier JNI_OnLoad for \"" << path_ << "\" " + << (okay ? "succeeded" : "failed") << "]"; + } + } + return okay; + } + + void SetResult(bool result) LOCKS_EXCLUDED(jni_on_load_lock_) { + Thread* self = Thread::Current(); + MutexLock mu(self, jni_on_load_lock_); + + jni_on_load_result_ = result ? kOkay : kFailed; + jni_on_load_thread_id_ = 0; + + // Broadcast a wakeup to anybody sleeping on the condition variable. + jni_on_load_cond_.Broadcast(self); + } + + void SetNeedsNativeBridge() { + needs_native_bridge_ = true; + } + + bool NeedsNativeBridge() const { + return needs_native_bridge_; + } + + void* FindSymbol(const std::string& symbol_name) { + return dlsym(handle_, symbol_name.c_str()); + } + + void* FindSymbolWithNativeBridge(const std::string& symbol_name, const char* shorty) { + CHECK(NeedsNativeBridge()); + + uint32_t len = 0; + return NativeBridgeGetTrampoline(handle_, symbol_name.c_str(), shorty, len); + } + + private: + enum JNI_OnLoadState { + kPending, + kFailed, + kOkay, + }; + + // Path to library "/system/lib/libjni.so". + const std::string path_; + + // The void* returned by dlopen(3). + void* const handle_; + + // True if a native bridge is required. + bool needs_native_bridge_; + + // The ClassLoader this library is associated with, a global JNI reference that is + // created/deleted with the scope of the library. + const jobject class_loader_; + + // Guards remaining items. + Mutex jni_on_load_lock_ DEFAULT_MUTEX_ACQUIRED_AFTER; + // Wait for JNI_OnLoad in other thread. + ConditionVariable jni_on_load_cond_ GUARDED_BY(jni_on_load_lock_); + // Recursive invocation guard. + uint32_t jni_on_load_thread_id_ GUARDED_BY(jni_on_load_lock_); + // Result of earlier JNI_OnLoad call. + JNI_OnLoadState jni_on_load_result_ GUARDED_BY(jni_on_load_lock_); +}; + +// This exists mainly to keep implementation details out of the header file. +class Libraries { + public: + Libraries() { + } + + ~Libraries() { + STLDeleteValues(&libraries_); + } + + void Dump(std::ostream& os) const { + bool first = true; + for (const auto& library : libraries_) { + if (!first) { + os << ' '; + } + first = false; + os << library.first; + } + } + + size_t size() const { + return libraries_.size(); + } + + SharedLibrary* Get(const std::string& path) { + auto it = libraries_.find(path); + return (it == libraries_.end()) ? nullptr : it->second; + } + + void Put(const std::string& path, SharedLibrary* library) { + libraries_.Put(path, library); + } + + // See section 11.3 "Linking Native Methods" of the JNI spec. + void* FindNativeMethod(mirror::ArtMethod* m, std::string& detail) + EXCLUSIVE_LOCKS_REQUIRED(Locks::jni_libraries_lock_) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { + std::string jni_short_name(JniShortName(m)); + std::string jni_long_name(JniLongName(m)); + const mirror::ClassLoader* declaring_class_loader = m->GetDeclaringClass()->GetClassLoader(); + ScopedObjectAccessUnchecked soa(Thread::Current()); + for (const auto& lib : libraries_) { + SharedLibrary* library = lib.second; + if (soa.Decode<mirror::ClassLoader*>(library->GetClassLoader()) != declaring_class_loader) { + // We only search libraries loaded by the appropriate ClassLoader. + continue; + } + // Try the short name then the long name... + void* fn; + if (library->NeedsNativeBridge()) { + const char* shorty = m->GetShorty(); + fn = library->FindSymbolWithNativeBridge(jni_short_name, shorty); + if (fn == nullptr) { + fn = library->FindSymbolWithNativeBridge(jni_long_name, shorty); + } + } else { + fn = library->FindSymbol(jni_short_name); + if (fn == nullptr) { + fn = library->FindSymbol(jni_long_name); + } + } + if (fn == nullptr) { + fn = library->FindSymbol(jni_long_name); + } + if (fn != nullptr) { + VLOG(jni) << "[Found native code for " << PrettyMethod(m) + << " in \"" << library->GetPath() << "\"]"; + return fn; + } + } + detail += "No implementation found for "; + detail += PrettyMethod(m); + detail += " (tried " + jni_short_name + " and " + jni_long_name + ")"; + LOG(ERROR) << detail; + return nullptr; + } + + private: + SafeMap<std::string, SharedLibrary*> libraries_; +}; + + +class JII { + public: + static jint DestroyJavaVM(JavaVM* vm) { + if (vm == nullptr) { + return JNI_ERR; + } + JavaVMExt* raw_vm = reinterpret_cast<JavaVMExt*>(vm); + delete raw_vm->GetRuntime(); + return JNI_OK; + } + + static jint AttachCurrentThread(JavaVM* vm, JNIEnv** p_env, void* thr_args) { + return AttachCurrentThreadInternal(vm, p_env, thr_args, false); + } + + static jint AttachCurrentThreadAsDaemon(JavaVM* vm, JNIEnv** p_env, void* thr_args) { + return AttachCurrentThreadInternal(vm, p_env, thr_args, true); + } + + static jint DetachCurrentThread(JavaVM* vm) { + if (vm == nullptr || Thread::Current() == nullptr) { + return JNI_ERR; + } + JavaVMExt* raw_vm = reinterpret_cast<JavaVMExt*>(vm); + Runtime* runtime = raw_vm->GetRuntime(); + runtime->DetachCurrentThread(); + return JNI_OK; + } + + static jint GetEnv(JavaVM* vm, void** env, jint version) { + // GetEnv always returns a JNIEnv* for the most current supported JNI version, + // and unlike other calls that take a JNI version doesn't care if you supply + // JNI_VERSION_1_1, which we don't otherwise support. + if (IsBadJniVersion(version) && version != JNI_VERSION_1_1) { + LOG(ERROR) << "Bad JNI version passed to GetEnv: " << version; + return JNI_EVERSION; + } + if (vm == nullptr || env == nullptr) { + return JNI_ERR; + } + Thread* thread = Thread::Current(); + if (thread == nullptr) { + *env = nullptr; + return JNI_EDETACHED; + } + *env = thread->GetJniEnv(); + return JNI_OK; + } + + private: + static jint AttachCurrentThreadInternal(JavaVM* vm, JNIEnv** p_env, void* raw_args, bool as_daemon) { + if (vm == nullptr || p_env == nullptr) { + return JNI_ERR; + } + + // Return immediately if we're already attached. + Thread* self = Thread::Current(); + if (self != nullptr) { + *p_env = self->GetJniEnv(); + return JNI_OK; + } + + Runtime* runtime = reinterpret_cast<JavaVMExt*>(vm)->GetRuntime(); + + // No threads allowed in zygote mode. + if (runtime->IsZygote()) { + LOG(ERROR) << "Attempt to attach a thread in the zygote"; + return JNI_ERR; + } + + JavaVMAttachArgs* args = static_cast<JavaVMAttachArgs*>(raw_args); + const char* thread_name = nullptr; + jobject thread_group = nullptr; + if (args != nullptr) { + if (IsBadJniVersion(args->version)) { + LOG(ERROR) << "Bad JNI version passed to " + << (as_daemon ? "AttachCurrentThreadAsDaemon" : "AttachCurrentThread") << ": " + << args->version; + return JNI_EVERSION; + } + thread_name = args->name; + thread_group = args->group; + } + + if (!runtime->AttachCurrentThread(thread_name, as_daemon, thread_group, !runtime->IsCompiler())) { + *p_env = nullptr; + return JNI_ERR; + } else { + *p_env = Thread::Current()->GetJniEnv(); + return JNI_OK; + } + } +}; + +const JNIInvokeInterface gJniInvokeInterface = { + nullptr, // reserved0 + nullptr, // reserved1 + nullptr, // reserved2 + JII::DestroyJavaVM, + JII::AttachCurrentThread, + JII::DetachCurrentThread, + JII::GetEnv, + JII::AttachCurrentThreadAsDaemon +}; + +JavaVMExt::JavaVMExt(Runtime* runtime, ParsedOptions* options) + : runtime_(runtime), + check_jni_abort_hook_(nullptr), + check_jni_abort_hook_data_(nullptr), + check_jni_(false), // Initialized properly in the constructor body below. + force_copy_(options->force_copy_), + tracing_enabled_(!options->jni_trace_.empty() || VLOG_IS_ON(third_party_jni)), + trace_(options->jni_trace_), + pins_lock_("JNI pin table lock", kPinTableLock), + pin_table_("pin table", kPinTableInitial, kPinTableMax), + globals_lock_("JNI global reference table lock"), + globals_(gGlobalsInitial, gGlobalsMax, kGlobal), + libraries_(new Libraries), + unchecked_functions_(&gJniInvokeInterface), + weak_globals_lock_("JNI weak global reference table lock"), + weak_globals_(kWeakGlobalsInitial, kWeakGlobalsMax, kWeakGlobal), + allow_new_weak_globals_(true), + weak_globals_add_condition_("weak globals add condition", weak_globals_lock_) { + functions = unchecked_functions_; + if (options->check_jni_) { + SetCheckJniEnabled(true); + } +} + +JavaVMExt::~JavaVMExt() { +} + +void JavaVMExt::JniAbort(const char* jni_function_name, const char* msg) { + Thread* self = Thread::Current(); + ScopedObjectAccess soa(self); + mirror::ArtMethod* current_method = self->GetCurrentMethod(nullptr); + + std::ostringstream os; + os << "JNI DETECTED ERROR IN APPLICATION: " << msg; + + if (jni_function_name != nullptr) { + os << "\n in call to " << jni_function_name; + } + // TODO: is this useful given that we're about to dump the calling thread's stack? + if (current_method != nullptr) { + os << "\n from " << PrettyMethod(current_method); + } + os << "\n"; + self->Dump(os); + + if (check_jni_abort_hook_ != nullptr) { + check_jni_abort_hook_(check_jni_abort_hook_data_, os.str()); + } else { + // Ensure that we get a native stack trace for this thread. + self->TransitionFromRunnableToSuspended(kNative); + LOG(FATAL) << os.str(); + self->TransitionFromSuspendedToRunnable(); // Unreachable, keep annotalysis happy. + } +} + +void JavaVMExt::JniAbortV(const char* jni_function_name, const char* fmt, va_list ap) { + std::string msg; + StringAppendV(&msg, fmt, ap); + JniAbort(jni_function_name, msg.c_str()); +} + +void JavaVMExt::JniAbortF(const char* jni_function_name, const char* fmt, ...) { + va_list args; + va_start(args, fmt); + JniAbortV(jni_function_name, fmt, args); + va_end(args); +} + +bool JavaVMExt::ShouldTrace(mirror::ArtMethod* method) SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { + // Fast where no tracing is enabled. + if (trace_.empty() && !VLOG_IS_ON(third_party_jni)) { + return false; + } + // Perform checks based on class name. + StringPiece class_name(method->GetDeclaringClassDescriptor()); + if (!trace_.empty() && class_name.find(trace_) != std::string::npos) { + return true; + } + if (!VLOG_IS_ON(third_party_jni)) { + return false; + } + // Return true if we're trying to log all third-party JNI activity and 'method' doesn't look + // like part of Android. + static const char* gBuiltInPrefixes[] = { + "Landroid/", + "Lcom/android/", + "Lcom/google/android/", + "Ldalvik/", + "Ljava/", + "Ljavax/", + "Llibcore/", + "Lorg/apache/harmony/", + }; + for (size_t i = 0; i < arraysize(gBuiltInPrefixes); ++i) { + if (class_name.starts_with(gBuiltInPrefixes[i])) { + return false; + } + } + return true; +} + +jobject JavaVMExt::AddGlobalRef(Thread* self, mirror::Object* obj) { + // Check for null after decoding the object to handle cleared weak globals. + if (obj == nullptr) { + return nullptr; + } + WriterMutexLock mu(self, globals_lock_); + IndirectRef ref = globals_.Add(IRT_FIRST_SEGMENT, obj); + return reinterpret_cast<jobject>(ref); +} + +jweak JavaVMExt::AddWeakGlobalRef(Thread* self, mirror::Object* obj) { + if (obj == nullptr) { + return nullptr; + } + MutexLock mu(self, weak_globals_lock_); + while (UNLIKELY(!allow_new_weak_globals_)) { + weak_globals_add_condition_.WaitHoldingLocks(self); + } + IndirectRef ref = weak_globals_.Add(IRT_FIRST_SEGMENT, obj); + return reinterpret_cast<jweak>(ref); +} + +void JavaVMExt::DeleteGlobalRef(Thread* self, jobject obj) { + if (obj == nullptr) { + return; + } + WriterMutexLock mu(self, globals_lock_); + if (!globals_.Remove(IRT_FIRST_SEGMENT, obj)) { + LOG(WARNING) << "JNI WARNING: DeleteGlobalRef(" << obj << ") " + << "failed to find entry"; + } +} + +void JavaVMExt::DeleteWeakGlobalRef(Thread* self, jweak obj) { + if (obj == nullptr) { + return; + } + MutexLock mu(self, weak_globals_lock_); + if (!weak_globals_.Remove(IRT_FIRST_SEGMENT, obj)) { + LOG(WARNING) << "JNI WARNING: DeleteWeakGlobalRef(" << obj << ") " + << "failed to find entry"; + } +} + +static void ThreadEnableCheckJni(Thread* thread, void* arg) { + bool* check_jni = reinterpret_cast<bool*>(arg); + thread->GetJniEnv()->SetCheckJniEnabled(*check_jni); +} + +bool JavaVMExt::SetCheckJniEnabled(bool enabled) { + bool old_check_jni = check_jni_; + check_jni_ = enabled; + functions = enabled ? GetCheckJniInvokeInterface() : unchecked_functions_; + MutexLock mu(Thread::Current(), *Locks::thread_list_lock_); + runtime_->GetThreadList()->ForEach(ThreadEnableCheckJni, &check_jni_); + return old_check_jni; +} + +void JavaVMExt::DumpForSigQuit(std::ostream& os) { + os << "JNI: CheckJNI is " << (check_jni_ ? "on" : "off"); + if (force_copy_) { + os << " (with forcecopy)"; + } + Thread* self = Thread::Current(); + { + MutexLock mu(self, pins_lock_); + os << "; pins=" << pin_table_.Size(); + } + { + ReaderMutexLock mu(self, globals_lock_); + os << "; globals=" << globals_.Capacity(); + } + { + MutexLock mu(self, weak_globals_lock_); + if (weak_globals_.Capacity() > 0) { + os << " (plus " << weak_globals_.Capacity() << " weak)"; + } + } + os << '\n'; + + { + MutexLock mu(self, *Locks::jni_libraries_lock_); + os << "Libraries: " << Dumpable<Libraries>(*libraries_) << " (" << libraries_->size() << ")\n"; + } +} + +void JavaVMExt::DisallowNewWeakGlobals() { + MutexLock mu(Thread::Current(), weak_globals_lock_); + allow_new_weak_globals_ = false; +} + +void JavaVMExt::AllowNewWeakGlobals() { + Thread* self = Thread::Current(); + MutexLock mu(self, weak_globals_lock_); + allow_new_weak_globals_ = true; + weak_globals_add_condition_.Broadcast(self); +} + +mirror::Object* JavaVMExt::DecodeGlobal(Thread* self, IndirectRef ref) { + return globals_.SynchronizedGet(self, &globals_lock_, ref); +} + +mirror::Object* JavaVMExt::DecodeWeakGlobal(Thread* self, IndirectRef ref) { + MutexLock mu(self, weak_globals_lock_); + while (UNLIKELY(!allow_new_weak_globals_)) { + weak_globals_add_condition_.WaitHoldingLocks(self); + } + return weak_globals_.Get(ref); +} + +void JavaVMExt::PinPrimitiveArray(Thread* self, mirror::Array* array) { + MutexLock mu(self, pins_lock_); + pin_table_.Add(array); +} + +void JavaVMExt::UnpinPrimitiveArray(Thread* self, mirror::Array* array) { + MutexLock mu(self, pins_lock_); + pin_table_.Remove(array); +} + +void JavaVMExt::DumpReferenceTables(std::ostream& os) { + Thread* self = Thread::Current(); + { + ReaderMutexLock mu(self, globals_lock_); + globals_.Dump(os); + } + { + MutexLock mu(self, weak_globals_lock_); + weak_globals_.Dump(os); + } + { + MutexLock mu(self, pins_lock_); + pin_table_.Dump(os); + } +} + +bool JavaVMExt::LoadNativeLibrary(JNIEnv* env, const std::string& path, jobject class_loader, + std::string* error_msg) { + error_msg->clear(); + + // See if we've already loaded this library. If we have, and the class loader + // matches, return successfully without doing anything. + // TODO: for better results we should canonicalize the pathname (or even compare + // inodes). This implementation is fine if everybody is using System.loadLibrary. + SharedLibrary* library; + Thread* self = Thread::Current(); + { + // TODO: move the locking (and more of this logic) into Libraries. + MutexLock mu(self, *Locks::jni_libraries_lock_); + library = libraries_->Get(path); + } + if (library != nullptr) { + if (env->IsSameObject(library->GetClassLoader(), class_loader) == JNI_FALSE) { + // The library will be associated with class_loader. The JNI + // spec says we can't load the same library into more than one + // class loader. + StringAppendF(error_msg, "Shared library \"%s\" already opened by " + "ClassLoader %p; can't open in ClassLoader %p", + path.c_str(), library->GetClassLoader(), class_loader); + LOG(WARNING) << error_msg; + return false; + } + VLOG(jni) << "[Shared library \"" << path << "\" already loaded in " + << " ClassLoader " << class_loader << "]"; + if (!library->CheckOnLoadResult()) { + StringAppendF(error_msg, "JNI_OnLoad failed on a previous attempt " + "to load \"%s\"", path.c_str()); + return false; + } + return true; + } + + // Open the shared library. Because we're using a full path, the system + // doesn't have to search through LD_LIBRARY_PATH. (It may do so to + // resolve this library's dependencies though.) + + // Failures here are expected when java.library.path has several entries + // and we have to hunt for the lib. + + // Below we dlopen but there is no paired dlclose, this would be necessary if we supported + // class unloading. Libraries will only be unloaded when the reference count (incremented by + // dlopen) becomes zero from dlclose. + + Locks::mutator_lock_->AssertNotHeld(self); + const char* path_str = path.empty() ? nullptr : path.c_str(); + void* handle = dlopen(path_str, RTLD_LAZY); + bool needs_native_bridge = false; + if (handle == nullptr) { + if (NativeBridgeIsSupported(path_str)) { + handle = NativeBridgeLoadLibrary(path_str, RTLD_LAZY); + needs_native_bridge = true; + } + } + + VLOG(jni) << "[Call to dlopen(\"" << path << "\", RTLD_LAZY) returned " << handle << "]"; + + if (handle == nullptr) { + *error_msg = dlerror(); + LOG(ERROR) << "dlopen(\"" << path << "\", RTLD_LAZY) failed: " << *error_msg; + return false; + } + + if (env->ExceptionCheck() == JNI_TRUE) { + LOG(ERROR) << "Unexpected exception:"; + env->ExceptionDescribe(); + env->ExceptionClear(); + } + // Create a new entry. + // TODO: move the locking (and more of this logic) into Libraries. + bool created_library = false; + { + // Create SharedLibrary ahead of taking the libraries lock to maintain lock ordering. + std::unique_ptr<SharedLibrary> new_library( + new SharedLibrary(env, self, path, handle, class_loader)); + MutexLock mu(self, *Locks::jni_libraries_lock_); + library = libraries_->Get(path); + if (library == nullptr) { // We won race to get libraries_lock. + library = new_library.release(); + libraries_->Put(path, library); + created_library = true; + } + } + if (!created_library) { + LOG(INFO) << "WOW: we lost a race to add shared library: " + << "\"" << path << "\" ClassLoader=" << class_loader; + return library->CheckOnLoadResult(); + } + VLOG(jni) << "[Added shared library \"" << path << "\" for ClassLoader " << class_loader << "]"; + + bool was_successful = false; + void* sym; + if (needs_native_bridge) { + library->SetNeedsNativeBridge(); + sym = library->FindSymbolWithNativeBridge("JNI_OnLoad", nullptr); + } else { + sym = dlsym(handle, "JNI_OnLoad"); + } + if (sym == nullptr) { + VLOG(jni) << "[No JNI_OnLoad found in \"" << path << "\"]"; + was_successful = true; + } else { + // Call JNI_OnLoad. We have to override the current class + // loader, which will always be "null" since the stuff at the + // top of the stack is around Runtime.loadLibrary(). (See + // the comments in the JNI FindClass function.) + ScopedLocalRef<jobject> old_class_loader(env, env->NewLocalRef(self->GetClassLoaderOverride())); + self->SetClassLoaderOverride(class_loader); + + VLOG(jni) << "[Calling JNI_OnLoad in \"" << path << "\"]"; + typedef int (*JNI_OnLoadFn)(JavaVM*, void*); + JNI_OnLoadFn jni_on_load = reinterpret_cast<JNI_OnLoadFn>(sym); + int version = (*jni_on_load)(this, nullptr); + + self->SetClassLoaderOverride(old_class_loader.get()); + + if (version == JNI_ERR) { + StringAppendF(error_msg, "JNI_ERR returned from JNI_OnLoad in \"%s\"", path.c_str()); + } else if (IsBadJniVersion(version)) { + StringAppendF(error_msg, "Bad JNI version returned from JNI_OnLoad in \"%s\": %d", + path.c_str(), version); + // It's unwise to call dlclose() here, but we can mark it + // as bad and ensure that future load attempts will fail. + // We don't know how far JNI_OnLoad got, so there could + // be some partially-initialized stuff accessible through + // newly-registered native method calls. We could try to + // unregister them, but that doesn't seem worthwhile. + } else { + was_successful = true; + } + VLOG(jni) << "[Returned " << (was_successful ? "successfully" : "failure") + << " from JNI_OnLoad in \"" << path << "\"]"; + } + + library->SetResult(was_successful); + return was_successful; +} + +void* JavaVMExt::FindCodeForNativeMethod(mirror::ArtMethod* m) { + CHECK(m->IsNative()); + mirror::Class* c = m->GetDeclaringClass(); + // If this is a static method, it could be called before the class has been initialized. + CHECK(c->IsInitializing()) << c->GetStatus() << " " << PrettyMethod(m); + std::string detail; + void* native_method; + Thread* self = Thread::Current(); + { + MutexLock mu(self, *Locks::jni_libraries_lock_); + native_method = libraries_->FindNativeMethod(m, detail); + } + // Throwing can cause libraries_lock to be reacquired. + if (native_method == nullptr) { + ThrowLocation throw_location = self->GetCurrentLocationForThrow(); + self->ThrowNewException(throw_location, "Ljava/lang/UnsatisfiedLinkError;", detail.c_str()); + } + return native_method; +} + +void JavaVMExt::SweepJniWeakGlobals(IsMarkedCallback* callback, void* arg) { + MutexLock mu(Thread::Current(), weak_globals_lock_); + for (mirror::Object** entry : weak_globals_) { + // Since this is called by the GC, we don't need a read barrier. + mirror::Object* obj = *entry; + mirror::Object* new_obj = callback(obj, arg); + if (new_obj == nullptr) { + new_obj = kClearedJniWeakGlobal; + } + *entry = new_obj; + } +} + +void JavaVMExt::VisitRoots(RootCallback* callback, void* arg) { + Thread* self = Thread::Current(); + { + ReaderMutexLock mu(self, globals_lock_); + globals_.VisitRoots(callback, arg, 0, kRootJNIGlobal); + } + { + MutexLock mu(self, pins_lock_); + pin_table_.VisitRoots(callback, arg, 0, kRootVMInternal); + } + // The weak_globals table is visited by the GC itself (because it mutates the table). +} + +// JNI Invocation interface. + +extern "C" jint JNI_CreateJavaVM(JavaVM** p_vm, JNIEnv** p_env, void* vm_args) { + const JavaVMInitArgs* args = static_cast<JavaVMInitArgs*>(vm_args); + if (IsBadJniVersion(args->version)) { + LOG(ERROR) << "Bad JNI version passed to CreateJavaVM: " << args->version; + return JNI_EVERSION; + } + RuntimeOptions options; + for (int i = 0; i < args->nOptions; ++i) { + JavaVMOption* option = &args->options[i]; + options.push_back(std::make_pair(std::string(option->optionString), option->extraInfo)); + } + bool ignore_unrecognized = args->ignoreUnrecognized; + if (!Runtime::Create(options, ignore_unrecognized)) { + return JNI_ERR; + } + Runtime* runtime = Runtime::Current(); + bool started = runtime->Start(); + if (!started) { + delete Thread::Current()->GetJniEnv(); + delete runtime->GetJavaVM(); + LOG(WARNING) << "CreateJavaVM failed"; + return JNI_ERR; + } + *p_env = Thread::Current()->GetJniEnv(); + *p_vm = runtime->GetJavaVM(); + return JNI_OK; +} + +extern "C" jint JNI_GetCreatedJavaVMs(JavaVM** vms, jsize, jsize* vm_count) { + Runtime* runtime = Runtime::Current(); + if (runtime == nullptr) { + *vm_count = 0; + } else { + *vm_count = 1; + vms[0] = runtime->GetJavaVM(); + } + return JNI_OK; +} + +// Historically unsupported. +extern "C" jint JNI_GetDefaultJavaVMInitArgs(void* /*vm_args*/) { + return JNI_ERR; +} + +} // namespace art |