8212200: assert when shared java.lang.Object is redefined by JVMTI agent
authoriklam
Wed, 17 Oct 2018 15:57:10 -0700
changeset 52319 625f6c742392
parent 52318 124af9276e44
child 52320 26777794ade5
8212200: assert when shared java.lang.Object is redefined by JVMTI agent Reviewed-by: dholmes, jiangli, hseigel, lfoltan, sspitsyn
src/hotspot/share/classfile/javaClasses.cpp
src/hotspot/share/classfile/systemDictionary.cpp
src/hotspot/share/classfile/systemDictionary.hpp
src/hotspot/share/memory/filemap.cpp
src/hotspot/share/memory/heapShared.cpp
src/hotspot/share/prims/jvmtiExport.cpp
src/hotspot/share/prims/jvmtiExport.hpp
test/hotspot/jtreg/runtime/SharedArchiveFile/serviceability/ReplaceCriticalClasses.java
test/hotspot/jtreg/testlibrary/jvmti/libSimpleClassFileLoadHook.c
test/lib/jdk/test/lib/cds/CDSOptions.java
test/lib/jdk/test/lib/cds/CDSTestUtils.java
--- a/src/hotspot/share/classfile/javaClasses.cpp	Mon Oct 29 12:33:41 2018 -0700
+++ b/src/hotspot/share/classfile/javaClasses.cpp	Wed Oct 17 15:57:10 2018 -0700
@@ -50,6 +50,7 @@
 #include "oops/oop.inline.hpp"
 #include "oops/symbol.hpp"
 #include "oops/typeArrayOop.inline.hpp"
+#include "prims/jvmtiExport.hpp"
 #include "prims/resolvedMethodTable.hpp"
 #include "runtime/fieldDescriptor.inline.hpp"
 #include "runtime/frame.inline.hpp"
@@ -125,7 +126,7 @@
   if (ik == NULL) {
     ResourceMark rm;
     log_error(class)("Mismatch JDK version for field: %s type: %s", name_symbol->as_C_string(), signature_symbol->as_C_string());
-    vm_exit_during_initialization("Invalid layout of preloaded class");
+    vm_exit_during_initialization("Invalid layout of well-known class");
   }
 
   if (!ik->find_local_field(name_symbol, signature_symbol, &fd) || fd.is_static() != is_static) {
@@ -138,7 +139,7 @@
     LogStream ls(lt.error());
     ik->print_on(&ls);
 #endif //PRODUCT
-    vm_exit_during_initialization("Invalid layout of preloaded class: use -Xlog:class+load=info to see the origin of the problem class");
+    vm_exit_during_initialization("Invalid layout of well-known class: use -Xlog:class+load=info to see the origin of the problem class");
   }
   dest_offset = fd.offset();
 }
@@ -151,7 +152,7 @@
   if (name == NULL) {
     ResourceMark rm;
     log_error(class)("Name %s should be in the SymbolTable since its class is loaded", name_string);
-    vm_exit_during_initialization("Invalid layout of preloaded class", ik->external_name());
+    vm_exit_during_initialization("Invalid layout of well-known class", ik->external_name());
   }
   compute_offset(dest_offset, ik, name, signature_symbol, is_static);
 }
