Code for loading shared libraries and calling JNI_OnLoad.

This causes a bit of log spam as we call unimplemented JNI functions,
but fixing those is my first job tomorrow. The exciting thing here is
that we're now loading libcore's classes and libcore's JNI.

I've changed ParsedOptions' std::vector of verbose options over to a
std::set for the convenience of callers, and defaulted -verbose:jni to
on. (This is not the cause of the log spam.)

The only nasty bit here is that icu4c doesn't like being initialized
more than 10 times, because it has a 10-element fixed-size array. The
u_cleanup function is not publicly accessible, so we have to fiddle
around with dlsym(3) twice in one patch...

Change-Id: I9f6b5d74621eb2e88d04a89f00335067fda4ccbe
diff --git a/build/Android.common.mk b/build/Android.common.mk
index fd0acf2..6fa0293 100644
--- a/build/Android.common.mk
+++ b/build/Android.common.mk
@@ -16,7 +16,11 @@
 
 ART_CPP_EXTENSION := .cc
 
-ART_C_INCLUDES := external/gtest/include external/zlib
+ART_C_INCLUDES := \
+	external/gtest/include \
+	external/icu4c/common \
+	external/icu4c/i18n \
+	external/zlib
 
 ART_CFLAGS := \
 	-O0 \
diff --git a/build/Android.libart.mk b/build/Android.libart.mk
index e6169e9..b71cc53 100644
--- a/build/Android.libart.mk
+++ b/build/Android.libart.mk
@@ -40,7 +40,7 @@
   LOCAL_C_INCLUDES += src $(ART_C_INCLUDES)
   LOCAL_SHARED_LIBRARIES := liblog
   ifeq ($(1),target)
-    LOCAL_SHARED_LIBRARIES += libcutils libstlport libz
+    LOCAL_SHARED_LIBRARIES += libcutils libstlport libz libdl
   else
     LOCAL_SHARED_LIBRARIES += libz-host
     LOCAL_LDLIBS := -ldl -lpthread -lrt
diff --git a/build/Android.libarttest.mk b/build/Android.libarttest.mk
index 37e45a1..107eddc 100644
--- a/build/Android.libarttest.mk
+++ b/build/Android.libarttest.mk
@@ -26,9 +26,9 @@
   LOCAL_SRC_FILES := $(LIBARTTEST_COMMON_SRC_FILES)
   LOCAL_CFLAGS := $(ART_CFLAGS) -UNDEBUG
   ifeq ($(1),target)
-    LOCAL_SHARED_LIBRARIES := libstlport
+    LOCAL_SHARED_LIBRARIES := libdl libstlport
   else
-    LOCAL_LDLIBS := -lrt
+    LOCAL_LDLIBS := -ldl -lrt
   endif
   ifeq ($(1),target)
     include $(BUILD_SHARED_LIBRARY)
diff --git a/build/Android.test.mk b/build/Android.test.mk
index ba76f27..3f5673c 100644
--- a/build/Android.test.mk
+++ b/build/Android.test.mk
@@ -32,10 +32,10 @@
   LOCAL_C_INCLUDES += $(ART_C_INCLUDES)
   LOCAL_SHARED_LIBRARIES := libarttest libartd
   ifeq ($(1),target)
-    LOCAL_SHARED_LIBRARIES += libstlport libz
+    LOCAL_SHARED_LIBRARIES += libdl libicuuc libicui18n libstlport libz
     LOCAL_STATIC_LIBRARIES := libgtest libgtest_main
   else
-    LOCAL_SHARED_LIBRARIES += libz-host
+    LOCAL_SHARED_LIBRARIES += libicuuc-host libicui18n-host libz-host
     LOCAL_WHOLE_STATIC_LIBRARIES := libgtest_host libgtest_main_host
   endif
   ifeq ($(1),target)
