patchoat: add ability to verify relocated .art files

Patchoat can verify that a relocated .art file hasn't been modified
after generation using the --verify flag.

Test: adb shell /system/bin/patchoat --verify \
        --input-image-location=/system/framework/boot.art \
        --output-image-file=/data/dalvik-cache/arm64/system@framework@boot.art \
        --instruction-set=arm64
Bug: 66697305

Change-Id: If6ea02a0527381c520078cd6f3ae2c275a8a8ab1
diff --git a/patchoat/patchoat.cc b/patchoat/patchoat.cc
index 6c9cf86..ba09cb3 100644
--- a/patchoat/patchoat.cc
+++ b/patchoat/patchoat.cc
@@ -25,12 +25,14 @@
 #include <string>
 #include <vector>
 
+#include "android-base/file.h"
 #include "android-base/stringprintf.h"
 #include "android-base/strings.h"
 
 #include "art_field-inl.h"
 #include "art_method-inl.h"
 #include "base/dumpable.h"
+#include "base/file_utils.h"
 #include "base/logging.h"  // For InitLogging.
 #include "base/memory_tool.h"
 #include "base/scoped_flock.h"
@@ -235,6 +237,126 @@
   return true;
 }
 
+static bool CheckImageIdenticalToOriginalExceptForRelocation(
+    const std::string& relocated_filename,
+    const std::string& original_filename,
+    std::string* error_msg) {
+  *error_msg = "";
+  std::string rel_filename = original_filename + ".rel";
+  std::unique_ptr<File> rel_file(OS::OpenFileForReading(rel_filename.c_str()));
+  if (rel_file.get() == nullptr) {
+    *error_msg = StringPrintf("Failed to open image relocation file %s", rel_filename.c_str());
+    return false;
+  }
+  int64_t rel_size = rel_file->GetLength();
+  if (rel_size < 0) {
+    *error_msg = StringPrintf("Error while getting size of image relocation file %s",
+                              rel_filename.c_str());
+    return false;
+  }
+  std::unique_ptr<uint8_t[]> rel(new uint8_t[rel_size]);
+  if (!rel_file->ReadFully(rel.get(), rel_size)) {
+    *error_msg = StringPrintf("Failed to read image relocation file %s", rel_filename.c_str());
+    return false;
+  }
+
+  std::unique_ptr<File> image_file(OS::OpenFileForReading(relocated_filename.c_str()));
+  if (image_file.get() == nullptr) {
+    *error_msg = StringPrintf("Unable to open relocated image file  %s",
+                              relocated_filename.c_str());
+    return false;
+  }
+
+  int64_t image_size = image_file->GetLength();
+  if (image_size < 0) {
+    *error_msg = StringPrintf("Error while getting size of relocated image file %s",
+                              relocated_filename.c_str());
+    return false;
+  }
+  if ((image_size % 4) != 0) {
+    *error_msg =
+        StringPrintf(
+            "Relocated image file %s size not multiple of 4: %jd",
+                relocated_filename.c_str(), image_size);
+    return false;
+  }
+  if (image_size > UINT32_MAX) {
+    *error_msg =
+        StringPrintf(
+            "Relocated image file %s too large: %jd" , relocated_filename.c_str(), image_size);
+    return false;
+  }
+
+  std::unique_ptr<uint8_t[]> image(new uint8_t[image_size]);
+  if (!image_file->ReadFully(image.get(), image_size)) {
+    *error_msg = StringPrintf("Failed to read relocated image file %s", relocated_filename.c_str());
+    return false;
+  }
+
+  const uint8_t* original_image_digest = rel.get();
+  if (rel_size < SHA256_DIGEST_LENGTH) {
+    *error_msg = StringPrintf("Malformed image relocation file %s: too short",
+                              rel_filename.c_str());
+    return false;
+  }
+
+  const ImageHeader& image_header = *reinterpret_cast<const ImageHeader*>(image.get());
+  off_t expected_diff = image_header.GetPatchDelta();
+
+  if (expected_diff == 0) {
+    *error_msg = StringPrintf("Unsuported patch delta of zero in %s",
+                              relocated_filename.c_str());
+    return false;
+  }
+
+  // Relocated image is expected to differ from the original due to relocation.
+  // Unrelocate the image in memory to compensate.
+  uint8_t* image_start = image.get();
+  const uint8_t* rel_end = &rel[rel_size];
+  const uint8_t* rel_ptr = &rel[SHA256_DIGEST_LENGTH];
+  // The remaining .rel file consists of offsets at which relocation should've occurred.
+  // For each offset, we "unrelocate" the image by subtracting the expected relocation
+  // diff value (as specified in the image header).
+  //
+  // Each offset is encoded as a delta/diff relative to the previous offset. With the
+  // very first offset being encoded relative to offset 0.
+  // Deltas are encoded using little-endian 7 bits per byte encoding, with all bytes except
+  // the last one having the highest bit set.
+  uint32_t offset = 0;
+  while (rel_ptr != rel_end) {
+    uint32_t offset_delta = 0;
+    if (DecodeUnsignedLeb128Checked(&rel_ptr, rel_end, &offset_delta)) {
+      offset += offset_delta;
+      uint32_t *image_value = reinterpret_cast<uint32_t*>(image_start + offset);
+      *image_value -= expected_diff;
+    } else {
+      *error_msg =
+          StringPrintf(
+              "Malformed image relocation file %s: "
+              "last byte has it's most significant bit set",
+              rel_filename.c_str());
+      return false;
+    }
+  }
+
+  // Image in memory is now supposed to be identical to the original.  We
+  // confirm this by comparing the digest of the in-memory image to the expected
+  // digest from relocation file.
+  uint8_t image_digest[SHA256_DIGEST_LENGTH];
+  SHA256(image.get(), image_size, image_digest);
+  if (memcmp(image_digest, original_image_digest, SHA256_DIGEST_LENGTH) != 0) {
+    *error_msg =
+        StringPrintf(
+            "Relocated image %s does not match the original %s after unrelocation",
+            relocated_filename.c_str(),
+            original_filename.c_str());
+    return false;
+  }
+
+  // Relocated image is identical to the original, once relocations are taken into account
+  return true;
+}
+
 bool PatchOat::Patch(const std::string& image_location,
                      off_t delta,
                      const std::string& output_image_directory,
@@ -475,6 +597,86 @@
   return true;
 }
 
