Fix boot image extension relocation.

Store boot image begin and size to the extension header as
it is needed for relocation; this requires rewriting the
ImageHeader::IsAppImage(). Also mark the class roots as
visited to satisfy a DCHECK() for extension relocation.

And re-enable loading boot image extensions for tests.

Test: Add relocation tests to dex2oat_image_test.
Test: m test-art-host-gtest
Test: testrunner.py --host --optimizing --relocate --no-relocate
Test: testrunner.py --target --64 --optimizing --relocate
Bug: 119800099
Change-Id: Ie7e883beaa927ca4dc91f0ae660c8de74f7d4ddb
diff --git a/dex2oat/dex2oat_image_test.cc b/dex2oat/dex2oat_image_test.cc
index 567a8da..fc824c1 100644
--- a/dex2oat/dex2oat_image_test.cc
+++ b/dex2oat/dex2oat_image_test.cc
@@ -283,10 +283,17 @@
 
 TEST_F(Dex2oatImageTest, TestExtension) {
   constexpr size_t kReservationSize = 256 * MB;  // This should be enough for the compiled images.
+  // Extend to both directions for maximum relocation difference.
+  static_assert(ART_BASE_ADDRESS_MIN_DELTA < 0);
+  static_assert(ART_BASE_ADDRESS_MAX_DELTA > 0);
+  static_assert(IsAligned<kPageSize>(ART_BASE_ADDRESS_MIN_DELTA));
+  static_assert(IsAligned<kPageSize>(ART_BASE_ADDRESS_MAX_DELTA));
+  constexpr size_t kExtra = ART_BASE_ADDRESS_MAX_DELTA - ART_BASE_ADDRESS_MIN_DELTA;
+  uint32_t min_relocated_address = kBaseAddress + ART_BASE_ADDRESS_MIN_DELTA;
   std::string error_msg;
   MemMap reservation = MemMap::MapAnonymous("Reservation",
-                                            reinterpret_cast<uint8_t*>(kBaseAddress),
-                                            kReservationSize,
+                                            reinterpret_cast<uint8_t*>(min_relocated_address),
+                                            kReservationSize + kExtra,
                                             PROT_NONE,
                                             /*low_4gb=*/ true,
                                             /*reuse=*/ false,
@@ -393,6 +400,7 @@
   // Try to load the boot image with different image locations.
   std::vector<std::string> boot_class_path = libcore_dex_files;
   std::vector<std::unique_ptr<gc::space::ImageSpace>> boot_image_spaces;
+  bool relocate = false;
   MemMap extra_reservation;
   auto load = [&](const std::string& image_location) {
     boot_image_spaces.clear();
@@ -403,7 +411,7 @@
                                                 image_location,
                                                 kRuntimeISA,
                                                 gc::space::ImageSpaceLoadingOrder::kSystemFirst,
-                                                /*relocate=*/ false,
+                                                relocate,
                                                 /*executable=*/ true,
                                                 /*is_zygote=*/ false,
                                                 /*extra_reservation_size=*/ 0u,
@@ -411,57 +419,61 @@
                                                 &extra_reservation);
   };
 
-  // Load primary image with full path.
-  bool load_ok = load(base_location);
-  ASSERT_TRUE(load_ok) << error_msg;
-  ASSERT_FALSE(extra_reservation.IsValid());
-  ASSERT_EQ(head_dex_files.size(), boot_image_spaces.size());
+  for (bool r : { false, true}) {
+    relocate = r;
 
-  // Fail to load primary image with just the name.
-  load_ok = load(base_name);
-  ASSERT_FALSE(load_ok);
+    // Load primary image with full path.
+    bool load_ok = load(base_location);
+    ASSERT_TRUE(load_ok) << error_msg;
+    ASSERT_FALSE(extra_reservation.IsValid());
+    ASSERT_EQ(head_dex_files.size(), boot_image_spaces.size());
 
-  // Fail to load primary image with a search path.
-  load_ok = load("*");
-  ASSERT_FALSE(load_ok);
-  load_ok = load(scratch_dir + "*");
-  ASSERT_FALSE(load_ok);
+    // Fail to load primary image with just the name.
+    load_ok = load(base_name);
+    ASSERT_FALSE(load_ok);
 
-  // Load the primary and first extension with full path.
-  load_ok = load(base_location + ':' + mid_location);
-  ASSERT_TRUE(load_ok) << error_msg;
-  ASSERT_EQ(mid_bcp.size(), boot_image_spaces.size());
+    // Fail to load primary image with a search path.
+    load_ok = load("*");
+    ASSERT_FALSE(load_ok);
+    load_ok = load(scratch_dir + "*");
+    ASSERT_FALSE(load_ok);
 
-  // Load the primary with full path and fail to load first extension without full path.
-  load_ok = load(base_location + ':' + mid_name);
-  ASSERT_TRUE(load_ok) << error_msg;  // Primary image loaded successfully.
-  ASSERT_EQ(head_dex_files.size(), boot_image_spaces.size());  // But only the primary image.
+    // Load the primary and first extension with full path.
+    load_ok = load(base_location + ':' + mid_location);
+    ASSERT_TRUE(load_ok) << error_msg;
+    ASSERT_EQ(mid_bcp.size(), boot_image_spaces.size());
 
-  // Load all the libcore images with full paths.
-  load_ok = load(base_location + ':' + mid_location + ':' + tail_location);
-  ASSERT_TRUE(load_ok) << error_msg;
-  ASSERT_EQ(full_bcp.size(), boot_image_spaces.size());
+    // Load the primary with full path and fail to load first extension without full path.
+    load_ok = load(base_location + ':' + mid_name);
+    ASSERT_TRUE(load_ok) << error_msg;  // Primary image loaded successfully.
+    ASSERT_EQ(head_dex_files.size(), boot_image_spaces.size());  // But only the primary image.
 
-  // Load the primary and first extension with full paths, fail to load second extension by name.
-  load_ok = load(base_location + ':' + mid_location + ':' + tail_name);
-  ASSERT_TRUE(load_ok) << error_msg;
-  ASSERT_EQ(mid_bcp.size(), boot_image_spaces.size());
+    // Load all the libcore images with full paths.
+    load_ok = load(base_location + ':' + mid_location + ':' + tail_location);
+    ASSERT_TRUE(load_ok) << error_msg;
+    ASSERT_EQ(full_bcp.size(), boot_image_spaces.size());
 
-  // Load the primary with full path and fail to load first extension without full path,
-  // fail to load second extension because it depends on the first.
-  load_ok = load(base_location + ':' + mid_name + ':' + tail_location);
-  ASSERT_TRUE(load_ok) << error_msg;  // Primary image loaded successfully.
-  ASSERT_EQ(head_dex_files.size(), boot_image_spaces.size());  // But only the primary image.
+    // Load the primary and first extension with full paths, fail to load second extension by name.
+    load_ok = load(base_location + ':' + mid_location + ':' + tail_name);
+    ASSERT_TRUE(load_ok) << error_msg;
+    ASSERT_EQ(mid_bcp.size(), boot_image_spaces.size());
 
-  // Load the primary with full path and extensions with a specified search path.
-  load_ok = load(base_location + ':' + scratch_dir + '*');
-  ASSERT_TRUE(load_ok) << error_msg;
-  ASSERT_EQ(full_bcp.size(), boot_image_spaces.size());
+    // Load the primary with full path and fail to load first extension without full path,
+    // fail to load second extension because it depends on the first.
+    load_ok = load(base_location + ':' + mid_name + ':' + tail_location);
+    ASSERT_TRUE(load_ok) << error_msg;  // Primary image loaded successfully.
+    ASSERT_EQ(head_dex_files.size(), boot_image_spaces.size());  // But only the primary image.
 
-  // Load the primary with full path and fail to find extensions in BCP path.
-  load_ok = load(base_location + ":*");
-  ASSERT_TRUE(load_ok) << error_msg;
-  ASSERT_EQ(head_dex_files.size(), boot_image_spaces.size());
+    // Load the primary with full path and extensions with a specified search path.
+    load_ok = load(base_location + ':' + scratch_dir + '*');
+    ASSERT_TRUE(load_ok) << error_msg;
+    ASSERT_EQ(full_bcp.size(), boot_image_spaces.size());
+
+    // Load the primary with full path and fail to find extensions in BCP path.
+    load_ok = load(base_location + ":*");
+    ASSERT_TRUE(load_ok) << error_msg;
+    ASSERT_EQ(head_dex_files.size(), boot_image_spaces.size());
+  }
 
   // Now copy the libcore dex files to the `scratch_dir` and retry loading the boot image
   // with BCP in the scratch_dir so that the images can be found based on BCP paths.
@@ -475,52 +487,56 @@
     bcp_component = new_location;
   }
 
