8243506: SharedBaseAddress is ignored by -Xshare:dump

Reviewed-by: stuefe, ccheung
diff --git a/src/hotspot/share/classfile/compactHashtable.cpp b/src/hotspot/share/classfile/compactHashtable.cpp
index 59ed933..13ad65f 100644
--- a/src/hotspot/share/classfile/compactHashtable.cpp
+++ b/src/hotspot/share/classfile/compactHashtable.cpp
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 1997, 2019, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1997, 2020, 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 @@
 #include "memory/heapShared.inline.hpp"
 #include "memory/metadataFactory.hpp"
 #include "memory/metaspaceShared.hpp"
+#include "runtime/globals.hpp"
 #include "runtime/vmThread.hpp"
 #include "utilities/numberSeq.hpp"
 #include <sys/stat.h>
@@ -212,11 +213,13 @@
 void SimpleCompactHashtable::serialize_header(SerializeClosure* soc) {
   // NOTE: if you change this function, you MUST change the number 5 in
   // calculate_header_size() accordingly.
-  soc->do_ptr((void**)&_base_address);
   soc->do_u4(&_entry_count);
   soc->do_u4(&_bucket_count);
   soc->do_ptr((void**)&_buckets);
   soc->do_ptr((void**)&_entries);
+  if (soc->reading()) {
+    _base_address = (address)SharedBaseAddress;
+  }
 }
 #endif // INCLUDE_CDS
 
