Reland: Enable patchoat to write image relocation files

This adds an off by default feature to patchoat whereby it can write
image relocation information (i.e., which offsets are patched up by
patchoat) to .rel files. .rel file writing is enabled by specifying
the name of boot.art.rel file using command-line parameter
--output-image-relocation-file=...

The currently intended use case is to make the Android build process
store these files on the system image next to boot*.art files. At boot
time, in follow-up commits, these .rel files will then be used to
verify that all differences between /system boot*.art and
/data/dalvik-cache boot*.art files can be explained by relocation. The
goal is to mitigate /data/dalvik-cache boot*.art being a persistence
vector.

Test: ./art/test/testrunner/run_build_test_target.py art-gtest-debug-gc
Test: make test-art-host-gtest-patchoat_test
Test: ART_HEAP_POISONING=true make test-art-host-gtest-patchoat_test
Test: make test-art-target-gtest-patchoat_test
Test: ANDROID_ROOT=out/target/product/sailfish/system \
        ANDROID_DATA=out/target/product/sailfish/dex_bootjars/system/framework/arm64/ \
        out/host/linux-x86/bin/patchoat \
        --input-image-location=<full path to>/out/target/product/sailfish/dex_bootjars/system/framework/boot.art \
        --output-image-file=out/target/product/sailfish/dex_bootjars/system/framework/arm64/boot.art \
        --instruction-set=arm64 --base-offset-delta=0x10000000
        produces same boot*.art files as prior to this change
Test: ANDROID_ROOT=out/target/product/sailfish/system \
        ANDROID_DATA=out/target/product/sailfish/dex_bootjars/system/framework/arm64/ \
        out/host/linux-x86/bin/patchoat \
        --input-image-location=<full path to>/out/target/product/sailfish/dex_bootjars/system/framework/boot.art \
        --output-image-relocation-file=out/target/product/sailfish/dex_bootjars/system/framework/arm64/boot.art.rel \
        --instruction-set=arm64 --base-offset-delta=0x10000000
        produces no boot*.art files, but produces expected boot.art.rel files
Bug: 66697305

Change-Id: Ia6b548c61429c61a62706d4021f8e6f22c49082e
diff --git a/patchoat/patchoat.cc b/patchoat/patchoat.cc
index eb648cb..6c9cf86 100644
--- a/patchoat/patchoat.cc
+++ b/patchoat/patchoat.cc
@@ -15,6 +15,7 @@
  */
 #include "patchoat.h"
 
+#include <openssl/sha.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <sys/file.h>
@@ -42,6 +43,7 @@
 #include "gc/space/image_space.h"
 #include "image-inl.h"
 #include "intern_table.h"
+#include "leb128.h"
 #include "mirror/dex_cache.h"
 #include "mirror/executable.h"
 #include "mirror/method.h"