@@ -1196,7 +1197,7 @@
                                               Handle class_loader, Handle module,
                                               Handle protection_domain, TRAPS) {
   // Postpone restoring archived mirror until java.lang.Class is loaded. Please
-  // see more details in SystemDictionary::resolve_preloaded_classes().
+  // see more details in SystemDictionary::resolve_well_known_classes().
   if (!SystemDictionary::Class_klass_loaded()) {
     assert(fixup_mirror_list() != NULL, "fixup_mirror_list not initialized");
     fixup_mirror_list()->push(k);
@@ -4250,12 +4251,19 @@
 // Compute non-hard-coded field offsets of all the classes in this file
 void JavaClasses::compute_offsets() {
   if (UseSharedSpaces) {
-    return; // field offsets are loaded from archive
+    assert(JvmtiExport::is_early_phase() && !(JvmtiExport::should_post_class_file_load_hook() &&
+                                              JvmtiExport::has_early_class_hook_env()),
+           "JavaClasses::compute_offsets() must be called in early JVMTI phase.");
+    // None of the classes used by the rest of this function can be replaced by
+    // JMVTI ClassFileLoadHook.
+    // We are safe to use the archived offsets, which have already been restored
+    // by JavaClasses::serialize_offsets, without computing the offsets again.
+    return;
   }
 
   // We have already called the compute_offsets() of the
   // BASIC_JAVA_CLASSES_DO_PART1 classes (java_lang_String and java_lang_Class)
-  // earlier inside SystemDictionary::resolve_preloaded_classes()
+  // earlier inside SystemDictionary::resolve_well_known_classes()
   BASIC_JAVA_CLASSES_DO_PART2(DO_COMPUTE_OFFSETS);
 
   // generated interpreter code wants to know about the offsets we just computed:
@@ -4356,7 +4364,7 @@
     tty->print_cr("  name: %s, sig: %s, flags: %08x", fs.name()->as_C_string(), fs.signature()->as_C_string(), fs.access_flags().as_int());
   }
 #endif //PRODUCT
-  vm_exit_during_initialization("Invalid layout of preloaded class: use -Xlog:class+load=info to see the origin of the problem class");
+  vm_exit_during_initialization("Invalid layout of well-known class: use -Xlog:class+load=info to see the origin of the problem class");
   return -1;
 }
 
--- a/src/hotspot/share/classfile/systemDictionary.cpp	Mon Oct 29 12:33:41 2018 -0700
+++ b/src/hotspot/share/classfile/systemDictionary.cpp	Wed Oct 17 15:57:10 2018 -0700
@@ -67,7 +67,7 @@
 #include "oops/oop.inline.hpp"
 #include "oops/symbol.hpp"
 #include "oops/typeArrayKlass.hpp"
-#include "prims/jvmtiEnvBase.hpp"
+#include "prims/jvmtiExport.hpp"
 #include "prims/resolvedMethodTable.hpp"
 #include "prims/methodHandles.hpp"
 #include "runtime/arguments.hpp"
@@ -1489,8 +1489,7 @@
            !search_only_bootloader_append,
            "Attempt to load a class outside of boot loader's module path");
 
-    // Search the shared system dictionary for classes preloaded into the
-    // shared spaces.
+    // Search for classes in the CDS archive.
     InstanceKlass* k = NULL;
     {
 #if INCLUDE_CDS
@@ -1958,7 +1957,7 @@
   // Allocate private object used as system class loader lock
   _system_loader_lock_obj = oopFactory::new_intArray(0, CHECK);
   // Initialize basic classes
-  resolve_preloaded_classes(CHECK);
+  resolve_well_known_classes(CHECK);
 }
 
 // Compact table of directions on the initialization of klasses:
@@ -1971,6 +1970,19 @@
   0
 };
 
+#ifdef ASSERT
+bool SystemDictionary::is_well_known_klass(Symbol* class_name) {
+  int sid;
+  for (int i = 0; (sid = wk_init_info[i]) != 0; i++) {
+    Symbol* symbol = vmSymbols::symbol_at((vmSymbols::SID)sid);
+    if (class_name == symbol) {
+      return true;
+    }
+  }
+  return false;
+}
+#endif
+
 bool SystemDictionary::resolve_wk_klass(WKID id, TRAPS) {
   assert(id >= (int)FIRST_WKID && id < (int)WKID_LIMIT, "oob");
   int sid = wk_init_info[id - FIRST_WKID];
@@ -2002,8 +2014,8 @@
   start_id = limit_id;
 }
 
