Revert "Pass boot class path to ImageSpace::LoadBootImage."

This reverts commit d19085141ad9c71eae1b0ff585999ac8e27dadd4.

Also squash a revert of
    Fix oatdump tests on target.
    (commit 77eea0898aca2881a87afd177a0422870c39a318)

Reason for revert: Broke JDWP tests.
Bug: 119868597
Change-Id: I005097d2d96014c961e5a4c0b089e7675004febc
diff --git a/cmdline/cmdline_parser_test.cc b/cmdline/cmdline_parser_test.cc
index 101e5c4..97daafa 100644
--- a/cmdline/cmdline_parser_test.cc
+++ b/cmdline/cmdline_parser_test.cc
@@ -63,12 +63,6 @@
     return expected == actual;
   }
 
-  template <char Separator>
-  bool UsuallyEquals(const std::vector<std::string>& expected,
-                     const ParseStringList<Separator>& actual) {
-    return expected == static_cast<std::vector<std::string>>(actual);
-  }
-
   // Try to use memcmp to compare simple plain-old-data structs.
   //
   // This should *not* generate false positives, but it can generate false negatives.
@@ -224,13 +218,8 @@
   }
 
   EXPECT_SINGLE_PARSE_EXISTS("-Xzygote", M::Zygote);
-  EXPECT_SINGLE_PARSE_VALUE(std::vector<std::string>({"/hello/world"}),
-                            "-Xbootclasspath:/hello/world",
-                            M::BootClassPath);
-  EXPECT_SINGLE_PARSE_VALUE(std::vector<std::string>({"/hello", "/world"}),
-                            "-Xbootclasspath:/hello:/world",
-                            M::BootClassPath);
-  EXPECT_SINGLE_PARSE_VALUE_STR("/hello/world", "-classpath /hello/world", M::ClassPath);
+  EXPECT_SINGLE_PARSE_VALUE_STR("/hello/world", "-Xbootclasspath:/hello/world", M::BootClassPath);
+  EXPECT_SINGLE_PARSE_VALUE("/hello/world", "-Xbootclasspath:/hello/world", M::BootClassPath);
   EXPECT_SINGLE_PARSE_VALUE(Memory<1>(234), "-Xss234", M::StackSize);
   EXPECT_SINGLE_PARSE_VALUE(MemoryKiB(1234*MB), "-Xms1234m", M::MemoryInitialSize);
   EXPECT_SINGLE_PARSE_VALUE(true, "-XX:EnableHSpaceCompactForOOM", M::EnableHSpaceCompactForOOM);
diff --git a/dex2oat/dex2oat.cc b/dex2oat/dex2oat.cc
index a5bba9b..f729934 100644
--- a/dex2oat/dex2oat.cc
+++ b/dex2oat/dex2oat.cc
@@ -103,7 +103,6 @@
 
 using android::base::StringAppendV;
 using android::base::StringPrintf;
-using gc::space::ImageSpace;
 
 static constexpr size_t kDefaultMinDexFilesForSwap = 2;
 static constexpr size_t kDefaultMinDexFileCumulativeSizeForSwap = 20 * MB;