-  // Loading the primary image with just the name now succeeds.
-  load_ok = load(base_name);
-  ASSERT_TRUE(load_ok) << error_msg;
+  for (bool r : { false, true}) {
+    relocate = r;
 
-  // Loading the primary image with a search path still fails.
-  load_ok = load("*");
-  ASSERT_FALSE(load_ok);
-  load_ok = load(scratch_dir + "*");
-  ASSERT_FALSE(load_ok);
+    // Loading the primary image with just the name now succeeds.
+    bool load_ok = load(base_name);
+    ASSERT_TRUE(load_ok) << error_msg;
 
-  // Load the primary and first extension without paths.
-  load_ok = load(base_name + ':' + mid_name);
-  ASSERT_TRUE(load_ok) << error_msg;
-  ASSERT_EQ(mid_bcp.size(), boot_image_spaces.size());
+    // Loading the primary image with a search path still fails.
+    load_ok = load("*");
+    ASSERT_FALSE(load_ok);
+    load_ok = load(scratch_dir + "*");
+    ASSERT_FALSE(load_ok);
 
-  // Load the primary with full path and the first extension without full path.
-  load_ok = load(base_location + ':' + mid_name);
-  ASSERT_TRUE(load_ok) << error_msg;  // Loaded successfully.
-  ASSERT_EQ(mid_bcp.size(), boot_image_spaces.size());  // Including the extension.
+    // Load the primary and first extension without paths.
+    load_ok = load(base_name + ':' + mid_name);
+    ASSERT_TRUE(load_ok) << error_msg;
+    ASSERT_EQ(mid_bcp.size(), boot_image_spaces.size());
 
-  // Load all the libcore images without paths.
-  load_ok = load(base_name + ':' + mid_name + ':' + tail_name);
-  ASSERT_TRUE(load_ok) << error_msg;
-  ASSERT_EQ(full_bcp.size(), boot_image_spaces.size());
+    // Load the primary with full path and the first extension without full path.
+    load_ok = load(base_location + ':' + mid_name);
+    ASSERT_TRUE(load_ok) << error_msg;  // Loaded successfully.
+    ASSERT_EQ(mid_bcp.size(), boot_image_spaces.size());  // Including the extension.
 
-  // Load the primary and first extension with full paths and second extension by name.
-  load_ok = load(base_location + ':' + mid_location + ':' + tail_name);
-  ASSERT_TRUE(load_ok) << error_msg;
-  ASSERT_EQ(full_bcp.size(), boot_image_spaces.size());
+    // Load all the libcore images without paths.
+    load_ok = load(base_name + ':' + mid_name + ':' + tail_name);
+    ASSERT_TRUE(load_ok) << error_msg;
+    ASSERT_EQ(full_bcp.size(), boot_image_spaces.size());
 
-  // Load the primary with full path, first extension without path,
-  // and second extension with full path.
-  load_ok = load(base_location + ':' + mid_name + ':' + tail_location);
-  ASSERT_TRUE(load_ok) << error_msg;  // Loaded successfully.
-  ASSERT_EQ(full_bcp.size(), boot_image_spaces.size());  // Including both extensions.
+    // Load the primary and first extension with full paths and second extension by name.
+    load_ok = load(base_location + ':' + mid_location + ':' + tail_name);
+    ASSERT_TRUE(load_ok) << error_msg;
+    ASSERT_EQ(full_bcp.size(), boot_image_spaces.size());
 
-  // Load the primary with full path and find both extensions in BCP path.
-  load_ok = load(base_location + ":*");
-  ASSERT_TRUE(load_ok) << error_msg;
-  ASSERT_EQ(full_bcp.size(), boot_image_spaces.size());
+    // Load the primary with full path, first extension without path,
+    // and second extension with full path.
+    load_ok = load(base_location + ':' + mid_name + ':' + tail_location);
+    ASSERT_TRUE(load_ok) << error_msg;  // Loaded successfully.
+    ASSERT_EQ(full_bcp.size(), boot_image_spaces.size());  // Including both extensions.
 
-  // Fail to load any images with invalid image locations (named component after search paths).
-  load_ok = load(base_location + ":*:" + tail_location);
-  ASSERT_FALSE(load_ok);
-  load_ok = load(base_location + ':' + scratch_dir + "*:" + tail_location);
-  ASSERT_FALSE(load_ok);
+    // Load the primary with full path and find both extensions in BCP path.
+    load_ok = load(base_location + ":*");
+    ASSERT_TRUE(load_ok) << error_msg;
+    ASSERT_EQ(full_bcp.size(), boot_image_spaces.size());
+
+    // Fail to load any images with invalid image locations (named component after search paths).
+    load_ok = load(base_location + ":*:" + tail_location);
+    ASSERT_FALSE(load_ok);
+    load_ok = load(base_location + ':' + scratch_dir + "*:" + tail_location);
+    ASSERT_FALSE(load_ok);
+  }
 
   ClearDirectory(scratch_dir.c_str());
   int rmdir_result = rmdir(scratch_dir.c_str());