-void SystemDictionary::resolve_preloaded_classes(TRAPS) {
-  assert(WK_KLASS(Object_klass) == NULL, "preloaded classes should only be initialized once");
+void SystemDictionary::resolve_well_known_classes(TRAPS) {
+  assert(WK_KLASS(Object_klass) == NULL, "well-known classes should only be initialized once");
 
   // Create the ModuleEntry for java.base.  This call needs to be done here,
   // after vmSymbols::initialize() is called but before any classes are pre-loaded.
@@ -2071,7 +2083,8 @@
   WKID jsr292_group_end   = WK_KLASS_ENUM_NAME(VolatileCallSite_klass);
   resolve_wk_klasses_until(jsr292_group_start, scan, CHECK);
   resolve_wk_klasses_through(jsr292_group_end, scan, CHECK);
-  resolve_wk_klasses_until(NOT_JVMCI(WKID_LIMIT) JVMCI_ONLY(FIRST_JVMCI_WKID), scan, CHECK);
+  WKID last = NOT_JVMCI(WKID_LIMIT) JVMCI_ONLY(FIRST_JVMCI_WKID);
+  resolve_wk_klasses_until(last, scan, CHECK);
 
   _box_klasses[T_BOOLEAN] = WK_KLASS(Boolean_klass);
   _box_klasses[T_CHAR]    = WK_KLASS(Character_klass);
@@ -2088,6 +2101,17 @@
     Method* method = InstanceKlass::cast(ClassLoader_klass())->find_method(vmSymbols::checkPackageAccess_name(), vmSymbols::class_protectiondomain_signature());
     _has_checkPackageAccess = (method != NULL);
   }
+
+#ifdef ASSERT
+  if (UseSharedSpaces) {
+    assert(JvmtiExport::is_early_phase(),
+           "All well known classes must be resolved in JVMTI early phase");
+    for (int i = FIRST_WKID; i < last; i++) {
+      InstanceKlass* k = _well_known_klasses[i];
+      assert(k->is_shared(), "must not be replaced by JVMTI class file load hook");
+    }
+  }
+#endif
 }
 
 // Tells if a given klass is a box (wrapper class, such as java.lang.Integer).
--- a/src/hotspot/share/classfile/systemDictionary.hpp	Mon Oct 29 12:33:41 2018 -0700
+++ b/src/hotspot/share/classfile/systemDictionary.hpp	Wed Oct 17 15:57:10 2018 -0700
@@ -85,19 +85,20 @@
 class GCTimer;
 class OopStorage;
 
-// Certain classes are preloaded, such as java.lang.Object and java.lang.String.
-// They are all "well-known", in the sense that no class loader is allowed
+#define WK_KLASS_ENUM_NAME(kname)    kname##_knum
+
+// Certain classes, such as java.lang.Object and java.lang.String,
+// are "well-known", in the sense that no class loader is allowed
 // to provide a different definition.
 //
-// These klasses must all have names defined in vmSymbols.
-
-#define WK_KLASS_ENUM_NAME(kname)    kname##_knum
-
 // Each well-known class has a short klass name (like object_klass),
 // and a vmSymbol name (like java_lang_Object).
-// The order of these definitions is significant; it is the order in which
-// preloading is actually performed by resolve_preloaded_classes.
-
+//
+// The order of these definitions is significant: the classes are
+// resolved during early VM start-up by resolve_well_known_classes
+// in this order. Changing the order may require careful restructuring
+// of the VM start-up sequence.
+//
 #define WK_KLASSES_DO(do_klass)                                                                                 \
   /* well-known classes */                                                                                      \
   do_klass(Object_klass,                                java_lang_Object                                      ) \
@@ -127,7 +128,7 @@
   do_klass(IllegalMonitorStateException_klass,          java_lang_IllegalMonitorStateException                ) \
   do_klass(Reference_klass,                             java_lang_ref_Reference                               ) \
                                                                                                                 \
-  /* Preload ref klasses and set reference types */                                                             \
+  /* ref klasses and set reference types */                                                                     \
   do_klass(SoftReference_klass,                         java_lang_ref_SoftReference                           ) \
   do_klass(WeakReference_klass,                         java_lang_ref_WeakReference                           ) \
   do_klass(FinalReference_klass,                        java_lang_ref_FinalReference                          ) \
@@ -200,7 +201,7 @@
   /* support for stack dump lock analysis */                                                                    \
   do_klass(java_util_concurrent_locks_AbstractOwnableSynchronizer_klass, java_util_concurrent_locks_AbstractOwnableSynchronizer) \
                                                                                                                 \
-  /* Preload boxing klasses */                                                                                  \
+  /* boxing klasses */                                                                                          \
   do_klass(Boolean_klass,                               java_lang_Boolean                                     ) \
   do_klass(Character_klass,                             java_lang_Character                                   ) \
   do_klass(Float_klass,                                 java_lang_Float                                       ) \
@@ -391,7 +392,8 @@
   // Initialization
   static void initialize(TRAPS);
 