@@ -965,22 +964,89 @@
   }
 
   void ExpandOatAndImageFilenames() {
-    if (image_filenames_[0].rfind('/') == std::string::npos) {
-      Usage("Unusable boot image filename %s", image_filenames_[0].c_str());
+    std::string base_oat = oat_filenames_[0];
+    size_t last_oat_slash = base_oat.rfind('/');
+    if (last_oat_slash == std::string::npos) {
+      Usage("Unusable boot image oat filename %s", base_oat.c_str());
     }
-    image_filenames_ = ImageSpace::ExpandMultiImageLocations(dex_locations_, image_filenames_[0]);
-
-    if (oat_filenames_[0].rfind('/') == std::string::npos) {
-      Usage("Unusable boot image oat filename %s", oat_filenames_[0].c_str());
+    // We also need to honor path components that were encoded through '@'. Otherwise the loading
+    // code won't be able to find the images.
+    if (base_oat.find('@', last_oat_slash) != std::string::npos) {
+      last_oat_slash = base_oat.rfind('@');
     }
-    oat_filenames_ = ImageSpace::ExpandMultiImageLocations(dex_locations_, oat_filenames_[0]);
+    base_oat = base_oat.substr(0, last_oat_slash + 1);
 
+    std::string base_img = image_filenames_[0];
+    size_t last_img_slash = base_img.rfind('/');
+    if (last_img_slash == std::string::npos) {
+      Usage("Unusable boot image filename %s", base_img.c_str());
+    }
+    // We also need to honor path components that were encoded through '@'. Otherwise the loading
+    // code won't be able to find the images.
+    if (base_img.find('@', last_img_slash) != std::string::npos) {
+      last_img_slash = base_img.rfind('@');
+    }
+
+    // Get the prefix, which is the primary image name (without path components). Strip the
+    // extension.
+    std::string prefix = base_img.substr(last_img_slash + 1);
+    if (prefix.rfind('.') != std::string::npos) {
+      prefix = prefix.substr(0, prefix.rfind('.'));
+    }
+    if (!prefix.empty()) {
+      prefix = prefix + "-";
+    }
+
+    base_img = base_img.substr(0, last_img_slash + 1);
+
+    std::string base_symbol_oat;
     if (!oat_unstripped_.empty()) {
-      if (oat_unstripped_[0].rfind('/') == std::string::npos) {
-        Usage("Unusable boot image symbol filename %s", oat_unstripped_[0].c_str());
+      base_symbol_oat = oat_unstripped_[0];
+      size_t last_symbol_oat_slash = base_symbol_oat.rfind('/');
+      if (last_symbol_oat_slash == std::string::npos) {
+        Usage("Unusable boot image symbol filename %s", base_symbol_oat.c_str());
       }
-      oat_unstripped_ = ImageSpace::ExpandMultiImageLocations(dex_locations_, oat_unstripped_[0]);
+      base_symbol_oat = base_symbol_oat.substr(0, last_symbol_oat_slash + 1);
     }
+
+    // Now create the other names. Use a counted loop to skip the first one.
+    for (size_t i = 1; i < dex_locations_.size(); ++i) {
+      // TODO: Make everything properly std::string.
+      std::string image_name = CreateMultiImageName(dex_locations_[i], prefix, ".art");
+      char_backing_storage_.push_front(base_img + image_name);
+      image_filenames_.push_back(char_backing_storage_.front().c_str());
+
+      std::string oat_name = CreateMultiImageName(dex_locations_[i], prefix, ".oat");
+      char_backing_storage_.push_front(base_oat + oat_name);
+      oat_filenames_.push_back(char_backing_storage_.front().c_str());
+
+      if (!base_symbol_oat.empty()) {
+        char_backing_storage_.push_front(base_symbol_oat + oat_name);
+        oat_unstripped_.push_back(char_backing_storage_.front().c_str());
+      }
+    }
+  }
+
+  // Modify the input string in the following way:
+  //   0) Assume input is /a/b/c.d
+  //   1) Strip the path  -> c.d
+  //   2) Inject prefix p -> pc.d
+  //   3) Replace suffix with s if it's "jar"  -> d == "jar" -> pc.s
+  static std::string CreateMultiImageName(std::string in,
+                                          const std::string& prefix,
+                                          const char* replace_suffix) {
+    size_t last_dex_slash = in.rfind('/');
+    if (last_dex_slash != std::string::npos) {
+      in = in.substr(last_dex_slash + 1);
+    }
+    if (!prefix.empty()) {
+      in = prefix + in;
+    }
+    if (android::base::EndsWith(in, ".jar")) {
+      in = in.substr(0, in.length() - strlen(".jar")) +
+          (replace_suffix != nullptr ? replace_suffix : "");
+    }
+    return in;
   }
 
   void InsertCompileOptions(int argc, char** argv) {
@@ -1431,8 +1497,11 @@
 
     if (IsBootImage()) {
       // If we're compiling the boot image, store the boot classpath into the Key-Value store.
-      // We use this when loading the boot image.
-      key_value_store_->Put(OatHeader::kBootClassPathKey, android::base::Join(dex_locations_, ':'));
+      // We need this for the multi-image case.
+      key_value_store_->Put(OatHeader::kBootClassPathKey,
+                            gc::space::ImageSpace::GetMultiImageBootClassPath(dex_locations_,
+                                                                              oat_filenames_,
+                                                                              image_filenames_));
     }
 
     if (!IsBootImage()) {
@@ -1444,7 +1513,8 @@
 
       if (CompilerFilter::DependsOnImageChecksum(compiler_options_->GetCompilerFilter())) {
         TimingLogger::ScopedTiming t3("Loading image checksum", timings_);
-        std::vector<ImageSpace*> image_spaces = Runtime::Current()->GetHeap()->GetBootImageSpaces();
+        std::vector<gc::space::ImageSpace*> image_spaces =
+            Runtime::Current()->GetHeap()->GetBootImageSpaces();
         boot_image_checksum_ = image_spaces[0]->GetImageHeader().GetImageChecksum();
       } else {
         boot_image_checksum_ = 0u;
@@ -1883,7 +1953,7 @@
     if (IsImage()) {
       if (IsAppImage() && image_base_ == 0) {
         gc::Heap* const heap = Runtime::Current()->GetHeap();
-        for (ImageSpace* image_space : heap->GetBootImageSpaces()) {
+        for (gc::space::ImageSpace* image_space : heap->GetBootImageSpaces()) {
           image_base_ = std::max(image_base_, RoundUp(
               reinterpret_cast<uintptr_t>(image_space->GetImageHeader().GetOatFileEnd()),
               kPageSize));
diff --git a/dex2oat/linker/image_test.h b/dex2oat/linker/image_test.h
index bd8cf5a..13fa0f0 100644
--- a/dex2oat/linker/image_test.h
+++ b/dex2oat/linker/image_test.h
@@ -169,11 +169,10 @@
   {
     // Create a generic tmp file, to be the base of the .art and .oat temporary files.
     ScratchFile location;
-    std::vector<std::string> image_locations =
-        gc::space::ImageSpace::ExpandMultiImageLocations(out_helper.dex_file_locations,
-                                                         location.GetFilename() + ".art");
-    for (size_t i = 0u; i != class_path.size(); ++i) {
-      out_helper.image_locations.push_back(ScratchFile(image_locations[i]));
+    for (int i = 0; i < static_cast<int>(class_path.size()); ++i) {
+      std::string cur_location =
+          android::base::StringPrintf("%s-%d.art", location.GetFilename().c_str(), i);
+      out_helper.image_locations.push_back(ScratchFile(cur_location));
     }
   }
   std::vector<std::string> image_filenames;
@@ -224,7 +223,10 @@
       TimingLogger::ScopedTiming t("WriteElf", &timings);
       SafeMap<std::string, std::string> key_value_store;
       key_value_store.Put(OatHeader::kBootClassPathKey,
-                          android::base::Join(out_helper.dex_file_locations, ':'));
+                          gc::space::ImageSpace::GetMultiImageBootClassPath(
+                              out_helper.dex_file_locations,
+                              oat_filenames,
+                              image_filenames));
 
       std::vector<std::unique_ptr<ElfWriter>> elf_writers;
       std::vector<std::unique_ptr<OatWriter>> oat_writers;
diff --git a/oatdump/oatdump_test.h b/oatdump/oatdump_test.h
index dfa659b..728939f 100644
--- a/oatdump/oatdump_test.h
+++ b/oatdump/oatdump_test.h
@@ -178,11 +178,6 @@
         expected_prefixes.push_back("InlineInfo");
       }
       if (mode == kModeArt) {
-        exec_argv.push_back("--runtime-arg");
-        exec_argv.push_back(GetClassPathOption("-Xbootclasspath:", GetLibCoreDexFileNames()));
-        exec_argv.push_back("--runtime-arg");
-        exec_argv.push_back(
-            GetClassPathOption("-Xbootclasspath-locations:", GetLibCoreDexLocations()));
         exec_argv.push_back("--image=" + core_art_location_);
         exec_argv.push_back("--instruction-set=" + std::string(
             GetInstructionSetString(kRuntimeISA)));
diff --git a/runtime/class_linker.cc b/runtime/class_linker.cc
index e31fe63..c964dbc 100644
--- a/runtime/class_linker.cc
+++ b/runtime/class_linker.cc
@@ -1037,20 +1037,13 @@
   runtime->SetSentinel(heap->AllocNonMovableObject<true>(
       self, java_lang_Object, java_lang_Object->GetObjectSize(), VoidFunctor()));
 
-  const std::vector<std::string>& boot_class_path = runtime->GetBootClassPath();
-  if (boot_class_path.size() != spaces.size()) {
-    *error_msg = StringPrintf("Boot class path has %zu components but there are %zu image spaces.",
-                              boot_class_path.size(),
-                              spaces.size());
-    return false;
-  }
-  for (size_t i = 0u, size = spaces.size(); i != size; ++i) {
+  for (gc::space::ImageSpace* image_space : spaces) {
     // Boot class loader, use a null handle.
     std::vector<std::unique_ptr<const DexFile>> dex_files;
-    if (!AddImageSpace(spaces[i],
+    if (!AddImageSpace(image_space,
                        ScopedNullHandle<mirror::ClassLoader>(),
-                       /*dex_elements=*/ nullptr,
-                       /*dex_location=*/ boot_class_path[i].c_str(),
+                       /*dex_elements=*/nullptr,
+                       /*dex_location=*/nullptr,
                        /*out*/&dex_files,
                        error_msg)) {
       return false;
@@ -1988,7 +1981,13 @@
     std::string dex_file_location(dex_cache->GetLocation()->ToModifiedUtf8());
     // TODO: Only store qualified paths.
     // If non qualified, qualify it.
-    dex_file_location = OatFile::ResolveRelativeEncodedDexLocation(dex_location, dex_file_location);
+    if (dex_file_location.find('/') == std::string::npos) {
+      std::string dex_location_path = dex_location;
+      const size_t pos = dex_location_path.find_last_of('/');
+      CHECK_NE(pos, std::string::npos);
+      dex_location_path = dex_location_path.substr(0, pos + 1);  // Keep trailing '/'
+      dex_file_location = dex_location_path + dex_file_location;
+    }
     std::unique_ptr<const DexFile> dex_file = OpenOatDexFile(oat_file,
                                                              dex_file_location.c_str(),
                                                              error_msg);
diff --git a/runtime/gc/heap.cc b/runtime/gc/heap.cc
index 2f36d02..86135c1 100644
--- a/runtime/gc/heap.cc
+++ b/runtime/gc/heap.cc
@@ -173,8 +173,6 @@
            double foreground_heap_growth_multiplier,
            size_t capacity,
            size_t non_moving_space_capacity,
-           const std::vector<std::string>& boot_class_path,
-           const std::vector<std::string>& boot_class_path_locations,
            const std::string& image_file_name,
            const InstructionSet image_instruction_set,
            CollectorType foreground_collector_type,
@@ -352,9 +350,7 @@
   // Load image space(s).
   std::vector<std::unique_ptr<space::ImageSpace>> boot_image_spaces;
   MemMap heap_reservation;
-  if (space::ImageSpace::LoadBootImage(boot_class_path,
-                                       boot_class_path_locations,
-                                       image_file_name,
+  if (space::ImageSpace::LoadBootImage(image_file_name,
                                        image_instruction_set,
                                        heap_reservation_size,
                                        &boot_image_spaces,
diff --git a/runtime/gc/heap.h b/runtime/gc/heap.h
index 8d81c11..411a446 100644
--- a/runtime/gc/heap.h
+++ b/runtime/gc/heap.h
@@ -174,9 +174,7 @@
        double foreground_heap_growth_multiplier,
        size_t capacity,
        size_t non_moving_space_capacity,
-       const std::vector<std::string>& boot_class_path,
-       const std::vector<std::string>& boot_class_path_locations,
-       const std::string& image_file_name,
+       const std::string& original_image_file_name,
        InstructionSet image_instruction_set,
        CollectorType foreground_collector_type,
        CollectorType background_collector_type,
diff --git a/runtime/gc/space/image_space.cc b/runtime/gc/space/image_space.cc
index 02ab50b..e494bd6 100644
--- a/runtime/gc/space/image_space.cc
+++ b/runtime/gc/space/image_space.cc
@@ -103,8 +103,9 @@
 static bool GenerateImage(const std::string& image_filename,
                           InstructionSet image_isa,
                           std::string* error_msg) {
-  Runtime* runtime = Runtime::Current();
-  const std::vector<std::string>& boot_class_path = runtime->GetBootClassPath();
+  const std::string boot_class_path_string(Runtime::Current()->GetBootClassPathString());
+  std::vector<std::string> boot_class_path;
+  Split(boot_class_path_string, ':', &boot_class_path);
   if (boot_class_path.empty()) {
     *error_msg = "Failed to generate image because no boot class path specified";
     return false;
@@ -124,11 +125,8 @@
   image_option_string += image_filename;
   arg_vector.push_back(image_option_string);
 
-  const std::vector<std::string>& boot_class_path_locations = runtime->GetBootClassPathLocations();
-  DCHECK_EQ(boot_class_path.size(), boot_class_path_locations.size());
-  for (size_t i = 0u; i < boot_class_path.size(); i++) {
+  for (size_t i = 0; i < boot_class_path.size(); i++) {
     arg_vector.push_back(std::string("--dex-file=") + boot_class_path[i]);
-    arg_vector.push_back(std::string("--dex-location=") + boot_class_path_locations[i]);
   }
 
   std::string oat_file_option_string("--oat-file=");
@@ -1207,13 +1205,8 @@
 
 class ImageSpace::BootImageLoader {
  public:
-  BootImageLoader(const std::vector<std::string>& boot_class_path,
-                  const std::vector<std::string>& boot_class_path_locations,
-                  const std::string& image_location,
-                  InstructionSet image_isa)
-      : boot_class_path_(boot_class_path),
-        boot_class_path_locations_(boot_class_path_locations),
-        image_location_(image_location),
+  BootImageLoader(const std::string& image_location, InstructionSet image_isa)
+      : image_location_(image_location),
         image_isa_(image_isa),
         is_zygote_(Runtime::Current()->IsZygote()),
         has_system_(false),
@@ -1261,8 +1254,10 @@
                       /*out*/std::string* error_msg) REQUIRES_SHARED(Locks::mutator_lock_) {
     TimingLogger logger(__PRETTY_FUNCTION__, /*precise=*/ true, VLOG_IS_ON(image));
     std::string filename = GetSystemImageFilename(image_location_.c_str(), image_isa_);
-    std::vector<std::string> locations =
-        ExpandMultiImageLocations(boot_class_path_locations_, image_location_);
+    std::vector<std::string> locations;
+    if (!GetBootClassPathImageLocations(image_location_, filename, &locations, error_msg)) {
+      return false;
+    }
     uint32_t image_start;
     uint32_t image_end;
     if (!GetBootImageAddressRange(filename, &image_start, &image_end, error_msg)) {
@@ -1295,16 +1290,9 @@
         return false;
       }
     }
-    for (size_t i = 0u, size = spaces.size(); i != size; ++i) {
-      std::string expected_boot_class_path =
-          (i == 0u) ? android::base::Join(boot_class_path_locations_, ':') : std::string();
-      if (!OpenOatFile(spaces[i].get(),
-                       boot_class_path_[i],
-                       expected_boot_class_path,
-                       /*validate_oat_file=*/ false,
-                       &logger,
-                       &image_reservation,
-                       error_msg)) {
+    for (std::unique_ptr<ImageSpace>& space : spaces) {
+      static constexpr bool kValidateOatFile = false;
+      if (!OpenOatFile(space.get(), kValidateOatFile, &logger, &image_reservation, error_msg)) {
         return false;
       }
     }
@@ -1333,8 +1321,10 @@
       /*out*/std::string* error_msg) REQUIRES_SHARED(Locks::mutator_lock_) {
     TimingLogger logger(__PRETTY_FUNCTION__, /*precise=*/ true, VLOG_IS_ON(image));
     DCHECK(DalvikCacheExists());
-    std::vector<std::string> locations =
-        ExpandMultiImageLocations(boot_class_path_locations_, image_location_);
+    std::vector<std::string> locations;
+    if (!GetBootClassPathImageLocations(image_location_, cache_filename_, &locations, error_msg)) {
+      return false;
+    }
     uint32_t image_start;
     uint32_t image_end;
     if (!GetBootImageAddressRange(cache_filename_, &image_start, &image_end, error_msg)) {
@@ -1376,16 +1366,8 @@
         return false;
       }
     }
-    for (size_t i = 0u, size = spaces.size(); i != size; ++i) {
-      std::string expected_boot_class_path =
-          (i == 0u) ? android::base::Join(boot_class_path_locations_, ':') : std::string();
-      if (!OpenOatFile(spaces[i].get(),
-                       boot_class_path_[i],
-                       expected_boot_class_path,
-                       validate_oat_file,
-                       &logger,
-                       &image_reservation,
-                       error_msg)) {
+    for (std::unique_ptr<ImageSpace>& space : spaces) {
+      if (!OpenOatFile(space.get(), validate_oat_file, &logger, &image_reservation, error_msg)) {
         return false;
       }
     }
@@ -1905,6 +1887,8 @@
     DCHECK(!spaces.empty());
     ImageSpace* space = spaces[0].get();
     const ImageHeader& image_header = space->GetImageHeader();
+    // Use oat_file_non_owned_ from the `space` to set the runtime methods.
+    runtime->SetInstructionSet(space->oat_file_non_owned_->GetOatHeader().GetInstructionSet());
     runtime->SetResolutionMethod(image_header.GetImageMethod(ImageHeader::kResolutionMethod));
     runtime->SetImtConflictMethod(image_header.GetImageMethod(ImageHeader::kImtConflictMethod));
     runtime->SetImtUnimplementedMethod(
@@ -1968,8 +1952,6 @@
   }
 
   bool OpenOatFile(ImageSpace* space,
-                   const std::string& dex_filename,
-                   const std::string& expected_boot_class_path,
                    bool validate_oat_file,
                    TimingLogger* logger,
                    /*inout*/MemMap* image_reservation,
@@ -1985,15 +1967,13 @@
       TimingLogger::ScopedTiming timing("OpenOatFile", logger);
       std::string oat_filename =
           ImageHeader::GetOatLocationFromImageLocation(space->GetImageFilename());
-      std::string oat_location =
-          ImageHeader::GetOatLocationFromImageLocation(space->GetImageLocation());
 
       oat_file.reset(OatFile::Open(/*zip_fd=*/ -1,
                                    oat_filename,
-                                   oat_location,
+                                   oat_filename,
                                    !Runtime::Current()->IsAotCompiler(),
                                    /*low_4gb=*/ false,
-                                   /*abs_dex_location=*/ dex_filename.c_str(),
+                                   /*abs_dex_location=*/ nullptr,
                                    image_reservation,
                                    error_msg));
       if (oat_file == nullptr) {
@@ -2014,17 +1994,6 @@
                                   space->GetName());
         return false;
       }
-      const char* oat_boot_class_path =
-          oat_file->GetOatHeader().GetStoreValueByKey(OatHeader::kBootClassPathKey);
-      oat_boot_class_path = (oat_boot_class_path != nullptr) ? oat_boot_class_path : "";
-      if (expected_boot_class_path != oat_boot_class_path) {
-        *error_msg = StringPrintf("Failed to match oat boot class path %s to expected "
-                                  "boot class path %s in image %s",
-                                  oat_boot_class_path,
-                                  expected_boot_class_path.c_str(),
-                                  space->GetName());
-        return false;
-      }
       ptrdiff_t relocation_diff = space->Begin() - image_header.GetImageBegin();
       CHECK(image_header.GetOatDataBegin() != nullptr);
       uint8_t* oat_data_begin = image_header.GetOatDataBegin() + relocation_diff;
@@ -2050,6 +2019,37 @@
     return true;
   }
 
+  // Extract boot class path from oat file associated with `image_filename`
+  // and list all associated image locations.
+  static bool GetBootClassPathImageLocations(const std::string& image_location,
+                                             const std::string& image_filename,
+                                             /*out*/ std::vector<std::string>* all_locations,
+                                             /*out*/ std::string* error_msg) {
+    std::string oat_filename = ImageHeader::GetOatLocationFromImageLocation(image_filename);
+    std::unique_ptr<OatFile> oat_file(OatFile::Open(/*zip_fd=*/ -1,
+                                                    oat_filename,
+                                                    oat_filename,
+                                                    /*executable=*/ false,
+                                                    /*low_4gb=*/ false,
+                                                    /*abs_dex_location=*/ nullptr,
+                                                    /*reservation=*/ nullptr,
+                                                    error_msg));
+    if (oat_file == nullptr) {
+      *error_msg = StringPrintf("Failed to open oat file '%s' for image file %s: %s",
+                                oat_filename.c_str(),
+                                image_filename.c_str(),
+                                error_msg->c_str());
+      return false;
+    }
+    const OatHeader& oat_header = oat_file->GetOatHeader();
+    const char* boot_classpath = oat_header.GetStoreValueByKey(OatHeader::kBootClassPathKey);
+    all_locations->push_back(image_location);
+    if (boot_classpath != nullptr && boot_classpath[0] != 0) {
+      ExtractMultiImageLocations(image_location, boot_classpath, all_locations);
+    }
+    return true;
+  }
+
   bool GetBootImageAddressRange(const std::string& filename,
                                 /*out*/uint32_t* start,
                                 /*out*/uint32_t* end,
@@ -2116,8 +2116,6 @@
     return true;
   }
 
-  const std::vector<std::string>& boot_class_path_;
-  const std::vector<std::string>& boot_class_path_locations_;
   const std::string& image_location_;
   InstructionSet image_isa_;
   bool is_zygote_;
@@ -2165,8 +2163,6 @@
 }
 
 bool ImageSpace::LoadBootImage(
-    const std::vector<std::string>& boot_class_path,
-    const std::vector<std::string>& boot_class_path_locations,
     const std::string& image_location,
     const InstructionSet image_isa,
     size_t extra_reservation_size,
@@ -2184,7 +2180,7 @@
     return false;
   }
 
-  BootImageLoader loader(boot_class_path, boot_class_path_locations, image_location, image_isa);
+  BootImageLoader loader(image_location, image_isa);
 
   // Step 0: Extra zygote work.
 
@@ -2345,6 +2341,57 @@
       << ",name=\"" << GetName() << "\"]";
 }
 
+std::string ImageSpace::GetMultiImageBootClassPath(
+    const std::vector<std::string>& dex_locations,
+    const std::vector<std::string>& oat_filenames,
+    const std::vector<std::string>& image_filenames) {
+  DCHECK_GT(oat_filenames.size(), 1u);
+  // If the image filename was adapted (e.g., for our tests), we need to change this here,
+  // too, but need to strip all path components (they will be re-established when loading).
+  // For example, dex location
+  //    /system/framework/core-libart.art
+  // with image name
+  //    out/target/product/taimen/dex_bootjars/system/framework/arm64/boot-core-libart.art
+  // yields boot class path component
+  //    /system/framework/boot-core-libart.art .
+  std::ostringstream bootcp_oss;
+  bool first_bootcp = true;
+  for (size_t i = 0; i < dex_locations.size(); ++i) {
+    if (!first_bootcp) {
+      bootcp_oss << ":";
+    }
+
+    std::string dex_loc = dex_locations[i];
+    std::string image_filename = image_filenames[i];
+
+    // Use the dex_loc path, but the image_filename name (without path elements).
+    size_t dex_last_slash = dex_loc.rfind('/');
+
+    // npos is max(size_t). That makes this a bit ugly.
+    size_t image_last_slash = image_filename.rfind('/');
+    size_t image_last_at = image_filename.rfind('@');
+    size_t image_last_sep = (image_last_slash == std::string::npos)
+                                ? image_last_at
+                                : (image_last_at == std::string::npos)
+                                      ? image_last_slash
+                                      : std::max(image_last_slash, image_last_at);
+    // Note: whenever image_last_sep == npos, +1 overflow means using the full string.
+
+    if (dex_last_slash == std::string::npos) {
+      dex_loc = image_filename.substr(image_last_sep + 1);
+    } else {
+      dex_loc = dex_loc.substr(0, dex_last_slash + 1) +
+          image_filename.substr(image_last_sep + 1);
+    }
+
+    // Image filenames already end with .art, no need to replace.
+
+    bootcp_oss << dex_loc;
+    first_bootcp = false;
+  }
+  return bootcp_oss.str();
+}
+
 bool ImageSpace::ValidateOatFile(const OatFile& oat_file, std::string* error_msg) {
   const ArtDexFileLoader dex_file_loader;
   for (const OatDexFile* oat_dex_file : oat_file.GetOatDexFiles()) {
@@ -2405,55 +2452,46 @@
   return true;
 }
 
-std::vector<std::string> ImageSpace::ExpandMultiImageLocations(
-    const std::vector<std::string>& dex_locations,
-    const std::string& image_location) {
-  DCHECK(!dex_locations.empty());
+void ImageSpace::ExtractMultiImageLocations(const std::string& input_image_file_name,
+                                            const std::string& boot_classpath,
+                                            std::vector<std::string>* image_file_names) {
+  DCHECK(image_file_names != nullptr);
 
-  // Find the path.
-  size_t last_slash = image_location.rfind('/');
-  CHECK_NE(last_slash, std::string::npos);
+  std::vector<std::string> images;
+  Split(boot_classpath, ':', &images);
 
-  // We also need to honor path components that were encoded through '@'. Otherwise the loading
-  // code won't be able to find the images.
-  if (image_location.find('@', last_slash) != std::string::npos) {
-    last_slash = image_location.rfind('@');
+  // Add the rest into the list. We have to adjust locations, possibly:
+  //
+  // For example, image_file_name is /a/b/c/d/e.art
+  //              images[0] is          f/c/d/e.art
+  // ----------------------------------------------
+  //              images[1] is          g/h/i/j.art  -> /a/b/h/i/j.art
+  const std::string& first_image = images[0];
+  // Length of common suffix.
+  size_t common = 0;
+  while (common < input_image_file_name.size() &&
+         common < first_image.size() &&
+         *(input_image_file_name.end() - common - 1) == *(first_image.end() - common - 1)) {
+    ++common;
   }
+  // We want to replace the prefix of the input image with the prefix of the boot class path.
+  // This handles the case where the image file contains @ separators.
+  // Example image_file_name is oats/system@framework@boot.art
+  // images[0] is .../arm/boot.art
+  // means that the image name prefix will be oats/system@framework@
+  // so that the other images are openable.
+  const size_t old_prefix_length = first_image.size() - common;
+  const std::string new_prefix = input_image_file_name.substr(
+      0,
+      input_image_file_name.size() - common);
 
-  // Find the dot separating the primary image name from the extension.
-  size_t last_dot = image_location.rfind('.');
-  // Extract the extension and base (the path and primary image name).
-  std::string extension;
-  std::string base = image_location;
-  if (last_dot != std::string::npos && last_dot > last_slash) {
-    extension = image_location.substr(last_dot);  // Including the dot.
-    base.resize(last_dot);
+  // Apply pattern to images[1] .. images[n].
+  for (size_t i = 1; i < images.size(); ++i) {
+    const std::string& image = images[i];
+    CHECK_GT(image.length(), old_prefix_length);
+    std::string suffix = image.substr(old_prefix_length);
+    image_file_names->push_back(new_prefix + suffix);
   }
-  // For non-empty primary image name, add '-' to the `base`.
-  if (last_slash + 1u != base.size()) {
-    base += '-';
-  }
-
-  std::vector<std::string> locations;
-  locations.reserve(dex_locations.size());
-  locations.push_back(image_location);
-
-  // Now create the other names. Use a counted loop to skip the first one.
-  for (size_t i = 1u; i < dex_locations.size(); ++i) {
-    // Replace path with `base` (i.e. image path and prefix) and replace the original
-    // extension (if any) with `extension`.
-    std::string name = dex_locations[i];
-    size_t last_dex_slash = name.rfind('/');
-    if (last_dex_slash != std::string::npos) {
-      name = name.substr(last_dex_slash + 1);
-    }
-    size_t last_dex_dot = name.rfind('.');
-    if (last_dex_dot != std::string::npos) {
-      name.resize(last_dex_dot);
-    }
-    locations.push_back(base + name + extension);
-  }
-  return locations;
 }
 
 void ImageSpace::DumpSections(std::ostream& os) const {
diff --git a/runtime/gc/space/image_space.h b/runtime/gc/space/image_space.h
index 05e7fa5..aa45ed3 100644
--- a/runtime/gc/space/image_space.h
+++ b/runtime/gc/space/image_space.h
@@ -39,11 +39,9 @@
   // Load boot image spaces from a primary image file for a specified instruction set.
   //
   // On successful return, the loaded spaces are added to boot_image_spaces (which must be
-  // empty on entry) and `extra_reservation` is set to the requested reservation located
-  // after the end of the last loaded oat file.
+  // empty on entry) and oat_file_end is updated with the (page-aligned) end of the last
+  // oat file.
   static bool LoadBootImage(
-      const std::vector<std::string>& boot_class_path,
-      const std::vector<std::string>& boot_class_path_locations,
       const std::string& image_location,
       const InstructionSet image_isa,
       size_t extra_reservation_size,
@@ -124,10 +122,15 @@
                                 bool* has_data,
                                 bool *is_global_cache);
 
-  // Expand a single image location to multi-image locations based on the dex locations.
-  static std::vector<std::string> ExpandMultiImageLocations(
-      const std::vector<std::string>& dex_locations,
-      const std::string& image_location);
+  // Use the input image filename to adapt the names in the given boot classpath to establish
+  // complete locations for secondary images.
+  static void ExtractMultiImageLocations(const std::string& input_image_file_name,
+                                        const std::string& boot_classpath,
+                                        std::vector<std::string>* image_filenames);
+
+  static std::string GetMultiImageBootClassPath(const std::vector<std::string>& dex_locations,
+                                                const std::vector<std::string>& oat_filenames,
+                                                const std::vector<std::string>& image_filenames);
 
   // Returns true if the dex checksums in the given oat file match the
   // checksums of the original dex files on disk. This is intended to be used
diff --git a/runtime/interpreter/unstarted_runtime.cc b/runtime/interpreter/unstarted_runtime.cc
index 4fa7271..e292a76 100644
--- a/runtime/interpreter/unstarted_runtime.cc
+++ b/runtime/interpreter/unstarted_runtime.cc
@@ -571,9 +571,12 @@
 
   Runtime* runtime = Runtime::Current();
 
-  const std::vector<std::string>& boot_class_path = Runtime::Current()->GetBootClassPath();
-  if (boot_class_path.empty()) {
-    AbortTransactionOrFail(self, "Boot classpath not set");
+  std::vector<std::string> split;
+  Split(runtime->GetBootClassPathString(), ':', &split);
+  if (split.empty()) {
+    AbortTransactionOrFail(self,
+                           "Boot classpath not set or split error:: %s",
+                           runtime->GetBootClassPathString().c_str());
     return;
   }
 
@@ -581,7 +584,7 @@
   size_t map_size;
   std::string last_error_msg;  // Only store the last message (we could concatenate).
 
-  for (const std::string& jar_file : boot_class_path) {
+  for (const std::string& jar_file : split) {
     mem_map = FindAndExtractEntry(jar_file, resource_cstr, &map_size, &last_error_msg);
     if (mem_map.IsValid()) {
       break;
diff --git a/runtime/jdwp/jdwp_handler.cc b/runtime/jdwp/jdwp_handler.cc
index 37365ff..d31f166 100644
--- a/runtime/jdwp/jdwp_handler.cc
+++ b/runtime/jdwp/jdwp_handler.cc
@@ -292,7 +292,8 @@
     expandBufAddUtf8String(pReply, str);
   }
 
-  std::vector<std::string> boot_class_path = Runtime::Current()->GetBootClassPath();
+  std::vector<std::string> boot_class_path;
+  Split(Runtime::Current()->GetBootClassPathString(), ':', &boot_class_path);
   expandBufAdd4BE(pReply, boot_class_path.size());
   for (const std::string& str : boot_class_path) {
     expandBufAddUtf8String(pReply, str);
diff --git a/runtime/native/dalvik_system_VMRuntime.cc b/runtime/native/dalvik_system_VMRuntime.cc
index 3e5003c..e213dc7 100644
--- a/runtime/native/dalvik_system_VMRuntime.cc
+++ b/runtime/native/dalvik_system_VMRuntime.cc
@@ -24,8 +24,7 @@
 #include <limits.h>
 #include "nativehelper/scoped_utf_chars.h"
 
-#include <android-base/stringprintf.h>
-#include <android-base/strings.h>
+#include "android-base/stringprintf.h"
 
 #include "arch/instruction_set.h"
 #include "art_method-inl.h"
@@ -223,8 +222,7 @@
 }
 
 static jstring VMRuntime_bootClassPath(JNIEnv* env, jobject) {
-  std::string boot_class_path = android::base::Join(Runtime::Current()->GetBootClassPath(), ':');
-  return env->NewStringUTF(DefaultToDot(boot_class_path));
+  return env->NewStringUTF(DefaultToDot(Runtime::Current()->GetBootClassPathString()));
 }
 
 static jstring VMRuntime_classPath(JNIEnv* env, jobject) {
diff --git a/runtime/oat.h b/runtime/oat.h
index b09c81e..ee46f42 100644
--- a/runtime/oat.h
+++ b/runtime/oat.h
@@ -31,8 +31,8 @@
 class PACKED(4) OatHeader {
  public:
   static constexpr uint8_t kOatMagic[] = { 'o', 'a', 't', '\n' };
-  // Last oat version changed reason: Pass boot class path to LoadBootImage.
-  static constexpr uint8_t kOatVersion[] = { '1', '6', '5', '\0' };
+  // Last oat version changed reason: Image checksums.
+  static constexpr uint8_t kOatVersion[] = { '1', '6', '4', '\0' };
 
   static constexpr const char* kDex2OatCmdLineKey = "dex2oat-cmdline";
   static constexpr const char* kDebuggableKey = "debuggable";
diff --git a/runtime/parsed_options.cc b/runtime/parsed_options.cc
index 17ff3a2..29b5690 100644
--- a/runtime/parsed_options.cc
+++ b/runtime/parsed_options.cc
@@ -20,7 +20,6 @@
 #include <sstream>
 
 #include <android-base/logging.h>
-#include <android-base/strings.h>
 
 #include "base/file_utils.h"
 #include "base/macros.h"
@@ -79,7 +78,7 @@
       .Define("-showversion")
           .IntoKey(M::ShowVersion)
       .Define("-Xbootclasspath:_")
-          .WithType<ParseStringList<':'>>()  // std::vector<std::string>, split by :
+          .WithType<std::string>()
           .IntoKey(M::BootClassPath)
       .Define("-Xbootclasspath-locations:_")
           .WithType<ParseStringList<':'>>()  // std::vector<std::string>, split by :
@@ -514,7 +513,7 @@
                  GetInstructionSetString(kRuntimeISA));
     Exit(0);
   } else if (args.Exists(M::BootClassPath)) {
-    LOG(INFO) << "setting boot class path to " << args.Get(M::BootClassPath)->Join();
+    LOG(INFO) << "setting boot class path to " << *args.Get(M::BootClassPath);
   }
 
   if (args.GetOrDefault(M::Interpret)) {
@@ -526,9 +525,8 @@
   }
 
   // Set a default boot class path if we didn't get an explicit one via command line.
-  const char* env_bcp = getenv("BOOTCLASSPATH");
-  if (env_bcp != nullptr) {
-    args.SetIfMissing(M::BootClassPath, ParseStringList<':'>::Split(env_bcp));
+  if (getenv("BOOTCLASSPATH") != nullptr) {
+    args.SetIfMissing(M::BootClassPath, std::string(getenv("BOOTCLASSPATH")));
   }
 
   // Set a default class path if we didn't get an explicit one via command line.
@@ -588,20 +586,22 @@
     args.Set(M::BackgroundGc, BackgroundGcOption { background_collector_type_ });
   }
 
-  const ParseStringList<':'>* boot_class_path_locations = args.Get(M::BootClassPathLocations);
-  if (boot_class_path_locations != nullptr && boot_class_path_locations->Size() != 0u) {
-    const ParseStringList<':'>* boot_class_path = args.Get(M::BootClassPath);
-    if (boot_class_path == nullptr ||
-        boot_class_path_locations->Size() != boot_class_path->Size()) {
-      Usage("The number of boot class path files does not match"
-          " the number of boot class path locations given\n"
-          "  boot class path files     (%zu): %s\n"
-          "  boot class path locations (%zu): %s\n",
-          (boot_class_path != nullptr) ? boot_class_path->Size() : 0u,
-          (boot_class_path != nullptr) ? boot_class_path->Join().c_str() : "<nil>",
-          boot_class_path_locations->Size(),
-          boot_class_path_locations->Join().c_str());
-      return false;
+  auto boot_class_path_string = args.GetOrDefault(M::BootClassPath);
+  {
+    auto&& boot_class_path = args.GetOrDefault(M::BootClassPath);
+    auto&& boot_class_path_locations = args.GetOrDefault(M::BootClassPathLocations);
+    if (args.Exists(M::BootClassPathLocations)) {
+      size_t boot_class_path_count = ParseStringList<':'>::Split(boot_class_path).Size();
+
+      if (boot_class_path_count != boot_class_path_locations.Size()) {
+        Usage("The number of boot class path files does not match"
+            " the number of boot class path locations given\n"
+            "  boot class path files     (%zu): %s\n"
+            "  boot class path locations (%zu): %s\n",
+            boot_class_path.size(), boot_class_path_string.c_str(),
+            boot_class_path_locations.Size(), boot_class_path_locations.Join().c_str());
+        return false;
+      }
     }
   }
 
diff --git a/runtime/parsed_options_test.cc b/runtime/parsed_options_test.cc
index cbb7b82..705cc6c 100644
--- a/runtime/parsed_options_test.cc
+++ b/runtime/parsed_options_test.cc
@@ -40,7 +40,8 @@
   boot_class_path += "-Xbootclasspath:";
 
   bool first_dex_file = true;
-  for (const std::string &dex_file_name : CommonRuntimeTest::GetLibCoreDexFileNames()) {
+  for (const std::string &dex_file_name :
+           CommonRuntimeTest::GetLibCoreDexFileNames()) {
     if (!first_dex_file) {
       class_path += ":";
     } else {
@@ -49,8 +50,6 @@
     class_path += dex_file_name;
   }
   boot_class_path += class_path;
-  std::vector<std::string> expected_boot_class_path;
-  Split(class_path, ':', &expected_boot_class_path);
 
   RuntimeOptions options;
   options.push_back(std::make_pair(boot_class_path.c_str(), nullptr));
@@ -79,11 +78,9 @@
   using Opt = RuntimeArgumentMap;
 
 #define EXPECT_PARSED_EQ(expected, actual_key) EXPECT_EQ(expected, map.GetOrDefault(actual_key))
-#define EXPECT_PARSED_EQ_AS_STRING_VECTOR(expected, actual_key) \
-  EXPECT_EQ(expected, static_cast<std::vector<std::string>>(map.GetOrDefault(actual_key)))
 #define EXPECT_PARSED_EXISTS(actual_key) EXPECT_TRUE(map.Exists(actual_key))
 
-  EXPECT_PARSED_EQ_AS_STRING_VECTOR(expected_boot_class_path, Opt::BootClassPath);
+  EXPECT_PARSED_EQ(class_path, Opt::BootClassPath);
   EXPECT_PARSED_EQ(class_path, Opt::ClassPath);
   EXPECT_PARSED_EQ(std::string("boot_image"), Opt::Image);
   EXPECT_PARSED_EXISTS(Opt::CheckJni);
diff --git a/runtime/runtime.cc b/runtime/runtime.cc
index 11e0bf4..8e3d71a 100644
--- a/runtime/runtime.cc
+++ b/runtime/runtime.cc
@@ -1097,45 +1097,7 @@
   Monitor::Init(runtime_options.GetOrDefault(Opt::LockProfThreshold),
                 runtime_options.GetOrDefault(Opt::StackDumpLockProfThreshold));
 
-  image_location_ = runtime_options.GetOrDefault(Opt::Image);
-  SetInstructionSet(runtime_options.GetOrDefault(Opt::ImageInstructionSet));
-  boot_class_path_ = runtime_options.ReleaseOrDefault(Opt::BootClassPath);
-  boot_class_path_locations_ = runtime_options.ReleaseOrDefault(Opt::BootClassPathLocations);
-  DCHECK(boot_class_path_locations_.empty() ||
-         boot_class_path_locations_.size() == boot_class_path_.size());
-  if (boot_class_path_.empty()) {
-    // Try to extract the boot class path from the system boot image.
-    if (image_location_.empty()) {
-      LOG(ERROR) << "Empty boot class path, cannot continue without image.";
-      return false;
-    }
-    std::string system_oat_filename = ImageHeader::GetOatLocationFromImageLocation(
-        GetSystemImageFilename(image_location_.c_str(), instruction_set_));
-    std::string system_oat_location = ImageHeader::GetOatLocationFromImageLocation(image_location_);
-    std::string error_msg;
-    std::unique_ptr<OatFile> oat_file(OatFile::Open(/*zip_fd=*/ -1,
-                                                    system_oat_filename,
-                                                    system_oat_location,
-                                                    /*executable=*/ false,
-                                                    /*low_4gb=*/ false,
-                                                    /*abs_dex_location=*/ nullptr,
-                                                    /*reservation=*/ nullptr,
-                                                    &error_msg));
-    if (oat_file == nullptr) {
-      LOG(ERROR) << "Could not open boot oat file for extracting boot class path: " << error_msg;
-      return false;
-    }
-    const OatHeader& oat_header = oat_file->GetOatHeader();
-    const char* oat_boot_class_path = oat_header.GetStoreValueByKey(OatHeader::kBootClassPathKey);
-    if (oat_boot_class_path != nullptr) {
-      Split(oat_boot_class_path, ':', &boot_class_path_);
-    }
-    if (boot_class_path_.empty()) {
-      LOG(ERROR) << "Boot class path missing from boot image oat file " << oat_file->GetLocation();
-      return false;
-    }
-  }
-
+  boot_class_path_string_ = runtime_options.ReleaseOrDefault(Opt::BootClassPath);
   class_path_string_ = runtime_options.ReleaseOrDefault(Opt::ClassPath);
   properties_ = runtime_options.ReleaseOrDefault(Opt::PropertiesList);
 
@@ -1161,6 +1123,7 @@
     }
   }
   image_compiler_options_ = runtime_options.ReleaseOrDefault(Opt::ImageCompilerOptions);
+  image_location_ = runtime_options.GetOrDefault(Opt::Image);
 
   max_spins_before_thin_lock_inflation_ =
       runtime_options.GetOrDefault(Opt::MaxSpinsBeforeThinLockInflation);
@@ -1229,10 +1192,8 @@
                        foreground_heap_growth_multiplier,
                        runtime_options.GetOrDefault(Opt::MemoryMaximumSize),
                        runtime_options.GetOrDefault(Opt::NonMovingSpaceCapacity),
-                       GetBootClassPath(),
-                       GetBootClassPathLocations(),
-                       image_location_,
-                       instruction_set_,
+                       runtime_options.GetOrDefault(Opt::Image),
+                       runtime_options.GetOrDefault(Opt::ImageInstructionSet),
                        // Override the collector type to CC if the read barrier config.
                        kUseReadBarrier ? gc::kCollectorTypeCC : xgc_option.collector_type_,
                        kUseReadBarrier ? BackgroundGcOption(gc::kCollectorTypeCCBackground)
@@ -1432,6 +1393,16 @@
         image_space->VerifyImageAllocations();
       }
     }
+    if (boot_class_path_string_.empty()) {
+      // The bootclasspath is not explicitly specified: construct it from the loaded dex files.
+      const std::vector<const DexFile*>& boot_class_path = GetClassLinker()->GetBootClassPath();
+      std::vector<std::string> dex_locations;
+      dex_locations.reserve(boot_class_path.size());
+      for (const DexFile* dex_file : boot_class_path) {
+        dex_locations.push_back(dex_file->GetLocation());
+      }
+      boot_class_path_string_ = android::base::Join(dex_locations, ':');
+    }
     {
       ScopedTrace trace2("AddImageStringsToTable");
       for (gc::space::ImageSpace* image_space : heap_->GetBootImageSpaces()) {
@@ -1444,12 +1415,24 @@
       DeoptimizeBootImage();
     }
   } else {
+    std::vector<std::string> dex_filenames;
+    Split(boot_class_path_string_, ':', &dex_filenames);
+
+    std::vector<std::string> dex_locations;
+    if (!runtime_options.Exists(Opt::BootClassPathLocations)) {
+      dex_locations = dex_filenames;
+    } else {
+      dex_locations = runtime_options.GetOrDefault(Opt::BootClassPathLocations);
+      CHECK_EQ(dex_filenames.size(), dex_locations.size());
+    }
+
     std::vector<std::unique_ptr<const DexFile>> boot_class_path;
     if (runtime_options.Exists(Opt::BootClassPathDexList)) {
       boot_class_path.swap(*runtime_options.GetOrDefault(Opt::BootClassPathDexList));
     } else {
-      OpenDexFiles(GetBootClassPath(), GetBootClassPathLocations(), &boot_class_path);
+      OpenDexFiles(dex_filenames, dex_locations, &boot_class_path);
     }
+    instruction_set_ = runtime_options.GetOrDefault(Opt::ImageInstructionSet);
     if (!class_linker_->InitWithoutImage(std::move(boot_class_path), &error_msg)) {
       LOG(ERROR) << "Could not initialize without image: " << error_msg;
       return false;
diff --git a/runtime/runtime.h b/runtime/runtime.h
index b76a658..4533376 100644
--- a/runtime/runtime.h
+++ b/runtime/runtime.h
@@ -243,14 +243,8 @@
 
   ~Runtime();
 
-  const std::vector<std::string>& GetBootClassPath() const {
-    return boot_class_path_;
-  }
-
-  const std::vector<std::string>& GetBootClassPathLocations() const {
-    DCHECK(boot_class_path_locations_.empty() ||
-           boot_class_path_locations_.size() == boot_class_path_.size());
-    return boot_class_path_locations_.empty() ? boot_class_path_ : boot_class_path_locations_;
+  const std::string& GetBootClassPathString() const {
+    return boot_class_path_string_;
   }
 
   const std::string& GetClassPathString() const {
@@ -872,8 +866,7 @@
   std::vector<std::string> image_compiler_options_;
   std::string image_location_;
 
-  std::vector<std::string> boot_class_path_;
-  std::vector<std::string> boot_class_path_locations_;
+  std::string boot_class_path_string_;
   std::string class_path_string_;
   std::vector<std::string> properties_;
 
diff --git a/runtime/runtime_options.def b/runtime/runtime_options.def
index 2b2919e..5cec309 100644
--- a/runtime/runtime_options.def
+++ b/runtime/runtime_options.def
@@ -37,7 +37,7 @@
 RUNTIME_OPTIONS_KEY (Unit,                Zygote)
 RUNTIME_OPTIONS_KEY (Unit,                Help)
 RUNTIME_OPTIONS_KEY (Unit,                ShowVersion)
-RUNTIME_OPTIONS_KEY (ParseStringList<':'>,BootClassPath)           // std::vector<std::string>
+RUNTIME_OPTIONS_KEY (std::string,         BootClassPath)
 RUNTIME_OPTIONS_KEY (ParseStringList<':'>,BootClassPathLocations)  // std::vector<std::string>
 RUNTIME_OPTIONS_KEY (std::string,         ClassPath)
 RUNTIME_OPTIONS_KEY (std::string,         Image)
diff --git a/tools/run-libcore-tests.sh b/tools/run-libcore-tests.sh
index 63f1fce..2d39b2a 100755
--- a/tools/run-libcore-tests.sh
+++ b/tools/run-libcore-tests.sh
@@ -46,21 +46,6 @@
   done
 }
 
-function boot_classpath_arg {
-  local dir="$1"
-  local suffix="$2"
-  shift 2
-  printf -- "--vm-arg -Xbootclasspath"
-  for var
-  do
-    printf -- ":${dir}/${var}${suffix}.jar";
-  done
-}
-
-# Note: This must match the TEST_CORE_JARS in Android.common_path.mk
-# because that's what we use for compiling the core.art image.
-BOOT_CLASSPATH_JARS="core-oj core-libart core-simple conscrypt okhttp bouncycastle"
-
 DEPS="core-tests jsr166-tests mockito-target"
 
 for lib in $DEPS
@@ -125,7 +110,6 @@
   if [[ "$1" == "--mode=device" ]]; then
     device_mode=true
     vogar_args="$vogar_args --vm-arg -Ximage:/data/art-test/core.art"
-    vogar_args="$vogar_args $(boot_classpath_arg /system/framework -testdex $BOOT_CLASSPATH_JARS)"
     shift
   elif [[ "$1" == "--mode=host" ]]; then
     # We explicitly give a wrong path for the image, to ensure vogar