+bool PatchOat::Verify(const std::string& image_location,
+                      const std::string& output_image_directory,
+                      InstructionSet isa,
+                      TimingLogger* timings) {
+  if (image_location.empty()) {
+    LOG(ERROR) << "Original image file not provided";
+    return false;
+  }
+  if (output_image_directory.empty()) {
+    LOG(ERROR) << "Relocated image directory not provided";
+    return false;
+  }
+
+  TimingLogger::ScopedTiming t("Runtime Setup", timings);
+
+  CHECK_NE(isa, InstructionSet::kNone);
+  const char* isa_name = GetInstructionSetString(isa);
+
+  // Set up the runtime
+  RuntimeOptions options;
+  NoopCompilerCallbacks callbacks;
+  options.push_back(std::make_pair("compilercallbacks", &callbacks));
+  std::string img = "-Ximage:" + image_location;
+  options.push_back(std::make_pair(img.c_str(), nullptr));
+  options.push_back(std::make_pair("imageinstructionset", reinterpret_cast<const void*>(isa_name)));
+  options.push_back(std::make_pair("-Xno-sig-chain", nullptr));
+  if (!Runtime::Create(options, false)) {
+    LOG(ERROR) << "Unable to initialize runtime";
+    return false;
+  }
+  std::unique_ptr<Runtime> runtime(Runtime::Current());
+
+  // Runtime::Create acquired the mutator_lock_ that is normally given away when we Runtime::Start,
+  // give it away now and then switch to a more manageable ScopedObjectAccess.
+  Thread::Current()->TransitionFromRunnableToSuspended(kNative);
+  ScopedObjectAccess soa(Thread::Current());
+
+  t.NewTiming("Image Verification setup");
+  std::vector<gc::space::ImageSpace*> spaces = Runtime::Current()->GetHeap()->GetBootImageSpaces();
+
+  // TODO: Check that no other .rel files exist in the original dir
+
+  bool success = true;
+  std::string image_location_dir = android::base::Dirname(image_location);
+  for (size_t i = 0; i < spaces.size(); ++i) {
+    gc::space::ImageSpace* space = spaces[i];
+    std::string image_filename = space->GetImageLocation();
+
+    std::string relocated_image_filename;
+    std::string error_msg;
+    if (!GetDalvikCacheFilename(image_filename.c_str(),
+            output_image_directory.c_str(), &relocated_image_filename, &error_msg)) {
+      LOG(ERROR) << "Failed to find relocated image file name: " << error_msg;
+      success = false;
+      break;
+    }
+    // location:     /system/framework/boot.art
+    // isa:          arm64
+    // basename:     boot.art
+    // original:     /system/framework/arm64/boot.art
+    // relocation:   /system/framework/arm64/boot.art.rel
+    std::string original_image_filename = GetSystemImageFilename(image_filename.c_str(), isa);
+
+    if (!CheckImageIdenticalToOriginalExceptForRelocation(
+            relocated_image_filename, original_image_filename, &error_msg)) {
+      LOG(ERROR) << error_msg;
+      success = false;
+      break;
+    }
+  }
+
+  if (!kIsDebugBuild && !(RUNNING_ON_MEMORY_TOOL && kMemoryToolDetectsLeaks)) {
+    // We want to just exit on non-debug builds, not bringing the runtime down
+    // in an orderly fashion. So release the following fields.
+    runtime.release();
+  }
+
+  return success;
+}
+
 bool PatchOat::WriteImage(File* out) {
   TimingLogger::ScopedTiming t("Writing image File", timings_);
   std::string error_msg;
@@ -919,6 +1121,8 @@
   UsageError("  --base-offset-delta=<delta>: Specify the amount to change the old base-offset by.");
   UsageError("      This value may be negative.");
   UsageError("");
+  UsageError("  --verify: Verify an existing patched file instead of creating one.");
+  UsageError("");
   UsageError("  --dump-timings: dump out patch timing information");
   UsageError("");
   UsageError("  --no-dump-timings: do not dump out patch timing information");
@@ -927,16 +1131,16 @@
   exit(EXIT_FAILURE);
 }
 