diff --git a/dex2oat/linker/image_writer.cc b/dex2oat/linker/image_writer.cc
index 2c0447b..ede5ef7 100644
--- a/dex2oat/linker/image_writer.cc
+++ b/dex2oat/linker/image_writer.cc
@@ -2693,8 +2693,8 @@
       PointerToLowMemUInt32(image_info.oat_data_begin_),
       PointerToLowMemUInt32(oat_data_end),
       PointerToLowMemUInt32(oat_file_end),
-      compiler_options_.IsAppImage() ? boot_image_begin_ : 0u,
-      compiler_options_.IsAppImage() ? boot_image_size_ : 0u,
+      boot_image_begin_,
+      boot_image_size_,
       static_cast<uint32_t>(target_ptr_size_));
 }
 
diff --git a/runtime/gc/space/image_space.cc b/runtime/gc/space/image_space.cc
index e19f964..51bc6a8 100644
--- a/runtime/gc/space/image_space.cc
+++ b/runtime/gc/space/image_space.cc
@@ -2227,6 +2227,7 @@
                                                            simple_relocate_visitor);
 
     // Retrieve the Class.class, Method.class and Constructor.class needed in the loops below.
+    ObjPtr<mirror::ObjectArray<mirror::Class>> class_roots;
     ObjPtr<mirror::Class> class_class;
     ObjPtr<mirror::Class> method_class;
     ObjPtr<mirror::Class> constructor_class;