-  // Checked fast access to commonly used classes - mostly preloaded
+  // Checked fast access to the well-known classes -- so that you don't try to use them
+  // before they are resolved.
   static InstanceKlass* check_klass(InstanceKlass* k) {
     assert(k != NULL, "klass not loaded");
     return k;
@@ -435,6 +437,12 @@
     return check_klass(_box_klasses[t]);
   }
   static BasicType box_klass_type(Klass* k);  // inverse of box_klass
+#ifdef ASSERT
+  static bool is_well_known_klass(Klass* k) {
+    return is_well_known_klass(k->name());
+  }
+  static bool is_well_known_klass(Symbol* class_name);
+#endif
 
 protected:
   // Returns the class loader data to be used when looking up/updating the
@@ -695,8 +703,8 @@
                                   ClassLoaderData* loader_data,
                                   TRAPS);
 
-  // Resolve preloaded classes so they can be used like SystemDictionary::String_klass()
-  static void resolve_preloaded_classes(TRAPS);
+  // Resolve well-known classes so they can be used like SystemDictionary::String_klass()
+  static void resolve_well_known_classes(TRAPS);
 
   // Class loader constraints
   static void check_constraints(unsigned int hash,
@@ -707,7 +715,6 @@
                                 InstanceKlass* k, Handle loader,
                                 TRAPS);
 
-  // Variables holding commonly used klasses (preloaded)
   static InstanceKlass* _well_known_klasses[];
 
   // table of box klasses (int_klass, etc.)
--- a/src/hotspot/share/memory/filemap.cpp	Mon Oct 29 12:33:41 2018 -0700
+++ b/src/hotspot/share/memory/filemap.cpp	Wed Oct 17 15:57:10 2018 -0700
@@ -914,6 +914,19 @@
     return;
   }
 
+  if (JvmtiExport::should_post_class_file_load_hook() && JvmtiExport::has_early_class_hook_env()) {
+    ShouldNotReachHere(); // CDS should have been disabled.
+    // The archived objects are mapped at JVM start-up, but we don't know if
+    // j.l.String or j.l.Class might be replaced by the ClassFileLoadHook,
+    // which would make the archived String or mirror objects invalid. Let's be safe and not
+    // use the archived objects. These 2 classes are loaded during the JVMTI "early" stage.
+    //
+    // If JvmtiExport::has_early_class_hook_env() is false, the classes of some objects
+    // in the archived subgraphs may be replaced by the ClassFileLoadHook. But that's OK
+    // because we won't install an archived object subgraph if the klass of any of the
+    // referenced objects are replaced. See HeapShared::initialize_from_archived_subgraph().
+  }
+
   MemRegion heap_reserved = Universe::heap()->reserved_region();
 
   log_info(cds)("CDS archive was created with max heap size = " SIZE_FORMAT "M, and the following configuration:",
@@ -1224,6 +1237,15 @@
 bool FileMapInfo::initialize() {
   assert(UseSharedSpaces, "UseSharedSpaces expected.");
 
+  if (JvmtiExport::should_post_class_file_load_hook() && JvmtiExport::has_early_class_hook_env()) {
+    // CDS assumes that no classes resolved in SystemDictionary::resolve_well_known_classes
+    // are replaced at runtime by JVMTI ClassFileLoadHook. All of those classes are resolved
+    // during the JVMTI "early" stage, so we can still use CDS if
+    // JvmtiExport::has_early_class_hook_env() is false.
+    FileMapInfo::fail_continue("CDS is disabled because early JVMTI ClassFileLoadHook is in use.");
+    return false;
+  }
+
   if (!open_for_read()) {
     return false;
   }
--- a/src/hotspot/share/memory/heapShared.cpp	Mon Oct 29 12:33:41 2018 -0700
+++ b/src/hotspot/share/memory/heapShared.cpp	Wed Oct 17 15:57:10 2018 -0700
@@ -417,6 +417,11 @@
         Klass* resolved_k = SystemDictionary::resolve_or_null(
                                               (obj_k)->name(), THREAD);
         if (resolved_k != obj_k) {
+          assert(!SystemDictionary::is_well_known_klass(resolved_k),
+                 "shared well-known classes must not be replaced by JVMTI ClassFileLoadHook");
+          ResourceMark rm(THREAD);
+          log_info(cds, heap)("Failed to load subgraph because %s was not loaded from archive",
+                              resolved_k->external_name());
           return;
         }
         if ((obj_k)->is_instance_klass()) {
--- a/src/hotspot/share/prims/jvmtiExport.cpp	Mon Oct 29 12:33:41 2018 -0700
+++ b/src/hotspot/share/prims/jvmtiExport.cpp	Wed Oct 17 15:57:10 2018 -0700
@@ -994,6 +994,20 @@
   }
 };
 
+bool JvmtiExport::is_early_phase() {
+  return JvmtiEnvBase::get_phase() <= JVMTI_PHASE_PRIMORDIAL;
+}
+
+bool JvmtiExport::has_early_class_hook_env() {
+  JvmtiEnvIterator it;
+  for (JvmtiEnv* env = it.first(); env != NULL; env = it.next(env)) {
+    if (env->early_class_hook_env()) {
+      return true;
+    }
+  }
+  return false;
+}
+
 bool JvmtiExport::_should_post_class_file_load_hook = false;
 
 // this entry is for class file load hook on class load, redefine and retransform
--- a/src/hotspot/share/prims/jvmtiExport.hpp	Mon Oct 29 12:33:41 2018 -0700
+++ b/src/hotspot/share/prims/jvmtiExport.hpp	Wed Oct 17 15:57:10 2018 -0700
@@ -328,6 +328,8 @@
     JVMTI_ONLY(return _should_post_class_file_load_hook);
     NOT_JVMTI(return false;)
   }