-static int patchoat_image(TimingLogger& timings,
-                          InstructionSet isa,
-                          const std::string& input_image_location,
-                          const std::string& output_image_filename,
-                          const std::string& output_image_relocation_filename,
-                          off_t base_delta,
-                          bool base_delta_set,
-                          bool debug) {
+static int patchoat_patch_image(TimingLogger& timings,
+                                InstructionSet isa,
+                                const std::string& input_image_location,
+                                const std::string& output_image_directory,
+                                const std::string& output_image_relocation_filename,
+                                off_t base_delta,
+                                bool base_delta_set,
+                                bool debug) {
   CHECK(!input_image_location.empty());
-  if ((output_image_filename.empty()) && (output_image_relocation_filename.empty())) {
+  if ((output_image_directory.empty()) && (output_image_relocation_filename.empty())) {
     Usage("Image patching requires --output-image-file or --output-image-relocation-file");
   }
 
@@ -956,8 +1160,6 @@
 
   TimingLogger::ScopedTiming pt("patch image and oat", &timings);
 
-  std::string output_image_directory =
-      output_image_filename.substr(0, output_image_filename.find_last_of('/'));
   std::string output_image_relocation_directory =
       output_image_relocation_filename.substr(
           0, output_image_relocation_filename.find_last_of('/'));
@@ -976,6 +1178,26 @@
   return ret ? EXIT_SUCCESS : EXIT_FAILURE;
 }
 
+static int patchoat_verify_image(TimingLogger& timings,
+                                 InstructionSet isa,
+                                 const std::string& input_image_location,
+                                 const std::string& output_image_directory) {
+  CHECK(!input_image_location.empty());
+  TimingLogger::ScopedTiming pt("verify image and oat", &timings);
+
+  bool ret =
+      PatchOat::Verify(
+          input_image_location,
+          output_image_directory,
+          isa,
+          &timings);
+
+  if (kIsDebugBuild) {
+    LOG(INFO) << "Exiting with return ... " << ret;
+  }
+  return ret ? EXIT_SUCCESS : EXIT_FAILURE;
+}
+
 static int patchoat(int argc, char **argv) {
   InitLogging(argv, Runtime::Abort);
   MemMap::Init();
@@ -1003,6 +1225,7 @@
   off_t base_delta = 0;
   bool base_delta_set = false;
   bool dump_timings = kIsDebugBuild;
+  bool verify = false;
 
   for (int i = 0; i < argc; ++i) {
     const StringPiece option(argv[i]);
@@ -1034,24 +1257,40 @@
       dump_timings = true;
     } else if (option == "--no-dump-timings") {
       dump_timings = false;
+    } else if (option == "--verify") {
+      verify = true;
     } else {
       Usage("Unknown argument %s", option.data());
     }
   }
 
+  // TODO: Have calls to patchoat pass in the output_image directory instead of
+  // the output_image_filename.
+  std::string output_image_directory;
+  if (!output_image_filename.empty())
+    output_image_directory = android::base::Dirname(output_image_filename);
+
   // The instruction set is mandatory. This simplifies things...
   if (!isa_set) {
     Usage("Instruction set must be set.");
   }
 
-  int ret = patchoat_image(timings,
-                           isa,
-                           input_image_location,
-                           output_image_filename,
-                           output_image_relocation_filename,
-                           base_delta,
-                           base_delta_set,
-                           debug);
+  int ret;
+  if (verify) {
+    ret = patchoat_verify_image(timings,
+                                isa,
+                                input_image_location,
+                                output_image_directory);
+  } else {
+    ret = patchoat_patch_image(timings,
+                               isa,
+                               input_image_location,
+                               output_image_directory,
+                               output_image_relocation_filename,
+                               base_delta,
+                               base_delta_set,
+                               debug);
+  }
 
   timings.EndTiming();
   if (dump_timings) {
diff --git a/patchoat/patchoat.h b/patchoat/patchoat.h
index 1033a2e..ba59d57 100644
--- a/patchoat/patchoat.h
+++ b/patchoat/patchoat.h
@@ -53,6 +53,10 @@
                     const std::string& output_image_relocation_directory,
                     InstructionSet isa,
                     TimingLogger* timings);
+  static bool Verify(const std::string& image_location,
+                     const std::string& output_image_filename,
+                     InstructionSet isa,
+                     TimingLogger* timings);
 
   // Generates a patch which can be used to efficiently relocate the original file or to check that
   // a relocated file matches the original. The patch is generated from the difference of the
diff --git a/patchoat/patchoat_test.cc b/patchoat/patchoat_test.cc
index 90cb4f8..69c6bfa 100644
--- a/patchoat/patchoat_test.cc
+++ b/patchoat/patchoat_test.cc
@@ -124,18 +124,36 @@
     return RunDex2OatOrPatchoat(argv, error_msg);
   }
 
-  bool RelocateBootImage(const std::string& input_image_location,
-                         const std::string& output_image_filename,
-                         off_t base_offset_delta,
-                         std::string* error_msg) {
+  static std::vector<std::string> BasePatchoatCommand(const std::string& input_image_location,
+                                                      off_t base_offset_delta) {
     Runtime* const runtime = Runtime::Current();
     std::vector<std::string> argv;
     argv.push_back(runtime->GetPatchoatExecutable());
     argv.push_back("--input-image-location=" + input_image_location);
-    argv.push_back("--output-image-file=" + output_image_filename);
     argv.push_back(StringPrintf("--base-offset-delta=0x%jx", (intmax_t) base_offset_delta));
     argv.push_back(StringPrintf("--instruction-set=%s", GetInstructionSetString(kRuntimeISA)));
 
+    return argv;
+  }
+
+  bool RelocateBootImage(const std::string& input_image_location,
+                         const std::string& output_image_filename,
+                         off_t base_offset_delta,
+                         std::string* error_msg) {
+    std::vector<std::string> argv = BasePatchoatCommand(input_image_location, base_offset_delta);
+    argv.push_back("--output-image-file=" + output_image_filename);
+
+    return RunDex2OatOrPatchoat(argv, error_msg);
+  }
+
+  bool VerifyBootImage(const std::string& input_image_location,
+                       const std::string& output_image_filename,
+                       off_t base_offset_delta,
+                       std::string* error_msg) {
+    std::vector<std::string> argv = BasePatchoatCommand(input_image_location, base_offset_delta);
+    argv.push_back("--output-image-file=" + output_image_filename);
+    argv.push_back("--verify");
+
     return RunDex2OatOrPatchoat(argv, error_msg);
   }
 
@@ -143,13 +161,8 @@
                                 const std::string& output_rel_filename,
                                 off_t base_offset_delta,
                                 std::string* error_msg) {
-    Runtime* const runtime = Runtime::Current();
-    std::vector<std::string> argv;
-    argv.push_back(runtime->GetPatchoatExecutable());
-    argv.push_back("--input-image-location=" + input_image_location);
+    std::vector<std::string> argv = BasePatchoatCommand(input_image_location, base_offset_delta);
     argv.push_back("--output-image-relocation-file=" + output_rel_filename);
-    argv.push_back(StringPrintf("--base-offset-delta=0x%jx", (intmax_t) base_offset_delta));
-    argv.push_back(StringPrintf("--instruction-set=%s", GetInstructionSetString(kRuntimeISA)));
 
     return RunDex2OatOrPatchoat(argv, error_msg);
   }
@@ -280,34 +293,6 @@
   }
 
   bool BinaryDiff(
-      const std::string& filename1,
-      const std::vector<uint8_t>& data1,
-      const std::string& filename2,
-      const std::vector<uint8_t>& data2,
-      std::string* error_msg) {
-    if (data1.size() != data1.size()) {
-      *error_msg =
-          StringPrintf(
-              "%s and %s are of different size: %zu vs %zu",
-              filename1.c_str(),
-              filename2.c_str(),
-              data1.size(),
-              data2.size());
-      return true;
-    }
-    size_t size = data1.size();
-    for (size_t i = 0; i < size; i++) {
-      if (data1[i] != data2[i]) {
-        *error_msg =
-            StringPrintf("%s and %s differ at offset %zu", filename1.c_str(), filename2.c_str(), i);
-        return true;
-      }
-    }
-
-    return false;
-  }
-
-  bool BinaryDiff(
       const std::string& filename1, const std::string& filename2, std::string* error_msg) {
     std::string read_error_msg;
     std::vector<uint8_t> image1;
@@ -320,97 +305,26 @@
       *error_msg = StringPrintf("Failed to read %s: %s", filename2.c_str(), read_error_msg.c_str());
       return true;
     }