diff --git a/src/hotspot/share/memory/archiveUtils.cpp b/src/hotspot/share/memory/archiveUtils.cpp
index bb267af..932df085 100644
--- a/src/hotspot/share/memory/archiveUtils.cpp
+++ b/src/hotspot/share/memory/archiveUtils.cpp
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2019, 2020, 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
@@ -58,6 +58,12 @@
 
   if (_ptr_base <= ptr_loc && ptr_loc < _ptr_end) {
     address value = *ptr_loc;
+    // We don't want any pointer that points to very bottom of the archive, otherwise when
+    // MetaspaceShared::default_base_address()==0, we can't distinguish between a pointer
+    // to nothing (NULL) vs a pointer to an objects that happens to be at the very bottom
+    // of the archive.
+    assert(value != (address)_ptr_base, "don't point to the bottom of the archive");
+
     if (value != NULL) {
       assert(uintx(ptr_loc) % sizeof(intptr_t) == 0, "pointers must be stored in aligned addresses");
       size_t idx = ptr_loc - _ptr_base;
diff --git a/src/hotspot/share/memory/archiveUtils.inline.hpp b/src/hotspot/share/memory/archiveUtils.inline.hpp
index 52ae6dd..e2bffce 100644
--- a/src/hotspot/share/memory/archiveUtils.inline.hpp
+++ b/src/hotspot/share/memory/archiveUtils.inline.hpp
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2019, 2020, 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
@@ -52,6 +52,7 @@
   }
 
   address new_ptr = old_ptr + _delta;
+  assert(new_ptr != NULL, "don't point to the bottom of the archive"); // See ArchivePtrMarker::mark_pointer().
   assert(_valid_new_base <= new_ptr && new_ptr < _valid_new_end, "must be");
 
   DEBUG_ONLY(log_trace(cds, reloc)("Patch2: @%8d [" PTR_FORMAT "] " PTR_FORMAT " -> " PTR_FORMAT,
diff --git a/src/hotspot/share/memory/dynamicArchive.cpp b/src/hotspot/share/memory/dynamicArchive.cpp
index c0a591a..25aa6e1 100644
--- a/src/hotspot/share/memory/dynamicArchive.cpp
+++ b/src/hotspot/share/memory/dynamicArchive.cpp
@@ -966,13 +966,13 @@
   if (addr_delta == 0) {
     ArchivePtrMarker::compact(relocatable_base, relocatable_end);
   } else {
-    // The base archive is NOT mapped at Arguments::default_SharedBaseAddress() (due to ASLR).
+    // The base archive is NOT mapped at MetaspaceShared::requested_base_address() (due to ASLR).
     // This means that the current content of the dynamic archive is based on a random
     // address. Let's relocate all the pointers, so that it can be mapped to
-    // Arguments::default_SharedBaseAddress() without runtime relocation.
+    // MetaspaceShared::requested_base_address() without runtime relocation.
     //
     // Note: both the base and dynamic archive are written with
-    // FileMapHeader::_shared_base_address == Arguments::default_SharedBaseAddress()
+    // FileMapHeader::_requested_base_address == MetaspaceShared::requested_base_address()
 
     // Patch all pointers that are marked by ptrmap within this region,
     // where we have just dumped all the metaspace data.
@@ -992,7 +992,7 @@
 
     // after patching, the pointers must point inside this range
     // (the requested location of the archive, as mapped at runtime).
-    address valid_new_base = (address)Arguments::default_SharedBaseAddress();
+    address valid_new_base = (address)MetaspaceShared::requested_base_address();
     address valid_new_end  = valid_new_base + base_plus_top_size;
 
     log_debug(cds)("Relocating archive from [" INTPTR_FORMAT " - " INTPTR_FORMAT "] to "
@@ -1020,7 +1020,7 @@
   const char* archive_name = Arguments::GetSharedDynamicArchivePath();
   dynamic_info->open_for_write(archive_name);
   MetaspaceShared::write_core_archive_regions(dynamic_info, NULL, NULL);
-  dynamic_info->set_final_requested_base((char*)Arguments::default_SharedBaseAddress());
+  dynamic_info->set_final_requested_base((char*)MetaspaceShared::requested_base_address());
   dynamic_info->set_header_crc(dynamic_info->compute_header_crc());
   dynamic_info->write_header();
   dynamic_info->close();
diff --git a/src/hotspot/share/memory/metaspaceShared.cpp b/src/hotspot/share/memory/metaspaceShared.cpp
index 00f624a..0bf13a8 100644
--- a/src/hotspot/share/memory/metaspaceShared.cpp
+++ b/src/hotspot/share/memory/metaspaceShared.cpp
@@ -88,6 +88,7 @@
 size_t MetaspaceShared::_i2i_entry_code_buffers_size = 0;
 void* MetaspaceShared::_shared_metaspace_static_top = NULL;
 intx MetaspaceShared::_relocation_delta;
+char* MetaspaceShared::_requested_base_address;
 
 // The CDS archive is divided into the following regions:
 //     mc  - misc code (the method entry trampolines, c++ vtables)
@@ -240,33 +241,53 @@
 
 size_t MetaspaceShared::reserved_space_alignment() { return os::vm_allocation_granularity(); }
 
+static bool shared_base_valid(char* shared_base) {
 #ifdef _LP64
-// Check SharedBaseAddress for validity. At this point, os::init() must
-//  have been ran.
-static void check_SharedBaseAddress() {
-  SharedBaseAddress = align_up(SharedBaseAddress,
-                               MetaspaceShared::reserved_space_alignment());
-  if (!CompressedKlassPointers::is_valid_base((address)SharedBaseAddress)) {
-    log_warning(cds)("SharedBaseAddress=" PTR_FORMAT " is invalid for this "
-                     "platform, option will be ignored.",
-                     p2i((address)SharedBaseAddress));
-    SharedBaseAddress = Arguments::default_SharedBaseAddress();
-  }
-}
+  return CompressedKlassPointers::is_valid_base((address)shared_base);
+#else
+  return true;
 #endif
+}
+
+static bool shared_base_too_high(char* shared_base, size_t cds_total) {
+  if (SharedBaseAddress != 0 && shared_base < (char*)SharedBaseAddress) {
+    // SharedBaseAddress is very high (e.g., 0xffffffffffffff00) so
+    // align_up(SharedBaseAddress, MetaspaceShared::reserved_space_alignment()) has wrapped around.
+    return true;
+  }
+  if (max_uintx - uintx(shared_base) < uintx(cds_total)) {
+    // The end of the archive will wrap around
+    return true;
+  }
+
+  return false;
+}
+
+static char* compute_shared_base(size_t cds_total) {
+  char* shared_base = (char*)align_up((char*)SharedBaseAddress, MetaspaceShared::reserved_space_alignment());
+  const char* err = NULL;
+  if (shared_base_too_high(shared_base, cds_total)) {
+    err = "too high";
+  } else if (!shared_base_valid(shared_base)) {
+    err = "invalid for this platform";
+  }
+  if (err) {
+    log_warning(cds)("SharedBaseAddress (" INTPTR_FORMAT ") is %s. Reverted to " INTPTR_FORMAT,
+                     p2i((void*)SharedBaseAddress), err,
+                     p2i((void*)Arguments::default_SharedBaseAddress()));
+    SharedBaseAddress = Arguments::default_SharedBaseAddress();
+    shared_base = (char*)align_up((char*)SharedBaseAddress, MetaspaceShared::reserved_space_alignment());
+  }
+  assert(!shared_base_too_high(shared_base, cds_total) && shared_base_valid(shared_base), "Sanity");
+  return shared_base;
+}
 
 void MetaspaceShared::initialize_dumptime_shared_and_meta_spaces() {
   assert(DumpSharedSpaces, "should be called for dump time only");
 
-#ifdef _LP64
-  check_SharedBaseAddress();
-#endif
-
   const size_t reserve_alignment = MetaspaceShared::reserved_space_alignment();
-  char* shared_base = (char*)align_up((char*)SharedBaseAddress, reserve_alignment);
 
 #ifdef _LP64
-  assert(CompressedKlassPointers::is_valid_base((address)shared_base), "Sanity");
   // On 64-bit VM we reserve a 4G range and, if UseCompressedClassPointers=1,
   //  will use that to house both the archives and the ccs. See below for
   //  details.
@@ -278,6 +299,9 @@
   size_t cds_total = align_down(256*M, reserve_alignment);
 #endif
 
+  char* shared_base = compute_shared_base(cds_total);
+  _requested_base_address = shared_base;
+
   // Whether to use SharedBaseAddress as attach address.
   bool use_requested_base = true;
 
@@ -398,6 +422,10 @@
   log_info(cds)("Allocated shared space: " SIZE_FORMAT " bytes at " PTR_FORMAT,
                 _shared_rs.size(), p2i(_shared_rs.base()));
 
+  // We don't want any valid object to be at the very bottom of the archive.
+  // See ArchivePtrMarker::mark_pointer().
+  MetaspaceShared::misc_code_space_alloc(16);
+
   size_t symbol_rs_size = LP64_ONLY(3 * G) NOT_LP64(128 * M);
   _symbol_rs = ReservedSpace(symbol_rs_size);
   if (!_symbol_rs.is_reserved()) {
@@ -1200,7 +1228,7 @@
   void print_bitmap_region_stats(size_t size, size_t total_size);
   void print_heap_region_stats(GrowableArray<MemRegion> *heap_mem,
                                const char *name, size_t total_size);
-  void relocate_to_default_base_address(CHeapBitMap* ptrmap);
+  void relocate_to_requested_base_address(CHeapBitMap* ptrmap);
 
 public:
 
@@ -1568,18 +1596,18 @@
   }
 }
 
-void VM_PopulateDumpSharedSpace::relocate_to_default_base_address(CHeapBitMap* ptrmap) {
+void VM_PopulateDumpSharedSpace::relocate_to_requested_base_address(CHeapBitMap* ptrmap) {
   intx addr_delta = MetaspaceShared::final_delta();
   if (addr_delta == 0) {
     ArchivePtrMarker::compact((address)SharedBaseAddress, (address)_ro_region.top());
   } else {
-    // We are not able to reserve space at Arguments::default_SharedBaseAddress() (due to ASLR).
+    // We are not able to reserve space at MetaspaceShared::requested_base_address() (due to ASLR).
     // This means that the current content of the archive is based on a random
     // address. Let's relocate all the pointers, so that it can be mapped to
-    // Arguments::default_SharedBaseAddress() without runtime relocation.
+    // MetaspaceShared::requested_base_address() without runtime relocation.
     //
     // Note: both the base and dynamic archive are written with
-    // FileMapHeader::_shared_base_address == Arguments::default_SharedBaseAddress()
+    // FileMapHeader::_requested_base_address == MetaspaceShared::requested_base_address()
 
     // Patch all pointers that are marked by ptrmap within this region,
     // where we have just dumped all the metaspace data.
@@ -1594,7 +1622,7 @@
 
     // after patching, the pointers must point inside this range
     // (the requested location of the archive, as mapped at runtime).
-    address valid_new_base = (address)Arguments::default_SharedBaseAddress();
+    address valid_new_base = (address)MetaspaceShared::requested_base_address();
     address valid_new_end  = valid_new_base + size;
 
     log_debug(cds)("Relocating archive from [" INTPTR_FORMAT " - " INTPTR_FORMAT " ] to "
@@ -1681,9 +1709,9 @@
   memset(MetaspaceShared::i2i_entry_code_buffers(), 0,
          MetaspaceShared::i2i_entry_code_buffers_size());
 
-  // relocate the data so that it can be mapped to Arguments::default_SharedBaseAddress()
+  // relocate the data so that it can be mapped to MetaspaceShared::requested_base_address()
   // without runtime relocation.
-  relocate_to_default_base_address(&ptrmap);
+  relocate_to_requested_base_address(&ptrmap);
 
   // Create and write the archive file that maps the shared spaces.
 
@@ -1706,7 +1734,7 @@
                                         MetaspaceShared::first_open_archive_heap_region,
                                         MetaspaceShared::max_open_archive_heap_region);
 
-  mapinfo->set_final_requested_base((char*)Arguments::default_SharedBaseAddress());
+  mapinfo->set_final_requested_base((char*)MetaspaceShared::requested_base_address());
   mapinfo->set_header_crc(mapinfo->compute_header_crc());
   mapinfo->write_header();
   print_region_stats(mapinfo);
@@ -2162,6 +2190,7 @@
     } else {
       FileMapInfo::set_shared_path_table(static_mapinfo);
     }
+    _requested_base_address = static_mapinfo->requested_base_address();
   } else {
     set_shared_metaspace_range(NULL, NULL, NULL);
     UseSharedSpaces = false;
@@ -2209,6 +2238,11 @@
 //  false = map at an alternative address picked by OS.
 MapArchiveResult MetaspaceShared::map_archives(FileMapInfo* static_mapinfo, FileMapInfo* dynamic_mapinfo,
                                                bool use_requested_addr) {
+  if (use_requested_addr && static_mapinfo->requested_base_address() == NULL) {
+    log_info(cds)("Archive(s) were created with -XX:SharedBaseAddress=0. Always map at os-selected address.");
+    return MAP_ARCHIVE_MMAP_FAILURE;
+  }
+
   PRODUCT_ONLY(if (ArchiveRelocationMode == 1 && use_requested_addr) {
       // For product build only -- this is for benchmarking the cost of doing relocation.
       // For debug builds, the check is done below, after reserving the space, for better test coverage
@@ -2660,11 +2694,11 @@
                                 "Please reduce the number of shared classes.");
 }
 
-// This is used to relocate the pointers so that the archive can be mapped at
-// Arguments::default_SharedBaseAddress() without runtime relocation.
+// This is used to relocate the pointers so that the base archive can be mapped at
+// MetaspaceShared::requested_base_address() without runtime relocation.
 intx MetaspaceShared::final_delta() {
-  return intx(Arguments::default_SharedBaseAddress())  // We want the archive to be mapped to here at runtime
-       - intx(SharedBaseAddress);                      // .. but the archive is mapped at here at dump time
+  return intx(MetaspaceShared::requested_base_address())  // We want the base archive to be mapped to here at runtime
+       - intx(SharedBaseAddress);                         // .. but the base archive is mapped at here at dump time
 }
 
 void MetaspaceShared::print_on(outputStream* st) {
diff --git a/src/hotspot/share/memory/metaspaceShared.hpp b/src/hotspot/share/memory/metaspaceShared.hpp
index ad3ba65..1c272ec 100644
--- a/src/hotspot/share/memory/metaspaceShared.hpp
+++ b/src/hotspot/share/memory/metaspaceShared.hpp
@@ -183,6 +183,7 @@
   static size_t  _core_spaces_size;
   static void* _shared_metaspace_static_top;
   static intx _relocation_delta;
+  static char* _requested_base_address;
  public:
   enum {
     // core archive spaces
@@ -353,6 +354,12 @@
   static intptr_t* fix_cpp_vtable_for_dynamic_archive(MetaspaceObj::Type msotype, address obj);
   static void initialize_ptr_marker(CHeapBitMap* ptrmap);
 
+  // This is the base address as specified by -XX:SharedBaseAddress during -Xshare:dump.
+  // Both the base/top archives are written using this as their base address.
+  static char* requested_base_address() {
+    return _requested_base_address;
+  }
+
   // Non-zero if the archive(s) need to be mapped a non-default location due to ASLR.
   static intx relocation_delta() { return _relocation_delta; }
   static intx final_delta();
diff --git a/src/hotspot/share/runtime/arguments.cpp b/src/hotspot/share/runtime/arguments.cpp
index 4788a21..ba5a1b7 100644
--- a/src/hotspot/share/runtime/arguments.cpp
+++ b/src/hotspot/share/runtime/arguments.cpp
@@ -85,7 +85,7 @@
 bool   Arguments::_ClipInlining                 = ClipInlining;
 intx   Arguments::_Tier3InvokeNotifyFreqLog     = Tier3InvokeNotifyFreqLog;
 intx   Arguments::_Tier4InvocationThreshold     = Tier4InvocationThreshold;
-size_t Arguments::_SharedBaseAddress            = SharedBaseAddress;
+size_t Arguments::_default_SharedBaseAddress    = SharedBaseAddress;
 
 bool   Arguments::_enable_preview               = false;
 
@@ -2281,8 +2281,8 @@
     Arguments::_Tier4InvocationThreshold = Tier4InvocationThreshold;
   }
 
-  // CDS dumping always write the archive to the default value of SharedBaseAddress.
-  Arguments::_SharedBaseAddress = SharedBaseAddress;
+  // Remember the default value of SharedBaseAddress.
+  Arguments::_default_SharedBaseAddress = SharedBaseAddress;
 
   // Setup flags for mixed which is the default
   set_mode_flags(_mixed);
diff --git a/src/hotspot/share/runtime/arguments.hpp b/src/hotspot/share/runtime/arguments.hpp
index 4bea8c1..c652ee3 100644
--- a/src/hotspot/share/runtime/arguments.hpp
+++ b/src/hotspot/share/runtime/arguments.hpp
@@ -489,7 +489,7 @@
 
   static char*  SharedArchivePath;
   static char*  SharedDynamicArchivePath;
-  static size_t _SharedBaseAddress; // The default value specified in globals.hpp
+  static size_t _default_SharedBaseAddress; // The default value specified in globals.hpp
   static int num_archives(const char* archive_path) NOT_CDS_RETURN_(0);
   static void extract_shared_archive_paths(const char* archive_path,
                                          char** base_archive_path,
@@ -572,7 +572,7 @@
 
   static const char* GetSharedArchivePath() { return SharedArchivePath; }
   static const char* GetSharedDynamicArchivePath() { return SharedDynamicArchivePath; }
-  static size_t default_SharedBaseAddress() { return _SharedBaseAddress; }
+  static size_t default_SharedBaseAddress() { return _default_SharedBaseAddress; }
   // Java launcher properties
   static void process_sun_java_launcher_properties(JavaVMInitArgs* args);
 
diff --git a/test/hotspot/jtreg/runtime/cds/SharedBaseAddress.java b/test/hotspot/jtreg/runtime/cds/SharedBaseAddress.java
index 1bf15fe..12b55db 100644
--- a/test/hotspot/jtreg/runtime/cds/SharedBaseAddress.java
+++ b/test/hotspot/jtreg/runtime/cds/SharedBaseAddress.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2014, 2019, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2014, 2020, 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
@@ -40,7 +40,13 @@
     private static final String[] testTable = {
         "1g", "8g", "64g","512g", "4t",
         "32t", "128t", "0",
-        "1", "64k", "64M"
+        "1", "64k", "64M",
+        "0xfffffffffff00000", // archive top wraps around 64-bit address space
+        "0xfff80000",         // archive top wraps around 32-bit address space
+        "0xffffffffffffffff", // archive bottom wraps around 64-bit address space -- due to align_up()
+        "0xffffffff",         // archive bottom wraps around 32-bit address space -- due to align_up()
+        "0x00007ffffff00000", // end of archive will go past the end of user space on linux/x64
+        "0",                  // always let OS pick the base address at runtime (ASLR for CDS archive)
     };
 
     public static void main(String[] args) throws Exception {
@@ -50,10 +56,16 @@
             System.out.println("sharedBaseAddress = " + testEntry);
             CDSOptions opts = (new CDSOptions())
                 .setArchiveName(filename)
-                .addPrefix("-XX:SharedBaseAddress=" + testEntry);
+                .addPrefix("-XX:SharedBaseAddress=" + testEntry)
+                .addPrefix("-Xlog:cds=debug")
+                .addPrefix("-Xlog:cds+reloc=debug");
 
             CDSTestUtils.createArchiveAndCheck(opts);
-            CDSTestUtils.runWithArchiveAndCheck(opts);
+            OutputAnalyzer out = CDSTestUtils.runWithArchiveAndCheck(opts);
+            if (testEntry.equals("0")) {
+              out.shouldContain("Archive(s) were created with -XX:SharedBaseAddress=0. Always map at os-selected address.")
+                 .shouldContain("Try to map archive(s) at an alternative address");
+            }
         }
     }
 }
diff --git a/test/hotspot/jtreg/runtime/cds/appcds/dynamicArchive/SharedBaseAddressOption.java b/test/hotspot/jtreg/runtime/cds/appcds/dynamicArchive/SharedBaseAddressOption.java
new file mode 100644
index 0000000..a570fe6
--- /dev/null
+++ b/test/hotspot/jtreg/runtime/cds/appcds/dynamicArchive/SharedBaseAddressOption.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (c) 2020, 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 Test how the dynamic archive handles -XX:SharedBaseAddress
+ * @requires vm.cds
+ * @library /test/lib /test/hotspot/jtreg/runtime/cds/appcds /test/hotspot/jtreg/runtime/cds/appcds/test-classes
+ * @build Hello
+ * @build sun.hotspot.WhiteBox
+ * @run driver ClassFileInstaller -jar hello.jar Hello
+ * @run driver ClassFileInstaller sun.hotspot.WhiteBox
+ * @run main/othervm -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI -Xbootclasspath/a:. SharedBaseAddressOption
+ */
+
+import jdk.test.lib.Platform;
+
+public class SharedBaseAddressOption extends DynamicArchiveTestBase {
+    static String appJar = ClassFileInstaller.getJarPath("hello.jar");
+    static String mainClass = "Hello";
+
+    public static void main(String[] args) throws Exception {
+        runTest(SharedBaseAddressOption::testCustomBase);
+    }
+
+    static void testCustomBase() throws Exception {
+
+        // (1) -XX:SharedBaseAddress=0 -- the archives will always be relocated at runtime
+        doTest("0");
+
+        // (2) -XX:SharedBaseAddress=0x810000000
+        if (Platform.is64bit()) {
+            doTest("0x810000000");
+        }
+
+        // (3) -XX:SharedBaseAddress that's so high that the archive may wrap around 64-bit
+        if (Platform.is64bit()) {
+            doTest("0xfffffffffff00000");
+        }
+    }
+
+    static void doTest(String sharedBase) throws Exception {
+        String baseArchiveName = getNewArchiveName("base");
+        String topArchiveName = getNewArchiveName("top");
+
+        TestCommon.dumpBaseArchive(baseArchiveName, "-XX:SharedBaseAddress=" + sharedBase,
+                        "-Xlog:cds=debug",
+                        "-Xlog:cds+reloc=debug",
+                        "-Xlog:cds+dynamic=debug");
+
+        dump2(baseArchiveName, topArchiveName,
+              "-Xlog:cds=debug",
+              "-Xlog:cds+reloc=debug",
+              "-Xlog:cds+dynamic=debug",
+              "-cp", appJar, mainClass)
+            .assertNormalExit();
+
+        // a top archive specified in the base archive position
+        run2(baseArchiveName, topArchiveName,
+             "-Xlog:cds=debug",
+             "-Xlog:cds+reloc=debug",
+             "-Xlog:cds+dynamic=debug",
+             "-cp", appJar, mainClass)
+            .assertNormalExit();
+    }
+}