+  static bool is_early_phase();
+  static bool has_early_class_hook_env();
   // Return true if the class was modified by the hook.
   static bool post_class_file_load_hook(Symbol* h_name, Handle class_loader,
                                         Handle h_protection_domain,
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/hotspot/jtreg/runtime/SharedArchiveFile/serviceability/ReplaceCriticalClasses.java	Wed Oct 17 15:57:10 2018 -0700
@@ -0,0 +1,222 @@
+/*
+ * Copyright (c) 2018, 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.
+ *
+ * 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.
+ *
+ */
+
+/*
+ * @test
+ * @summary Tests how CDS works when critical library classes are replaced with JVMTI ClassFileLoadHook
+ * @library /test/lib
+ * @requires vm.cds
+ * @build sun.hotspot.WhiteBox
+ * @run driver ClassFileInstaller -jar whitebox.jar sun.hotspot.WhiteBox
+ * @run main/othervm/native ReplaceCriticalClasses
+ */
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import jdk.test.lib.cds.CDSTestUtils;
+import jdk.test.lib.cds.CDSOptions;
+import jdk.test.lib.process.OutputAnalyzer;
+import sun.hotspot.WhiteBox;
+
+public class ReplaceCriticalClasses {
+    public static void main(String args[]) throws Throwable {
+        if (args.length == 0) {
+            launchChildProcesses();
+        } else if (args.length == 3 && args[0].equals("child")) {
+            Class klass = Class.forName(args[2].replace("/", "."));
+            if (args[1].equals("-shared")) {
+                testInChild(true, klass);
+            } else if (args[1].equals("-notshared")) {
+                testInChild(false, klass);
+            } else {
+                throw new RuntimeException("Unknown child exec option " + args[1]);
+            }
+            return;
+        } else {
+            throw new RuntimeException("Usage: @run main/othervm/native ReplaceCriticalClasses");
+        }
+    }
+
+    static void launchChildProcesses() throws Throwable {
+        String tests[] = {
+            // CDS should be disabled -- these critical classes will be replaced
+            // because JvmtiExport::early_class_hook_env() is true.
+            "-early -notshared java/lang/Object",
+            "-early -notshared java/lang/String",
+            "-early -notshared java/lang/Cloneable",
+            "-early -notshared java/io/Serializable",
+
+            // CDS should not be disabled -- these critical classes cannot be replaced because
+            // JvmtiExport::early_class_hook_env() is false.
+            "java/lang/Object",
+            "java/lang/String",
+            "java/lang/Cloneable",
+            "java/io/Serializable",
+
+            // Try to replace classes that are used by the archived subgraph graphs.
+            "-subgraph java/util/ArrayList",
+            "-subgraph java/lang/module/ResolvedModule",
+
+            // Replace classes that are loaded after JVMTI_PHASE_PRIMORDIAL. It's OK to replace such
+            // classes even when CDS is enabled. Nothing bad should happen.
+            "-notshared jdk/internal/vm/PostVMInitHook",
+            "-notshared java/util/Locale",
+            "-notshared sun/util/locale/BaseLocale",
+            "-notshared java/lang/Readable",
+        };
+
+        int n = 0;
+        for (String s : tests) {
+            System.out.println("Test case[" + (n++) + "] = \"" + s + "\"");
+            String args[] = s.split("\\s+"); // split by space character
+            launchChild(args);
+        }
+    }
+
+    static void launchChild(String args[]) throws Throwable {
+        if (args.length < 1) {
+            throw new RuntimeException("Invalid test case. Should be <-early> <-subgraph> <-notshared> klassName");
+        }
+        String klassName = null;
+        String early = "";
+        boolean subgraph = false;
+        String shared = "-shared";
+
+        for (int i=0; i<args.length-1; i++) {
+            String opt = args[i];
+            if (opt.equals("-early")) {
+                early = "-early,";
+            } else if (opt.equals("-subgraph")) {
+                subgraph = true;
+            } else if (opt.equals("-notshared")) {
+                shared = opt;
+            } else {
+                throw new RuntimeException("Unknown option: " + opt);
+            }
+        }
+        klassName = args[args.length-1];
+        Class.forName(klassName.replace("/", ".")); // make sure it's a valid class
+
+        // We will pass an option like "-agentlib:SimpleClassFileLoadHook=java/util/Locale,XXX,XXX".
+        // The SimpleClassFileLoadHook agent would attempt to hook the java/util/Locale class
+        // but leave the class file bytes unchanged (it replaces all bytes "XXX" with "XXX", i.e.,
+        // a no-op). JVMTI doesn't check the class file bytes returned by the agent, so as long
+        // as the agent returns a buffer, it will not load the class from CDS, and will instead
+        // load the class by parsing the buffer.
+        //
+        // Note that for safety we don't change the contents of the class file bytes. If in the
+        // future JVMTI starts checking the contents of the class file bytes, this test would need
+        // to be updated. (You'd see the test case with java/util/Locale staring to fail).
+        String agent = "-agentlib:SimpleClassFileLoadHook=" + early + klassName + ",XXX,XXX";
+
+        CDSOptions opts = (new CDSOptions())
+            .setXShareMode("auto")
+            .setUseSystemArchive(true)
+            .setUseVersion(false)
+            .addSuffix("-showversion",
+                       "-Xlog:cds",
+                       "-XX:+UnlockDiagnosticVMOptions",
+                       agent,
+                       "-XX:+WhiteBoxAPI",
+                       "-Xbootclasspath/a:" + ClassFileInstaller.getJarPath("whitebox.jar"));
+
+        if (subgraph) {
+            opts.addSuffix("-Xlog:cds+heap",
+                           "-Xlog:class+load");
+        }
+
+        opts.addSuffix("ReplaceCriticalClasses",
+                       "child",
+                       shared,
+                       klassName);
+
+        final boolean expectDisable = !early.equals("");
+        final boolean checkSubgraph = subgraph;
+        CDSTestUtils.run(opts).assertNormalExit(out -> {
+                if (expectDisable) {
+                    out.shouldContain("UseSharedSpaces: CDS is disabled because early JVMTI ClassFileLoadHook is in use.");
+                    System.out.println("CDS disabled as expected");
+                }
+                if (checkSubgraph) {
+                    // As of 2018/10/21 the classes in the archived subgraphs won't be
+                    // replaced because all archived subgraphs were loaded in JVMTI_PHASE_PRIMORDIAL.
+                    //
+                    // This is the first class to be loaded after JVMTI has exited JVMTI_PHASE_PRIMORDIAL.
+                    // Make sure no subgraphs are loaded afterwards.
+                    //
+                    // Can't use out.shouldNotMatch() because that doesn't match across multiple lines.
+                    String firstNonPrimordialClass = "jdk.jfr.internal.EventWriter";
+                    String regexp = firstNonPrimordialClass + ".*initialize_from_archived_subgraph";
+                    Pattern regex = Pattern.compile(regexp, Pattern.DOTALL);
+                    Matcher matcher = regex.matcher(out.getStdout());
+                    if (matcher.find()) {
+                        out.reportDiagnosticSummary();
+                        throw new RuntimeException("'" + regexp
+                                                   + "' found in stdout: '" + matcher.group() + "' \n");
+                    }
+                }
+            });
+    }
+
+    static void testInChild(boolean shouldBeShared, Class klass) {
+        WhiteBox wb = WhiteBox.getWhiteBox();
+
+        if (shouldBeShared && !wb.isSharedClass(klass)) {
+            throw new RuntimeException(klass + " should be shared but but actually is not.");
+        }
+        if (!shouldBeShared && wb.isSharedClass(klass)) {
+            throw new RuntimeException(klass + " should not be shared but actually is.");
+        }
+        System.out.println("wb.isSharedClass(klass): " + wb.isSharedClass(klass) + " == " + shouldBeShared);
+
+        String strings[] = {
+            // interned strings from j.l.Object
+            "@",
+            "nanosecond timeout value out of range",
+            "timeoutMillis value is negative",
+
+            // interned strings from j.l.Integer
+            "0",
+            "0X",
+            "0x",
+            "int"
+        };
+
+        // Make sure the interned string table is same
+        for (String s : strings) {
+            String i = s.intern();
+            if (s != i) {
+                throw new RuntimeException("Interned string mismatch: \"" + s + "\" @ " + System.identityHashCode(s) +
+                                           " vs \"" + i + "\" @ " + System.identityHashCode(i));
+            }
+        }
+        // We have tried to use ClassFileLoadHook to replace critical library classes (which may
+        // may not have succeeded, depending on whether the agent has requested
+        // can_generate_all_class_hook_events/can_generate_early_class_hook_events capabilities).
+        //
+        // In any case, the JVM should have started properly (perhaps with CDS disabled) and
+        // the above operations should succeed.
+        System.out.println("If I can come to here without crashing, things should be OK");
+    }
+}
--- a/test/hotspot/jtreg/testlibrary/jvmti/libSimpleClassFileLoadHook.c	Mon Oct 29 12:33:41 2018 -0700
+++ b/test/hotspot/jtreg/testlibrary/jvmti/libSimpleClassFileLoadHook.c	Wed Oct 17 15:57:10 2018 -0700
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2016, 2018, 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
@@ -91,17 +91,23 @@
         *new_class_data_len = class_data_len;
         *new_class_data = new_data;
 
-        fprintf(stderr, "Rewriting done. Replaced %d occurrence(s)\n", count);
+        fprintf(stderr, "Rewriting done. Replaced %d occurrence(s) of \"%s\" to \"%s\"\n", count, FROM, TO);
       }
     }
 }
 