-    return BinaryDiff(filename1, image1, filename2, image2, error_msg);
-  }
-
-  bool IsImageIdenticalToOriginalExceptForRelocation(
-      const std::string& relocated_filename,
-      const std::string& original_filename,
-      const std::string& rel_filename,
-      std::string* error_msg) {
-    *error_msg = "";
-    std::string read_error_msg;
-    std::vector<uint8_t> rel;
-    if (!ReadFully(rel_filename, &rel, &read_error_msg)) {
-      *error_msg =
-          StringPrintf("Failed to read %s: %s", rel_filename.c_str(), read_error_msg.c_str());
-      return false;
-    }
-    std::vector<uint8_t> relocated;
-    if (!ReadFully(relocated_filename, &relocated, &read_error_msg)) {
-      *error_msg =
-          StringPrintf("Failed to read %s: %s", relocated_filename.c_str(), read_error_msg.c_str());
-      return false;
-    }
-
-    size_t image_size = relocated.size();
-    if ((image_size % 4) != 0) {
+    if (image1.size() != image1.size()) {
       *error_msg =
           StringPrintf(
-              "Relocated image file %s size not multiple of 4: %zu",
-                  relocated_filename.c_str(), image_size);
-      return false;
+              "%s and %s are of different size: %zu vs %zu",
+              filename1.c_str(),
+              filename2.c_str(),
+              image1.size(),
+              image2.size());
+      return true;
     }
-    if (image_size > UINT32_MAX) {
-      *error_msg =
-          StringPrintf(
-              "Relocated image file %s too large: %zu" , relocated_filename.c_str(), image_size);
-      return false;
-    }
-
-    const ImageHeader& relocated_header = *reinterpret_cast<const ImageHeader*>(relocated.data());
-    off_t expected_diff = relocated_header.GetPatchDelta();
-
-    if (expected_diff != 0) {
-      // Relocated image is expected to differ from the original due to relocation.
-      // Unrelocate the image in memory to compensate.
-      uint8_t* image_start = relocated.data();
-      const uint8_t* rel_end = &rel[rel.size()];
-      if (rel.size() < SHA256_DIGEST_LENGTH) {
+    size_t size = image1.size();
+    for (size_t i = 0; i < size; i++) {
+      if (image1[i] != image2[i]) {
         *error_msg =
-            StringPrintf("Malformed image relocation file %s: too short", rel_filename.c_str());
-        return false;
-      }
-      const uint8_t* rel_ptr = &rel[SHA256_DIGEST_LENGTH];
-      // The remaining .rel file consists of offsets at which relocation should've occurred.
-      // For each offset, we "unrelocate" the image by subtracting the expected relocation
-      // diff value (as specified in the image header).
-      //
-      // Each offset is encoded as a delta/diff relative to the previous offset. With the
-      // very first offset being encoded relative to offset 0.
-      // Deltas are encoded using little-endian 7 bits per byte encoding, with all bytes except
-      // the last one having the highest bit set.
-      uint32_t offset = 0;
-      while (rel_ptr != rel_end) {
-        uint32_t offset_delta = 0;
-        if (DecodeUnsignedLeb128Checked(&rel_ptr, rel_end, &offset_delta)) {
-          offset += offset_delta;
-          uint32_t *image_value = reinterpret_cast<uint32_t*>(image_start + offset);
-          *image_value -= expected_diff;
-        } else {
-            *error_msg =
-                StringPrintf(
-                    "Malformed image relocation file %s: "
-                    "last byte has it's most significant bit set",
-                    rel_filename.c_str());
-            return false;
-        }
+            StringPrintf("%s and %s differ at offset %zu", filename1.c_str(), filename2.c_str(), i);
+        return true;
       }
     }
 
-    // Image in memory is now supposed to be identical to the original. Compare it to the original.
-    std::vector<uint8_t> original;
-    if (!ReadFully(original_filename, &original, &read_error_msg)) {
-      *error_msg =
-          StringPrintf("Failed to read %s: %s", original_filename.c_str(), read_error_msg.c_str());
-      return false;
-    }
-    if (BinaryDiff(relocated_filename, relocated, original_filename, original, error_msg)) {
-      return false;
-    }
-
-    // Relocated image is identical to the original, once relocations are taken into account
-    return true;
+    return false;
   }
 };
 
@@ -524,7 +438,7 @@
 #endif
 }
 