@@ -2241,9 +2242,8 @@
           kExtension ? source_size - image_size : image_size);
       int32_t class_roots_index = enum_cast<int32_t>(ImageHeader::kClassRoots);
       DCHECK_LT(class_roots_index, image_roots->GetLength<kVerifyNone>());
-      ObjPtr<mirror::ObjectArray<mirror::Class>> class_roots =
-          ObjPtr<mirror::ObjectArray<mirror::Class>>::DownCast(base_relocate_visitor(
-              image_roots->GetWithoutChecks<kVerifyNone>(class_roots_index).Ptr()));
+      class_roots = ObjPtr<mirror::ObjectArray<mirror::Class>>::DownCast(base_relocate_visitor(
+          image_roots->GetWithoutChecks<kVerifyNone>(class_roots_index).Ptr()));
       if (kExtension) {
         DCHECK(patched_objects->Test(class_roots.Ptr()));
         class_class = GetClassRoot<mirror::Class, kWithoutReadBarrier>(class_roots);
@@ -2375,6 +2375,12 @@
         pos += RoundUp(object->SizeOf<kVerifyNone>(), kObjectAlignment);
       }
     }
+    if (kIsDebugBuild && !kExtension) {
+      // We used just Test() instead of Set() above but we need to use Set()
+      // for class roots to satisfy a DCHECK() for extensions.
+      DCHECK(!patched_objects->Test(class_roots.Ptr()));
+      patched_objects->Set(class_roots.Ptr());
+    }
   }
 
   void MaybeRelocateSpaces(const std::vector<std::unique_ptr<ImageSpace>>& spaces,
diff --git a/runtime/image.cc b/runtime/image.cc
index 256b957..08b81c1 100644
--- a/runtime/image.cc
+++ b/runtime/image.cc
@@ -93,6 +93,13 @@
   }
 }
 