+static int early = 0;
+
 static jint init_options(char *options) {
   char* class_name;
   char* from;
   char* to;
 
   fprintf(stderr, "Agent library loaded with options = %s\n", options);
+  if (options != NULL && strncmp(options, "-early,", 7) == 0) {
+    early = 1;
+    options += 7;
+  }
   if ((class_name = options) != NULL &&
       (from = strchr(class_name, ',')) != NULL && (from[1] != 0)) {
     *from = 0;
@@ -132,6 +138,7 @@
 
 static jint Agent_Initialize(JavaVM *jvm, char *options, void *reserved) {
   int rc;
+  jvmtiCapabilities caps;
 
   if ((rc = (*jvm)->GetEnv(jvm, (void **)&jvmti, JVMTI_VERSION_1_1)) != JNI_OK) {
     fprintf(stderr, "Unable to create jvmtiEnv, GetEnv failed, error = %d\n", rc);
@@ -141,6 +148,19 @@
     return JNI_ERR;
   }
 
+  memset(&caps, 0, sizeof(caps));
+
+  caps.can_redefine_classes = 1;
+  if (early) {
+    fprintf(stderr, "can_generate_all_class_hook_events/can_generate_early_vmstart/can_generate_early_class_hook_events == 1\n");
+    caps.can_generate_all_class_hook_events = 1;
+    caps.can_generate_early_class_hook_events = 1;
+  }
+  if ((rc = (*jvmti)->AddCapabilities(jvmti, &caps)) != JNI_OK) {
+    fprintf(stderr, "AddCapabilities failed, error = %d\n", rc);
+    return JNI_ERR;
+  }
+
   (void) memset(&callbacks, 0, sizeof(callbacks));
   callbacks.ClassFileLoadHook = &ClassFileLoadHook;
   if ((rc = (*jvmti)->SetEventCallbacks(jvmti, &callbacks, sizeof(callbacks))) != JNI_OK) {
--- a/test/lib/jdk/test/lib/cds/CDSOptions.java	Mon Oct 29 12:33:41 2018 -0700
+++ b/test/lib/jdk/test/lib/cds/CDSOptions.java	Wed Oct 17 15:57:10 2018 -0700
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2017, 2018, 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
@@ -31,6 +31,7 @@
     public String archiveName;
     public ArrayList<String> prefix = new ArrayList<String>();
     public ArrayList<String> suffix = new ArrayList<String>();
+    public boolean useSystemArchive = false;
 
     // Indicate whether to append "-version" when using CDS Archive.
     // Most of tests will use '-version'
@@ -68,4 +69,9 @@
         this.useVersion = use;
         return this;
     }
+
+    public CDSOptions setUseSystemArchive(boolean use) {
+        this.useSystemArchive = use;
+        return this;
+    }
 }
--- a/test/lib/jdk/test/lib/cds/CDSTestUtils.java	Mon Oct 29 12:33:41 2018 -0700
+++ b/test/lib/jdk/test/lib/cds/CDSTestUtils.java	Wed Oct 17 15:57:10 2018 -0700
@@ -82,21 +82,21 @@
      *
      * Instead, the test case should be written as
      *
-     *      CCDSTestUtils.run(args).assertNormalExit("Hi");
+     *      CDSTestUtils.run(args).assertNormalExit("Hi");
      *
      * EXAMPLES/HOWTO
      *
      * 1. For simple substring matching:
      *
-     *      CCDSTestUtils.run(args).assertNormalExit("Hi");
-     *      CCDSTestUtils.run(args).assertNormalExit("a", "b", "x");
-     *      CCDSTestUtils.run(args).assertAbnormalExit("failure 1", "failure2");
+     *      CDSTestUtils.run(args).assertNormalExit("Hi");
+     *      CDSTestUtils.run(args).assertNormalExit("a", "b", "x");
+     *      CDSTestUtils.run(args).assertAbnormalExit("failure 1", "failure2");
      *
      * 2. For more complex output matching: using Lambda expressions
      *
-     *      CCDSTestUtils.run(args)
+     *      CDSTestUtils.run(args)
      *         .assertNormalExit(output -> output.shouldNotContain("this should not be printed");
-     *      CCDSTestUtils.run(args)
+     *      CDSTestUtils.run(args)
      *         .assertAbnormalExit(output -> {
      *             output.shouldNotContain("this should not be printed");
      *             output.shouldHaveExitValue(123);
@@ -104,13 +104,13 @@
      *
      * 3. Chaining several checks:
      *
-     *      CCDSTestUtils.run(args)
+     *      CDSTestUtils.run(args)
      *         .assertNormalExit(output -> output.shouldNotContain("this should not be printed")
      *         .assertNormalExit("should have this", "should have that");
      *
      * 4. [Rare use case] if a test sometimes exit normally, and sometimes abnormally:
      *
-     *      CCDSTestUtils.run(args)
+     *      CDSTestUtils.run(args)
      *         .ifNormalExit("ths string is printed when exiting with 0")
      *         .ifAbNormalExit("ths string is printed when exiting with 1");
      *
@@ -388,9 +388,11 @@
         cmd.add("-Xshare:" + opts.xShareMode);
         cmd.add("-Dtest.timeout.factor=" + TestTimeoutFactor);
 
-        if (opts.archiveName == null)
-            opts.archiveName = getDefaultArchiveName();
-        cmd.add("-XX:SharedArchiveFile=" + opts.archiveName);
+        if (!opts.useSystemArchive) {
+            if (opts.archiveName == null)
+                opts.archiveName = getDefaultArchiveName();
+            cmd.add("-XX:SharedArchiveFile=" + opts.archiveName);
+        }
 
         if (opts.useVersion)
             cmd.add("-version");