-TEST_F(PatchoatTest, RelFileSufficientToUnpatch) {
+TEST_F(PatchoatTest, RelFileVerification) {
   // This test checks that a boot image relocated using patchoat can be unrelocated using the .rel
   // file created by patchoat.
 
@@ -546,10 +460,6 @@
   }
 
   // Generate image relocation file for the original boot image
-  ScratchFile rel_scratch;
-  rel_scratch.Unlink();
-  std::string rel_dir = rel_scratch.GetFilename();
-  ASSERT_EQ(0, mkdir(rel_dir.c_str(), 0700));
   std::string dex2oat_orig_with_arch_dir =
       dex2oat_orig_dir + "/" + GetInstructionSetString(kRuntimeISA);
   // The arch-including symlink is needed by patchoat
@@ -557,7 +467,7 @@
   off_t base_addr_delta = 0x100000;
   if (!GenerateBootImageRelFile(
       dex2oat_orig_dir + "/boot.art",
-      rel_dir + "/boot.art.rel",
+      dex2oat_orig_dir + "/boot.art.rel",
       base_addr_delta,
       &error_msg)) {
     FAIL() << "RelocateBootImage failed: " << error_msg;
@@ -582,8 +492,8 @@
   // Assert that patchoat created the same set of .art and .art.rel files
   std::vector<std::string> rel_basenames;
   std::vector<std::string> relocated_image_basenames;
-  if (!ListDirFilesEndingWith(rel_dir, "", &rel_basenames, &error_msg)) {
-    FAIL() << "Failed to list *.art.rel files in " << rel_dir << ": " << error_msg;
+  if (!ListDirFilesEndingWith(dex2oat_orig_dir, ".rel", &rel_basenames, &error_msg)) {
+    FAIL() << "Failed to list *.art.rel files in " << dex2oat_orig_dir << ": " << error_msg;
   }
   if (!ListDirFilesEndingWith(relocated_dir, ".art", &relocated_image_basenames, &error_msg)) {
     FAIL() << "Failed to list *.art files in " << relocated_dir << ": " << error_msg;
@@ -611,52 +521,19 @@
   }
   ASSERT_EQ(rel_shortened_basenames, relocated_image_shortened_basenames);
 
-  // For each image file, assert that unrelocating the image produces its original version
-  for (size_t i = 0; i < relocated_image_basenames.size(); i++) {
-    const std::string& original_image_filename =
-        dex2oat_orig_dir + "/" + relocated_image_shortened_basenames[i] + ".art";
-    const std::string& relocated_image_filename =
-        relocated_dir + "/" + relocated_image_basenames[i];
-    const std::string& rel_filename = rel_dir + "/" + rel_basenames[i];
-
-    // Assert that relocated image differs from the original
-    if (!BinaryDiff(original_image_filename, relocated_image_filename, &error_msg)) {
-      FAIL() << "Relocated image " << relocated_image_filename
-          << " identical to the original image " << original_image_filename;
-    }
-
-    // Assert that relocated image is identical to the original except for relocations described in
-    // the .rel file
-    if (!IsImageIdenticalToOriginalExceptForRelocation(
-        relocated_image_filename, original_image_filename, rel_filename, &error_msg)) {
-      FAIL() << "Unrelocating " << relocated_image_filename << " using " << rel_filename
-          << " did not produce the same output as " << original_image_filename << ": " << error_msg;
-    }
-
-    // Assert that the digest of original image in .rel file is as expected
-    std::vector<uint8_t> original;
-    if (!ReadFully(original_image_filename, &original, &error_msg)) {
-      FAIL() << "Failed to read original image " << original_image_filename;
-    }
-    std::vector<uint8_t> rel;
-    if (!ReadFully(rel_filename, &rel, &error_msg)) {
-      FAIL() << "Failed to read image relocation file " << rel_filename;
-    }
-    uint8_t original_image_digest[SHA256_DIGEST_LENGTH];
-    SHA256(original.data(), original.size(), original_image_digest);
-    const uint8_t* original_image_digest_in_rel_file = rel.data();
-    if (memcmp(original_image_digest_in_rel_file, original_image_digest, SHA256_DIGEST_LENGTH)) {
-      FAIL() << "Digest of original image in " << rel_filename << " does not match the original"
-          " image " << original_image_filename;
-    }
+  // Assert that verification works with the .rel files.
+  if (!VerifyBootImage(
+      dex2oat_orig_dir + "/boot.art",
+      relocated_dir + "/boot.art",
+      base_addr_delta,
+      &error_msg)) {
+    FAIL() << "VerifyBootImage failed: " << error_msg;
   }
 
   ClearDirectory(dex2oat_orig_dir.c_str(), /*recursive*/ true);
-  ClearDirectory(rel_dir.c_str(), /*recursive*/ true);
   ClearDirectory(relocated_dir.c_str(), /*recursive*/ true);
 
   rmdir(dex2oat_orig_dir.c_str());
-  rmdir(rel_dir.c_str());
   rmdir(relocated_dir.c_str());
 }