oatdump: Convert cdex before exporting dex files

Use DexLayout to convert the compact dex files inside a vdex container
back to a standard dex file before exporting. There is limited benefit
when exporting a cdex file since it is an art internal format that is
not supported from most dex parsers/tools. Backwards compatibility is
still maintained, although not really useful since the oat loader will
reject files of different versions.

Also since commit 734806, the VdexFile::UnquickenDexFile() is no longer
implementing apis unhiding. Therefore, the oatdump should manually
unhide them before exporting. Otherwise the verifier will complain
when reading the exported standard dex files.

This feature is mostly useful when inspecting oat files that have the
original bytecode being stripped from the matching archive (apk, jar).

Test: oatdump --oat-file=services.odex --export-dex-to=/tmp \
      --header-only && dexdump2 /tmp/services.jar_export.dex (manual)
Test: test-art-host-gtest

Change-Id: Ide79c5b778f1d6d6b4e58a95811e66cccb20735e
Signed-off-by: Anestis Bechtsoudis <anestis@census-labs.com>
diff --git a/libdexfile/dex/dex_file.cc b/libdexfile/dex/dex_file.cc
index a2198b7..a3e6221 100644
--- a/libdexfile/dex/dex_file.cc
+++ b/libdexfile/dex/dex_file.cc
@@ -60,6 +60,17 @@
   UpdateUnsignedLeb128(data_ptr, new_access_flags);
 }
 
+void DexFile::UnhideApis() const {
+  for (ClassAccessor accessor : GetClasses()) {
+    for (const ClassAccessor::Field& field : accessor.GetFields()) {
+      field.UnHideAccessFlags();
+    }
+    for (const ClassAccessor::Method& method : accessor.GetMethods()) {
+      method.UnHideAccessFlags();
+    }
+  }
+}
+
 uint32_t DexFile::CalculateChecksum() const {
   return CalculateChecksum(Begin(), Size());
 }
diff --git a/libdexfile/dex/dex_file.h b/libdexfile/dex/dex_file.h
index 98787d1..fc218fb 100644
--- a/libdexfile/dex/dex_file.h
+++ b/libdexfile/dex/dex_file.h
@@ -1013,6 +1013,9 @@
   // Changes the dex class data pointed to by data_ptr it to not have any hiddenapi flags.
   static void UnHideAccessFlags(uint8_t* data_ptr, uint32_t new_access_flags, bool is_method);
 
+  // Iterate dex classes and remove hiddenapi flags in fields and methods.
+  void UnhideApis() const;
+
   inline IterationRange<ClassIterator> GetClasses() const;
 
  protected:
diff --git a/oatdump/Android.bp b/oatdump/Android.bp
index 0704499..86f0ff8 100644
--- a/oatdump/Android.bp
+++ b/oatdump/Android.bp
@@ -37,6 +37,7 @@
     shared_libs: [
         "libart",
         "libart-compiler",
+        "libart-dexlayout",
         "libart-disassembler",
         "libdexfile",
         "libartbase",
@@ -54,6 +55,7 @@
     shared_libs: [
         "libartd",
         "libartd-compiler",
+        "libartd-dexlayout",
         "libartd-disassembler",
         "libdexfiled",
         "libartbased",
@@ -93,6 +95,7 @@
         "libdexfile_static_defaults",
         "libprofile_static_defaults",
         "libart-compiler_static_defaults",
+        "libart-dexlayout_static_defaults",
         "oatdumps-defaults",
     ],
     static_libs: [
@@ -111,6 +114,7 @@
         "libdexfiled_static_defaults",
         "libprofiled_static_defaults",
         "libartd-compiler_static_defaults",
+        "libartd-dexlayout_static_defaults",
         "oatdumps-defaults",
     ],
     target: {
diff --git a/oatdump/oatdump.cc b/oatdump/oatdump.cc
index ac9ece7..0e5504e 100644
--- a/oatdump/oatdump.cc
+++ b/oatdump/oatdump.cc
@@ -49,6 +49,7 @@
 #include "debug/debug_info.h"
 #include "debug/elf_debug_writer.h"
 #include "debug/method_debug_info.h"
+#include "dex/art_dex_file_loader.h"
 #include "dex/class_accessor-inl.h"
 #include "dex/code_item_accessors-inl.h"
 #include "dex/descriptors_names.h"
@@ -56,6 +57,7 @@
 #include "dex/dex_instruction-inl.h"
 #include "dex/string_reference.h"
 #include "dex/type_lookup_table.h"
+#include "dexlayout.h"
 #include "disassembler.h"
 #include "gc/accounting/space_bitmap-inl.h"
 #include "gc/space/image_space.h"
@@ -625,8 +627,64 @@
         const OatDexFile* oat_dex_file = oat_dex_files_[i];
         CHECK(oat_dex_file != nullptr);
         CHECK(vdex_dex_file != nullptr);
-        if (!ExportDexFile(os, *oat_dex_file, vdex_dex_file.get())) {
-          success = false;
+
+        // Remove hiddenapis
+        vdex_dex_file->UnhideApis();
+
+        // If a CompactDex file is detected within a Vdex container, DexLayout is used to convert
+        // back to a StandardDex file. Since the converted DexFile will most likely not reproduce
+        // the original input Dex file, the `update_checksum_` option is used to recompute the
+        // checksum. If the vdex container does not contain cdex resources (`used_dexlayout` is
+        // false), ExportDexFile() enforces a reproducible checksum verification.
+        if (vdex_dex_file->IsCompactDexFile()) {
+          Options options;
+          options.compact_dex_level_ = CompactDexLevel::kCompactDexLevelNone;
+          options.update_checksum_ = true;
+          DexLayout dex_layout(options, /*info*/ nullptr, /*out_file*/ nullptr, /*header*/ nullptr);
+          std::unique_ptr<art::DexContainer> dex_container;
+          bool result = dex_layout.ProcessDexFile(vdex_dex_file->GetLocation().c_str(),
+                                                  vdex_dex_file.get(),
+                                                  i,
+                                                  &dex_container,
+                                                  &error_msg);
+          if (!result) {
+            os << "DexLayout failed to process Dex file: " + error_msg;
+            success = false;
+            break;
+          }
+          DexContainer::Section* main_section = dex_container->GetMainSection();
+          CHECK_EQ(dex_container->GetDataSection()->Size(), 0u);
+
+          const ArtDexFileLoader dex_file_loader;
+          std::unique_ptr<const DexFile> dex(dex_file_loader.Open(
+              main_section->Begin(),
+              main_section->Size(),
+              vdex_dex_file->GetLocation(),
+              vdex_file->GetLocationChecksum(i),
+              nullptr /*oat_dex_file*/,
+              false /*verify*/,
+              true /*verify_checksum*/,
+              &error_msg));
+          if (dex == nullptr) {
+            os << "Failed to load DexFile from layout container: " + error_msg;
+            success = false;
+            break;
+          }
+          if (dex->IsCompactDexFile()) {
+            os <<"CompactDex conversion to StandardDex failed";
+            success = false;
+            break;
+          }
+
+          if (!ExportDexFile(os, *oat_dex_file, dex.get(), true /*used_dexlayout*/)) {
+            success = false;
+            break;
+          }
+        } else {
+          if (!ExportDexFile(os, *oat_dex_file, vdex_dex_file.get(), false /*used_dexlayout*/)) {
+            success = false;
+            break;
+          }
         }
         i++;
       }
@@ -898,10 +956,16 @@
   // Dex resource is extracted from the oat_dex_file and its checksum is repaired since it's not
   // unquickened. Otherwise the dex_file has been fully unquickened and is expected to verify the
   // original checksum.
-  bool ExportDexFile(std::ostream& os, const OatDexFile& oat_dex_file, const DexFile* dex_file) {
+  bool ExportDexFile(std::ostream& os,
+                     const OatDexFile& oat_dex_file,
+                     const DexFile* dex_file,
+                     bool used_dexlayout) {
     std::string error_msg;
     std::string dex_file_location = oat_dex_file.GetDexFileLocation();
-    size_t fsize = oat_dex_file.FileSize();
+
+    // If dex_file (from unquicken or dexlayout) is not available, the output DexFile size is the
+    // same as the one extracted from the Oat container (pre-oreo)
+    size_t fsize = dex_file == nullptr ? oat_dex_file.FileSize() : dex_file->Size();
 
     // Some quick checks just in case
     if (fsize == 0 || fsize < sizeof(DexFile::Header)) {
@@ -921,27 +985,19 @@
       reinterpret_cast<DexFile::Header*>(const_cast<uint8_t*>(dex_file->Begin()))->checksum_ =
           dex_file->CalculateChecksum();
     } else {
-      // Vdex unquicken output should match original input bytecode
-      uint32_t orig_checksum =
-          reinterpret_cast<DexFile::Header*>(const_cast<uint8_t*>(dex_file->Begin()))->checksum_;
-      CHECK_EQ(orig_checksum, dex_file->CalculateChecksum());
-      if (orig_checksum != dex_file->CalculateChecksum()) {
-        os << "Unexpected checksum from unquicken dex file '" << dex_file_location << "'\n";
-        return false;
+      // If dexlayout was used to convert CompactDex back to StandardDex, checksum will be updated
+      // due to `update_checksum_` option, otherwise we expect a reproducible checksum.
+      if (!used_dexlayout) {
+        // Vdex unquicken output should match original input bytecode
+        uint32_t orig_checksum =
+            reinterpret_cast<DexFile::Header*>(const_cast<uint8_t*>(dex_file->Begin()))->checksum_;
+        if (orig_checksum != dex_file->CalculateChecksum()) {
+          os << "Unexpected checksum from unquicken dex file '" << dex_file_location << "'\n";
+          return false;
+        }
       }
     }
 
-    // Update header for shared section.
-    uint32_t shared_section_offset = 0u;
-    uint32_t shared_section_size = 0u;
-    if (dex_file->IsCompactDexFile()) {
-      CompactDexFile::Header* const header =
-          reinterpret_cast<CompactDexFile::Header*>(const_cast<uint8_t*>(dex_file->Begin()));
-      shared_section_offset = header->data_off_;
-      shared_section_size = header->data_size_;
-      // The shared section will be serialized right after the dex file.
-      header->data_off_ = header->file_size_;
-    }
     // Verify output directory exists
     if (!OS::DirectoryExists(options_.export_dex_location_)) {
       // TODO: Extend OS::DirectoryExists if symlink support is required
@@ -995,15 +1051,6 @@
       return false;
     }
 
-    if (shared_section_size != 0) {
-      success = file->WriteFully(dex_file->Begin() + shared_section_offset, shared_section_size);
-      if (!success) {
-        os << "Failed to write shared data section";
-        file->Erase();
-        return false;
-      }
-    }
-
     if (file->FlushCloseOrErase() != 0) {
       os << "Flush and close failed";
       return false;