@@ -47,7 +47,7 @@
     ART_TARGET_TEST_EXECUTABLES += $(TARGET_OUT_EXECUTABLES)/$$(LOCAL_MODULE)
   else
     ART_HOST_TEST_EXECUTABLES += $(HOST_OUT_EXECUTABLES)/$$(LOCAL_MODULE)
-    LOCAL_LDFLAGS += -lpthread
+    LOCAL_LDFLAGS += -ldl -lpthread
   endif
 endef
 
diff --git a/src/common_test.h b/src/common_test.h
index 1fbe718..bfc58f6 100644
--- a/src/common_test.h
+++ b/src/common_test.h
@@ -1,6 +1,7 @@
 // Copyright 2011 Google Inc. All Rights Reserved.
 
 #include <dirent.h>
+#include <dlfcn.h>
 #include <sys/stat.h>
 #include <sys/types.h>
 
@@ -11,6 +12,9 @@
 #include "class_linker.h"
 #include "dex_file.h"
 
+#include "unicode/uclean.h"
+#include "unicode/uvernum.h"
+
 #include "gtest/gtest.h"
 
 namespace art {
@@ -320,6 +324,15 @@
   virtual void SetUp() {
     is_host_ = getenv("ANDROID_BUILD_TOP") != NULL;
 
+    if (is_host_) {
+      // $ANDROID_ROOT is set on the device, but not on the host.
+      // We need to set this so that icu4c can find its locale data.
+      std::string root;
+      root += getenv("ANDROID_BUILD_TOP");
+      root += "/out/host/linux-x86";
+      setenv("ANDROID_ROOT", root.c_str(), 1);
+    }
+
     android_data_.reset(strdup(is_host_ ? "/tmp/art-data-XXXXXX" : "/sdcard/art-data-XXXXXX"));
     ASSERT_TRUE(android_data_ != NULL);
     const char* android_data_modified = mkdtemp(android_data_.get());
@@ -368,6 +381,15 @@
     ASSERT_EQ(0, rmdir_cache_result);
     int rmdir_data_result = rmdir(android_data_.get());
     ASSERT_EQ(0, rmdir_data_result);
+
+    // icu4c has a fixed 10-element array "gCommonICUDataArray".
+    // If we run > 10 tests, we fill that array and u_setCommonData fails.
+    // There's a function to clear the array, but it's not public...
+    typedef void (*IcuCleanupFn)();
+    void* sym = dlsym(RTLD_DEFAULT, "u_cleanup_" U_ICU_VERSION_SHORT);
+    CHECK(sym != NULL);
+    IcuCleanupFn icu_cleanup_fn = reinterpret_cast<IcuCleanupFn>(sym);
+    (*icu_cleanup_fn)();
   }
 
   std::string GetLibCoreDexFileName() {
diff --git a/src/jni_internal.cc b/src/jni_internal.cc
index ef3bf57..b08d12a 100644
--- a/src/jni_internal.cc
+++ b/src/jni_internal.cc
@@ -3,9 +3,10 @@
 #include "jni_internal.h"
 
 #include <cstdarg>
-#include <vector>
-#include <utility>
+#include <dlfcn.h>
 #include <sys/mman.h>
+#include <utility>
+#include <vector>
 
 #include "class_linker.h"
 #include "jni.h"
@@ -18,6 +19,238 @@
 
 namespace art {
 
+enum JNI_OnLoadState {
+  kPending = 0,     /* initial state, must be zero */
+  kFailed,
+  kOkay,
+};
+
+struct SharedLibrary {
+  SharedLibrary() : jni_on_load_lock("JNI_OnLoad") {
+  }
+
+  // Path to library "/system/lib/libjni.so".
+  std::string path;
+
+  // The void* returned by dlopen(3).
+  void* handle;
+
+  // The ClassLoader this library is associated with.
+  Object* class_loader;
+
+  // Guards remaining items.
+  Mutex jni_on_load_lock;
+  // Wait for JNI_OnLoad in other thread.
+  pthread_cond_t  jni_on_load_cond;
+  // Recursive invocation guard.
+  uint32_t jni_on_load_tid;
+  // Result of earlier JNI_OnLoad call.
+  JNI_OnLoadState jni_on_load_result;
+};
+
+/*
+ * 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(JavaVMExt* vm, SharedLibrary* library) {
+  Thread* self = Thread::Current();
+  if (library->jni_on_load_tid == self->GetId()) {
+    // 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 "
+              << "\"" << library->path << "\"";
+    return true;
+  }
+
+  UNIMPLEMENTED(ERROR) << "need to pthread_cond_wait!";
+  // MutexLock mu(&library->jni_on_load_lock);
+  while (library->jni_on_load_result == kPending) {
+    if (vm->verbose_jni) {
+      LOG(INFO) << "[" << *self << " waiting for \"" << library->path << "\" "
+                << "JNI_OnLoad...]";
+    }
+    Thread::State old_state = self->GetState();
+    self->SetState(Thread::kWaiting); // TODO: VMWAIT
+    // pthread_cond_wait(&library->jni_on_load_cond, &library->jni_on_load_lock);
+    self->SetState(old_state);
+  }
+
+  bool okay = (library->jni_on_load_result == kOkay);
+  if (vm->verbose_jni) {
+    LOG(INFO) << "[Earlier JNI_OnLoad for \"" << library->path << "\" "
+              << (okay ? "succeeded" : "failed") << "]";
+  }
+  return okay;
+}
+
+typedef int (*JNI_OnLoadFn)(JavaVM*, void*);
+
+/*
+ * Load native code from the specified absolute pathname.  Per the spec,
+ * if we've already loaded a library with the specified pathname, we
+ * return without doing anything.
+ *
+ * TODO? for better results we should absolutify the pathname.  For fully
+ * correct results we should stat to get the inode and compare that.  The
+ * existing implementation is fine so long as everybody is using
+ * System.loadLibrary.
+ *
+ * The library will be associated with the specified class loader.  The JNI
+ * spec says we can't load the same library into more than one class loader.
+ *
+ * Returns "true" on success. On failure, sets *detail to a
+ * human-readable description of the error or NULL if no detail is
+ * available; ownership of the string is transferred to the caller.
+ */
+bool JavaVMExt::LoadNativeLibrary(const std::string& path, Object* class_loader, char** detail) {
+  *detail = NULL;
+
+  // See if we've already loaded this library.  If we have, and the class loader
+  // matches, return successfully without doing anything.
+  SharedLibrary* library = libraries[path];
+  if (library != NULL) {
+    if (library->class_loader != class_loader) {
+      LOG(WARNING) << "Shared library \"" << path << "\" already opened by "
+                   << "ClassLoader " << library->class_loader << "; "
+                   << "can't open in " << class_loader;
+      *detail = strdup("already opened by different ClassLoader");
+      return false;
+    }
+    if (verbose_jni) {
+      LOG(INFO) << "[Shared library \"" << path << "\" already loaded in "
+                << "ClassLoader " << class_loader << "]";
+    }
+    if (!CheckOnLoadResult(this, library)) {
+      *detail = strdup("JNI_OnLoad failed before");
+      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.
+
+  // The current version of the dynamic linker prints detailed information
+  // about dlopen() failures.  Some things to check if the message is
+  // cryptic:
+  //   - make sure the library exists on the device
+  //   - verify that the right path is being opened (the debug log message
+  //     above can help with that)
+  //   - check to see if the library is valid (e.g. not zero bytes long)
+  //   - check config/prelink-linux-arm.map to ensure that the library
+  //     is listed and is not being overrun by the previous entry (if
+  //     loading suddenly stops working on a prelinked library, this is
+  //     a good one to check)
+  //   - write a trivial app that calls sleep() then dlopen(), attach
+  //     to it with "strace -p <pid>" while it sleeps, and watch for
+  //     attempts to open nonexistent dependent shared libs
+
+  // TODO: automate some of these checks!
+
+  if (verbose_jni) {
+    LOG(INFO) << "[calling dlopen(\"" << path << "\")...]";
+  }
+
+  // This can execute slowly for a large library on a busy system, so we
+  // want to switch from RUNNING to VMWAIT while it executes.  This allows
+  // the GC to ignore us.
+  Thread* self = Thread::Current();
+  Thread::State old_state = self->GetState();
+  self->SetState(Thread::kWaiting); // TODO: VMWAIT
+  void* handle = dlopen(path.c_str(), RTLD_LAZY);
+  self->SetState(old_state);
+
+  if (verbose_jni) {
+    LOG(INFO) << "[dlopen(\"" << path << "\") returned " << handle << "]";
+  }
+
+  if (handle == NULL) {
+    *detail = strdup(dlerror());
+    return false;
+  }
+
+  // Create a new entry.
+  library = new SharedLibrary;
+  library->path = path;
+  library->handle = handle;
+  library->class_loader = class_loader;
+  UNIMPLEMENTED(ERROR) << "missing pthread_cond_init";
+  // pthread_cond_init(&library->onLoadCond, NULL);
+  library->jni_on_load_tid = self->GetId();
+
+  libraries[path] = library;
+
+//  if (pNewEntry != pActualEntry) {
+//    LOG(INFO) << "WOW: we lost a race to add a shared library (\"" << path << "\" ClassLoader=" << class_loader <<")";
+//    freeSharedLibEntry(pNewEntry);
+//    return CheckOnLoadResult(this, pActualEntry);
+//  } else
+  {
+    if (verbose_jni) {
+      LOG(INFO) << "[Added shared library \"" << path << "\" for ClassLoader " << class_loader << "]";
+    }
+
+    bool result = true;
+    void* sym = dlsym(handle, "JNI_OnLoad");
+    if (sym == NULL) {
+      if (verbose_jni) {
+        LOG(INFO) << "[No JNI_OnLoad found in \"" << path << "\"]";
+      }
+    } 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.)
+      UNIMPLEMENTED(WARNING) << "need to override current class loader";
+      JNI_OnLoadFn jni_on_load = reinterpret_cast<JNI_OnLoadFn>(sym);
+      //Object* prevOverride = self->classLoaderOverride;
+      //self->classLoaderOverride = classLoader;
+
+      old_state = self->GetState();
+      self->SetState(Thread::kNative);
+      if (verbose_jni) {
+        LOG(INFO) << "[Calling JNI_OnLoad in \"" << path << "\"]";
+      }
+      int version = (*jni_on_load)(reinterpret_cast<JavaVM*>(this), NULL);
+      self->SetState(old_state);
+
+      UNIMPLEMENTED(WARNING) << "need to restore current class loader";
+      //self->classLoaderOverride = prevOverride;
+
+      if (version != JNI_VERSION_1_2 &&
+          version != JNI_VERSION_1_4 &&
+          version != JNI_VERSION_1_6) {
+        LOG(WARNING) << "JNI_OnLoad in \"" << path << "\" returned "
+                     << "bad version: " << 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.
+        result = false;
+      } else {
+        if (verbose_jni) {
+          LOG(INFO) << "[Returned from JNI_OnLoad in \"" << path << "\"]";
+        }
+      }
+    }
+
+    library->jni_on_load_result = result ? kOkay : kFailed;
+    library->jni_on_load_tid = 0;
+
+    // Broadcast a wakeup to anybody sleeping on the condition variable.
+    UNIMPLEMENTED(ERROR) << "missing pthread_cond_broadcast";
+    // MutexLock mu(&library->jni_on_load_lock);
+    // pthread_cond_broadcast(&library->jni_on_load_cond);
+    return result;
+  }
+}
+
 // Entry/exit processing for all JNI calls.
 //
 // This performs the necessary thread state switching, lets us amortize the
@@ -320,14 +553,14 @@
 
 jint PushLocalFrame(JNIEnv* env, jint cap) {
   ScopedJniThreadState ts(env);
-  UNIMPLEMENTED(FATAL);
-  return 0;
+  UNIMPLEMENTED(WARNING) << "ignoring PushLocalFrame(" << cap << ")";
+  return JNI_OK;
 }
 
 jobject PopLocalFrame(JNIEnv* env, jobject res) {
   ScopedJniThreadState ts(env);
-  UNIMPLEMENTED(FATAL);
-  return NULL;
+  UNIMPLEMENTED(WARNING) << "ignoring PopLocalFrame";
+  return res;
 }
 
 jobject NewGlobalRef(JNIEnv* env, jobject lobj) {
@@ -1656,6 +1889,12 @@
   for(int i = 0; i < nMethods; i++) {
     const char* name = methods[i].name;
     const char* sig = methods[i].signature;
+
+    if (*sig == '!') {
+      // TODO: fast jni. it's too noisy to log all these.
+      ++sig;
+    }
+
     Method* method = klass->FindDirectMethod(name, sig);
     if (method == NULL) {
       method = klass->FindVirtualMethod(name, sig);
@@ -1704,7 +1943,7 @@
   ScopedJniThreadState ts(env);
   Runtime* runtime = Runtime::Current();
   if (runtime != NULL) {
-    *vm = runtime->GetJavaVM();
+    *vm = reinterpret_cast<JavaVM*>(runtime->GetJavaVM());
   } else {
     *vm = NULL;
   }
@@ -2058,7 +2297,7 @@
     return JNI_ERR;
   } else {
     *p_env = reinterpret_cast<JNIEnv*>(Thread::Current()->GetJniEnv());
-    *p_vm = runtime->GetJavaVM();
+    *p_vm = reinterpret_cast<JavaVM*>(runtime->GetJavaVM());
     return JNI_OK;
   }
 }
@@ -2069,7 +2308,7 @@
     *vm_count = 0;
   } else {
     *vm_count = 1;
-    vms[0] = runtime->GetJavaVM();
+    vms[0] = reinterpret_cast<JavaVM*>(runtime->GetJavaVM());
   }
   return JNI_OK;
 }
@@ -2177,10 +2416,11 @@
 static const size_t kWeakGlobalsInitial = 16; // Arbitrary.
 static const size_t kWeakGlobalsMax = 51200; // Arbitrary sanity check.
 
-JavaVMExt::JavaVMExt(Runtime* runtime, bool check_jni)
+JavaVMExt::JavaVMExt(Runtime* runtime, bool check_jni, bool verbose_jni)
     : fns(&gInvokeInterface),
       runtime(runtime),
       check_jni(check_jni),
+      verbose_jni(verbose_jni),
       pin_table("pin table", kPinTableInitialSize, kPinTableMaxSize),
       globals(kGlobalsInitial, kGlobalsMax, kGlobal),
       weak_globals(kWeakGlobalsInitial, kWeakGlobalsMax, kWeakGlobal) {
diff --git a/src/jni_internal.h b/src/jni_internal.h
index 50e3925..5d89dd8 100644
--- a/src/jni_internal.h
+++ b/src/jni_internal.h
@@ -10,13 +10,36 @@
 #include "macros.h"
 #include "reference_table.h"
 
+#include <map>
+#include <string>
+
 namespace art {
 
 class Runtime;
+class SharedLibrary;
 class Thread;
 
 struct JavaVMExt {
-  JavaVMExt(Runtime* runtime, bool check_jni);
+  JavaVMExt(Runtime* runtime, bool check_jni, bool verbose_jni);
+
+  /*
+   * Load native code from the specified absolute pathname.  Per the spec,
+   * if we've already loaded a library with the specified pathname, we
+   * return without doing anything.
+   *
+   * TODO: for better results we should canonicalize the pathname.  For fully
+   * correct results we should stat to get the inode and compare that.  The
+   * existing implementation is fine so long as everybody is using
+   * System.loadLibrary.
+   *
+   * The library will be associated with the specified class loader.  The JNI
+   * spec says we can't load the same library into more than one class loader.
+   *
+   * Returns true on success. On failure, returns false and sets *detail to a
+   * human-readable description of the error or NULL if no detail is
+   * available; ownership of the string is transferred to the caller.
+   */
+  bool LoadNativeLibrary(const std::string& path, Object* class_loader, char** detail);
 
   // Must be first to correspond with JNIEnv.
   const struct JNIInvokeInterface* fns;
@@ -24,6 +47,7 @@
   Runtime* runtime;
 
   bool check_jni;
+  bool verbose_jni;
 
   // Used to hold references to pinned primitive arrays.
   ReferenceTable pin_table;
@@ -33,6 +57,8 @@
 
   // JNI weak global references.
   IndirectReferenceTable weak_globals;
+
+  std::map<std::string, SharedLibrary*> libraries;
 };
 
 struct JNIEnvExt {
diff --git a/src/runtime.cc b/src/runtime.cc
index 2476837..11eb9bd 100644
--- a/src/runtime.cc
+++ b/src/runtime.cc
@@ -150,6 +150,16 @@
   return 0;
 }
 
+void LoadJniLibrary(JavaVMExt* vm, const char* name) {
+  // TODO: OS_SHARED_LIB_FORMAT_STR
+  std::string mapped_name(StringPrintf("lib%s.so", name));
+  char* reason = NULL;
+  if (!vm->LoadNativeLibrary(mapped_name, NULL, &reason)) {
+    LOG(FATAL) << "LoadNativeLibrary failed for \"" << mapped_name << "\": "
+               << reason;
+  }
+}
+
 DexFile* Open(const std::string& filename) {
   if (filename.size() < 4) {
     LOG(WARNING) << "Ignoring short classpath entry '" << filename << "'";
@@ -181,11 +191,12 @@
   const char* boot_class_path = getenv("BOOTCLASSPATH");
   parsed->boot_image_ = NULL;
 #ifdef NDEBUG
-  // CheckJNI is off by default for regular builds...
+  // -Xcheck:jni and -verbose:jni are off by default for regular builds...
   parsed->check_jni_ = false;
 #else
   // ...but on by default in debug builds.
   parsed->check_jni_ = true;
+  parsed->verbose_.insert("jni");
 #endif
   parsed->heap_initial_size_ = Heap::kInitialSize;
   parsed->heap_maximum_size_ = Heap::kMaximumSize;
@@ -210,7 +221,11 @@
     } else if (option.starts_with("-D")) {
       parsed->properties_.push_back(option.substr(strlen("-D")).data());
     } else if (option.starts_with("-verbose:")) {
-      Split(option.substr(strlen("-verbose:")).data(), ",", parsed->verbose_);
+      std::vector<std::string> verbose_options;
+      Split(option.substr(strlen("-verbose:")).data(), ",", verbose_options);
+      for (size_t i = 0; i < verbose_options.size(); ++i) {
+        parsed->verbose_.insert(verbose_options[i]);
+      }
     } else if (option == "vfprintf") {
       parsed->hook_vfprintf_ = reinterpret_cast<int (*)(FILE *, const char*, va_list)>(options[i].second);
     } else if (option == "exit") {
@@ -250,34 +265,40 @@
   bool success = runtime->Init(options, ignore_unrecognized);
   if (!success) {
     return NULL;
-  } else {
-    return Runtime::instance_ = runtime.release();
   }
+  instance_ = runtime.release();
+
+  // Most JNI libraries can just use System.loadLibrary, but you can't
+  // if you're the library that implements System.loadLibrary!
+  LoadJniLibrary(instance_->GetJavaVM(), "javacore");
+
+  return instance_;
 }
 
-bool Runtime::Init(const Options& options, bool ignore_unrecognized) {
+bool Runtime::Init(const Options& raw_options, bool ignore_unrecognized) {
   CHECK_EQ(kPageSize, sysconf(_SC_PAGE_SIZE));
 
-  scoped_ptr<ParsedOptions> parsed_options(ParsedOptions::Create(options, ignore_unrecognized));
-  if (parsed_options == NULL) {
+  scoped_ptr<ParsedOptions> options(ParsedOptions::Create(raw_options, ignore_unrecognized));
+  if (options == NULL) {
     return false;
   }
-  vfprintf_ = parsed_options->hook_vfprintf_;
-  exit_ = parsed_options->hook_exit_;
-  abort_ = parsed_options->hook_abort_;
+  vfprintf_ = options->hook_vfprintf_;
+  exit_ = options->hook_exit_;
+  abort_ = options->hook_abort_;
 
   thread_list_ = ThreadList::Create();
 
-  Heap::Init(parsed_options->heap_initial_size_,
-             parsed_options->heap_maximum_size_);
+  Heap::Init(options->heap_initial_size_,
+             options->heap_maximum_size_);
 
-  java_vm_.reset(reinterpret_cast<JavaVM*>(new JavaVMExt(this, parsed_options->check_jni_)));
+  bool verbose_jni = options->verbose_.find("jni") != options->verbose_.end();
+  java_vm_.reset(new JavaVMExt(this, options->check_jni_, verbose_jni));
 
   Thread::Init();
   Thread* current_thread = Thread::Attach(this);
   thread_list_->Register(current_thread);
 
-  class_linker_ = ClassLinker::Create(parsed_options->boot_class_path_);
+  class_linker_ = ClassLinker::Create(options->boot_class_path_);
 
   return true;
 }
diff --git a/src/runtime.h b/src/runtime.h
index 828e677..11c6f78 100644
--- a/src/runtime.h
+++ b/src/runtime.h
@@ -3,12 +3,13 @@
 #ifndef ART_SRC_RUNTIME_H_
 #define ART_SRC_RUNTIME_H_
 
+#include <set>
 #include <string>
 #include <utility>
 #include <vector>
 
-#include "jni.h"
 #include "globals.h"
+#include "jni_internal.h"
 #include "macros.h"
 #include "scoped_ptr.h"
 #include "stringpiece.h"
@@ -39,7 +40,7 @@
     jint (*hook_vfprintf_)(FILE* stream, const char* format, va_list ap);
     void (*hook_exit_)(jint status);
     void (*hook_abort_)();
-    std::vector<std::string> verbose_;
+    std::set<std::string> verbose_;
     std::vector<std::string> properties_;
 
    private:
@@ -77,7 +78,7 @@
     return class_linker_;
   }
 
-  JavaVM* GetJavaVM() const {
+  JavaVMExt* GetJavaVM() const {
     return java_vm_.get();
   }
 
@@ -93,7 +94,7 @@
 
   ClassLinker* class_linker_;
 
-  scoped_ptr<JavaVM> java_vm_;
+  scoped_ptr<JavaVMExt> java_vm_;
 
   // Hooks supported by JNI_CreateJavaVM
   jint (*vfprintf_)(FILE* stream, const char* format, va_list ap);
diff --git a/src/thread.cc b/src/thread.cc
index baa659c..edbc770 100644
--- a/src/thread.cc
+++ b/src/thread.cc
@@ -103,7 +103,7 @@
       PLOG(FATAL) << "pthread_setspecific failed";
   }
 
-  JavaVMExt* vm = reinterpret_cast<JavaVMExt*>(runtime->GetJavaVM());
+  JavaVMExt* vm = runtime->GetJavaVM();
   CHECK(vm != NULL);
   bool check_jni = vm->check_jni;
   thread->jni_env_ = reinterpret_cast<JNIEnv*>(new JNIEnvExt(thread, check_jni));