@@ -58,6 +60,8 @@
 
 namespace art {
 
+using android::base::StringPrintf;
+
 static const OatHeader* GetOatHeader(const ElfFile* elf_file) {
   uint64_t off = 0;
   if (!elf_file->GetSectionOffsetAndSize(".rodata", &off, nullptr)) {
@@ -120,11 +124,134 @@
   return true;
 }
 
+bool PatchOat::GeneratePatch(
+    const MemMap& original,
+    const MemMap& relocated,
+    std::vector<uint8_t>* output,
+    std::string* error_msg) {
+  // FORMAT of the patch (aka image relocation) file:
+  // * SHA-256 digest (32 bytes) of original/unrelocated file (e.g., the one from /system)
+  // * List of monotonically increasing offsets (max value defined by uint32_t) at which relocations
+  //   occur.
+  //   Each element is represented as the delta from the previous offset in the list (first element
+  //   is a delta from 0). Each delta is encoded using unsigned LEB128: little-endian
+  //   variable-length 7 bits per byte encoding, where all bytes have the highest bit (0x80) set
+  //   except for the final byte which does not have that bit set. For example, 0x3f is offset 0x3f,
+  //   whereas 0xbf 0x05 is offset (0x3f & 0x7f) | (0x5 << 7) which is 0x2bf. Most deltas end up
+  //   being encoding using just one byte, achieving ~4x decrease in relocation file size compared
+  //   to the encoding where offsets are stored verbatim, as uint32_t.
+
+  size_t original_size = original.Size();
+  size_t relocated_size = relocated.Size();
+  if (original_size != relocated_size) {
+    *error_msg =
+        StringPrintf(
+            "Original and relocated image sizes differ: %zu vs %zu", original_size, relocated_size);
+    return false;
+  }
+  if ((original_size % 4) != 0) {
+    *error_msg = StringPrintf("Image size not multiple of 4: %zu", original_size);
+    return false;
+  }
+  if (original_size > UINT32_MAX) {
+    *error_msg = StringPrintf("Image too large: %zu" , original_size);
+    return false;
+  }
+
+  const ImageHeader& relocated_header =
+      *reinterpret_cast<const ImageHeader*>(relocated.Begin());
+  // Offsets are supposed to differ between original and relocated by this value
+  off_t expected_diff = relocated_header.GetPatchDelta();
+  if (expected_diff == 0) {
+    // Can't identify offsets which are supposed to differ due to relocation
+    *error_msg = "Relocation delta is 0";
+    return false;
+  }
+
+  // Output the SHA-256 digest of the original
+  output->resize(SHA256_DIGEST_LENGTH);
+  const uint8_t* original_bytes = original.Begin();
+  SHA256(original_bytes, original_size, output->data());
+
+  // Output the list of offsets at which the original and patched images differ
+  size_t last_diff_offset = 0;
+  size_t diff_offset_count = 0;
+  const uint8_t* relocated_bytes = relocated.Begin();
+  for (size_t offset = 0; offset < original_size; offset += 4) {
+    uint32_t original_value = *reinterpret_cast<const uint32_t*>(original_bytes + offset);
+    uint32_t relocated_value = *reinterpret_cast<const uint32_t*>(relocated_bytes + offset);
+    off_t diff = relocated_value - original_value;
+    if (diff == 0) {
+      continue;
+    } else if (diff != expected_diff) {
+      *error_msg =
+          StringPrintf(
+              "Unexpected diff at offset %zu. Expected: %jd, but was: %jd",
+              offset,
+              (intmax_t) expected_diff,
+              (intmax_t) diff);
+      return false;
+    }
+
+    uint32_t offset_diff = offset - last_diff_offset;
+    last_diff_offset = offset;
+    diff_offset_count++;
+
+    EncodeUnsignedLeb128(output, offset_diff);
+  }
+
+  if (diff_offset_count == 0) {
+    *error_msg = "Original and patched images are identical";
+    return false;
+  }
+
+  return true;
+}
+
+static bool WriteRelFile(
+    const MemMap& original,
+    const MemMap& relocated,
+    const std::string& rel_filename,
+    std::string* error_msg) {
+  std::vector<uint8_t> output;
+  if (!PatchOat::GeneratePatch(original, relocated, &output, error_msg)) {
+    return false;
+  }
+
+  std::unique_ptr<File> rel_file(OS::CreateEmptyFileWriteOnly(rel_filename.c_str()));
+  if (rel_file.get() == nullptr) {
+    *error_msg = StringPrintf("Failed to create/open output file %s", rel_filename.c_str());
+    return false;
+  }
+  if (!rel_file->WriteFully(output.data(), output.size())) {
+    *error_msg = StringPrintf("Failed to write to %s", rel_filename.c_str());
+    return false;
+  }
+  if (rel_file->FlushCloseOrErase() != 0) {
+    *error_msg = StringPrintf("Failed to flush and close %s", rel_filename.c_str());
+    return false;
+  }
+
+  return true;
+}
+
 bool PatchOat::Patch(const std::string& image_location,
                      off_t delta,
-                     const std::string& output_directory,
+                     const std::string& output_image_directory,
+                     const std::string& output_image_relocation_directory,
                      InstructionSet isa,
                      TimingLogger* timings) {
+  bool output_image = !output_image_directory.empty();
+  bool output_image_relocation = !output_image_relocation_directory.empty();
+  if ((!output_image) && (!output_image_relocation)) {
+    // Nothing to do
+    return true;
+  }
+  if ((output_image_relocation) && (delta == 0)) {
+    LOG(ERROR) << "Cannot output image relocation information when requested relocation delta is 0";
+    return false;
+  }
+
   CHECK(Runtime::Current() == nullptr);
   CHECK(!image_location.empty()) << "image file must have a filename.";
 
@@ -221,32 +348,35 @@
       return false;
     }
 
-    MaybePic is_oat_pic = IsOatPic(elf.get());
-    if (is_oat_pic >= ERROR_FIRST) {
-      // Error logged by IsOatPic
-      return false;
-    } else if (is_oat_pic == NOT_PIC) {
-      LOG(ERROR) << "patchoat cannot be used on non-PIC oat file: " << input_oat_file->GetPath();
-      return false;
-    } else {
-      CHECK(is_oat_pic == PIC);
-
-      // Create a symlink.
-      std::string converted_image_filename = space->GetImageLocation();
-      std::replace(converted_image_filename.begin() + 1, converted_image_filename.end(), '/', '@');
-      std::string output_image_filename = output_directory +
-          (android::base::StartsWith(converted_image_filename, "/") ? "" : "/") +
-          converted_image_filename;
-      std::string output_vdex_filename =
-          ImageHeader::GetVdexLocationFromImageLocation(output_image_filename);
-      std::string output_oat_filename =
-          ImageHeader::GetOatLocationFromImageLocation(output_image_filename);
-
-      if (!ReplaceOatFileWithSymlink(input_oat_file->GetPath(),
-                                     output_oat_filename) ||
-          !SymlinkFile(input_vdex_filename, output_vdex_filename)) {
-        // Errors already logged by above call.
+    if (output_image) {
+      MaybePic is_oat_pic = IsOatPic(elf.get());
+      if (is_oat_pic >= ERROR_FIRST) {
+        // Error logged by IsOatPic
         return false;
+      } else if (is_oat_pic == NOT_PIC) {
+        LOG(ERROR) << "patchoat cannot be used on non-PIC oat file: " << input_oat_file->GetPath();
+        return false;
+      } else {
+        CHECK(is_oat_pic == PIC);
+
+        // Create a symlink.
+        std::string converted_image_filename = space->GetImageLocation();
+        std::replace(
+            converted_image_filename.begin() + 1, converted_image_filename.end(), '/', '@');
+        std::string output_image_filename = output_image_directory +
+            (android::base::StartsWith(converted_image_filename, "/") ? "" : "/") +
+            converted_image_filename;
+        std::string output_vdex_filename =
+            ImageHeader::GetVdexLocationFromImageLocation(output_image_filename);
+        std::string output_oat_filename =
+            ImageHeader::GetOatLocationFromImageLocation(output_image_filename);
+
+        if (!ReplaceOatFileWithSymlink(input_oat_file->GetPath(),
+                                       output_oat_filename) ||
+            !SymlinkFile(input_vdex_filename, output_vdex_filename)) {
+          // Errors already logged by above call.
+          return false;
+        }
       }
     }
 
@@ -267,28 +397,72 @@
     }
   }
 
-  // Write the patched image spaces.
-  for (size_t i = 0; i < spaces.size(); ++i) {
-    gc::space::ImageSpace* space = spaces[i];
+  if (output_image) {
+    // Write the patched image spaces.
+    for (size_t i = 0; i < spaces.size(); ++i) {
+      gc::space::ImageSpace* space = spaces[i];
 
-    t.NewTiming("Writing image");
-    std::string converted_image_filename = space->GetImageLocation();
-    std::replace(converted_image_filename.begin() + 1, converted_image_filename.end(), '/', '@');
-    std::string output_image_filename = output_directory +
-        (android::base::StartsWith(converted_image_filename, "/") ? "" : "/") +
-        converted_image_filename;
-    std::unique_ptr<File> output_image_file(CreateOrOpen(output_image_filename.c_str()));
-    if (output_image_file.get() == nullptr) {
-      LOG(ERROR) << "Failed to open output image file at " << output_image_filename;
-      return false;
+      t.NewTiming("Writing image");
+      std::string converted_image_filename = space->GetImageLocation();
+      std::replace(converted_image_filename.begin() + 1, converted_image_filename.end(), '/', '@');
+      std::string output_image_filename = output_image_directory +
+          (android::base::StartsWith(converted_image_filename, "/") ? "" : "/") +
+          converted_image_filename;
+      std::unique_ptr<File> output_image_file(CreateOrOpen(output_image_filename.c_str()));
+      if (output_image_file.get() == nullptr) {
+        LOG(ERROR) << "Failed to open output image file at " << output_image_filename;
+        return false;
+      }
+
+      PatchOat& p = space_to_patchoat_map.find(space)->second;
+
+      bool success = p.WriteImage(output_image_file.get());
+      success = FinishFile(output_image_file.get(), success);
+      if (!success) {
+        return false;
+      }
     }
+  }
 
-    PatchOat& p = space_to_patchoat_map.find(space)->second;
+  if (output_image_relocation) {
+    // Write the image relocation information for each space.
+    for (size_t i = 0; i < spaces.size(); ++i) {
+      gc::space::ImageSpace* space = spaces[i];
 
-    bool success = p.WriteImage(output_image_file.get());
-    success = FinishFile(output_image_file.get(), success);
-    if (!success) {
-      return false;
+      t.NewTiming("Writing image relocation");
+      std::string original_image_filename(space->GetImageLocation() + ".rel");
+      std::string image_relocation_filename =
+          output_image_relocation_directory
+              + (android::base::StartsWith(original_image_filename, "/") ? "" : "/")
+              + original_image_filename.substr(original_image_filename.find_last_of("/"));
+      File& input_image = *space_to_file_map.find(space)->second;
+      int64_t input_image_size = input_image.GetLength();
+      if (input_image_size < 0) {
+        LOG(ERROR) << "Error while getting input image size";
+        return false;
+      }
+      std::string error_msg;
+      std::unique_ptr<MemMap> original(MemMap::MapFile(input_image_size,
+                                                       PROT_READ,
+                                                       MAP_PRIVATE,
+                                                       input_image.Fd(),
+                                                       0,
+                                                       /*low_4gb*/false,
+                                                       input_image.GetPath().c_str(),
+                                                       &error_msg));
+      if (original.get() == nullptr) {
+        LOG(ERROR) << "Unable to map image file " << input_image.GetPath() << " : " << error_msg;
+        return false;
+      }
+
+      PatchOat& p = space_to_patchoat_map.find(space)->second;
+      const MemMap* relocated = p.image_;
+
+      if (!WriteRelFile(*original, *relocated, image_relocation_filename, &error_msg)) {
+        LOG(ERROR) << "Failed to create image relocation file " << image_relocation_filename
+            << ": " << error_msg;
+        return false;
+      }
     }
   }
 
@@ -739,6 +913,9 @@
   UsageError("  --output-image-file=<file.art>: Specifies the exact file to write the patched");
   UsageError("      image file to.");
   UsageError("");
+  UsageError("  --output-image-relocation-file=<file.art.rel>: Specifies the exact file to write");
+  UsageError("      the image relocation information to.");
+  UsageError("");
   UsageError("  --base-offset-delta=<delta>: Specify the amount to change the old base-offset by.");
   UsageError("      This value may be negative.");
   UsageError("");
@@ -754,12 +931,13 @@
                           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) {
   CHECK(!input_image_location.empty());
-  if (output_image_filename.empty()) {
-    Usage("Image patching requires --output-image-file");
+  if ((output_image_filename.empty()) && (output_image_relocation_filename.empty())) {
+    Usage("Image patching requires --output-image-file or --output-image-relocation-file");
   }
 
   if (!base_delta_set) {
@@ -778,9 +956,19 @@
 
   TimingLogger::ScopedTiming pt("patch image and oat", &timings);
 
-  std::string output_directory =
+  std::string output_image_directory =
       output_image_filename.substr(0, output_image_filename.find_last_of('/'));
-  bool ret = PatchOat::Patch(input_image_location, base_delta, output_directory, isa, &timings);
+  std::string output_image_relocation_directory =
+      output_image_relocation_filename.substr(
+          0, output_image_relocation_filename.find_last_of('/'));
+  bool ret =
+      PatchOat::Patch(
+          input_image_location,
+          base_delta,
+          output_image_directory,
+          output_image_relocation_directory,
+          isa,
+          &timings);
 
   if (kIsDebugBuild) {
     LOG(INFO) << "Exiting with return ... " << ret;
@@ -811,6 +999,7 @@
   InstructionSet isa = InstructionSet::kNone;
   std::string input_image_location;
   std::string output_image_filename;
+  std::string output_image_relocation_filename;
   off_t base_delta = 0;
   bool base_delta_set = false;
   bool dump_timings = kIsDebugBuild;
@@ -832,6 +1021,9 @@
       input_image_location = option.substr(strlen("--input-image-location=")).data();
     } else if (option.starts_with("--output-image-file=")) {
       output_image_filename = option.substr(strlen("--output-image-file=")).data();
+    } else if (option.starts_with("--output-image-relocation-file=")) {
+      output_image_relocation_filename =
+          option.substr(strlen("--output-image-relocation-file=")).data();
     } else if (option.starts_with("--base-offset-delta=")) {
       const char* base_delta_str = option.substr(strlen("--base-offset-delta=")).data();
       base_delta_set = true;
@@ -856,6 +1048,7 @@
                            isa,
                            input_image_location,
                            output_image_filename,
+                           output_image_relocation_filename,
                            base_delta,
                            base_delta_set,
                            debug);