+bool ImageHeader::IsAppImage() const {
+  // Unlike boot image and boot image extensions which include address space for
+  // oat files in their reservation size, app images are loaded separately from oat
+  // files and their reservation size is the image size rounded up to full page.
+  return image_reservation_size_ == RoundUp(image_size_, kPageSize);
+}
+
 bool ImageHeader::IsValid() const {
   if (memcmp(magic_, kImageMagic, sizeof(kImageMagic)) != 0) {
     return false;
diff --git a/runtime/image.h b/runtime/image.h
index 13bf112..a2d163a 100644
--- a/runtime/image.h
+++ b/runtime/image.h
@@ -354,11 +354,7 @@
     return data_size_;
   }
 
-  bool IsAppImage() const {
-    // App images currently require a boot image, if the size is non zero then it is an app image
-    // header.
-    return boot_image_size_ != 0u;
-  }
+  bool IsAppImage() const;
 
   // Visit mirror::Objects in the section starting at base.
   // TODO: Delete base parameter if it is always equal to GetImageBegin.
diff --git a/test/run-test b/test/run-test
index eeeefbb..72e7562 100755
--- a/test/run-test
+++ b/test/run-test
@@ -672,12 +672,12 @@
 elif [ "$runtime" = "art" ]; then
     if [ "$target_mode" = "no" ]; then
         guess_host_arch_name
-        run_args+=(--boot "${ANDROID_HOST_OUT}/framework/core${image_suffix}.art")
+        run_args+=(--boot "${ANDROID_HOST_OUT}/framework/core${image_suffix}.art:*")
         run_args+=(--runtime-option "-Djava.library.path=${host_lib_root}/lib${suffix64}:${host_lib_root}/nativetest${suffix64}")
     else
         guess_target_arch_name
         run_args+=(--runtime-option "-Djava.library.path=/data/nativetest${suffix64}/art/${target_arch_name}")
-        run_args+=(--boot "/data/art-test/core${image_suffix}.art")
+        run_args+=(--boot "/data/art-test/core${image_suffix}.art:/data/art-test/*")
     fi
     if [ "$relocate" = "yes" ]; then
       run_args+=(--relocate)