ART: oatdump enhancements

New features list includes:
- Class filter option to limit classes search space
- Method filter is applied only against the method
  name, instead of the entire signature. Can be
  combined with class filter for maximum efficiency.
- Bulk dump of class and method names list only.
  Can be combined with filters to limit results.
- Export embedded dex files from input oat files
  to filesystem (symlinks not supported as utils
  functions are utilized for os & fs operations).
- addr2instr option to locate the in-range method
  implementation and limit disassemble dumps. Input
  relative addr is added to oat executable offset to
  calculate the search offset. If method has been
  successfully located, code is dumped and program
  aborts further analysis of the input file. Methods
  located before the target address just print their
  signature, although skip all disassemble and other
  info. Calculated search offset is also printed as
  part of the initial header info.
- Little-endian dex instructions bytecode is printed
  in the same line before the instruction string.

Some minor re-orders have been also taken place for
more targeted results.

Change-Id: I3116ee3c99c258718f46faea8ea4295da6ae2bf7
Signed-off-by: Anestis Bechtsoudis <anestis@census-labs.com>
diff --git a/oatdump/oatdump.cc b/oatdump/oatdump.cc
index 11ccafb..3ce86d8 100644
--- a/oatdump/oatdump.cc
+++ b/oatdump/oatdump.cc
@@ -311,13 +311,23 @@
                    bool dump_vmap,
                    bool disassemble_code,
                    bool absolute_addresses,
-                   const char* method_filter)
+                   const char* class_filter,
+                   const char* method_filter,
+                   bool list_classes,
+                   bool list_methods,
+                   const char* export_dex_location,
+                   uint32_t addr2instr)
     : dump_raw_mapping_table_(dump_raw_mapping_table),
       dump_raw_gc_map_(dump_raw_gc_map),
       dump_vmap_(dump_vmap),
       disassemble_code_(disassemble_code),
       absolute_addresses_(absolute_addresses),
+      class_filter_(class_filter),
       method_filter_(method_filter),
+      list_classes_(list_classes),
+      list_methods_(list_methods),
+      export_dex_location_(export_dex_location),
+      addr2instr_(addr2instr),
       class_loader_(nullptr) {}
 
   const bool dump_raw_mapping_table_;
@@ -325,27 +335,34 @@
   const bool dump_vmap_;
   const bool disassemble_code_;
   const bool absolute_addresses_;
+  const char* const class_filter_;
   const char* const method_filter_;
+  const bool list_classes_;
+  const bool list_methods_;
+  const char* const export_dex_location_;
+  uint32_t addr2instr_;
   Handle<mirror::ClassLoader>* class_loader_;
 };
 
 class OatDumper {
  public:
-  explicit OatDumper(const OatFile& oat_file, OatDumperOptions* options)
+  explicit OatDumper(const OatFile& oat_file, const OatDumperOptions& options)
     : oat_file_(oat_file),
       oat_dex_files_(oat_file.GetOatDexFiles()),
       options_(options),
+      resolved_addr2instr_(0),
       instruction_set_(oat_file_.GetOatHeader().GetInstructionSet()),
       disassembler_(Disassembler::Create(instruction_set_,
-                                         new DisassemblerOptions(options_->absolute_addresses_,
+                                         new DisassemblerOptions(options_.absolute_addresses_,
                                                                  oat_file.Begin(),
                                                                  true /* can_read_litals_ */))) {
-    CHECK(options_->class_loader_ != nullptr);
+    CHECK(options_.class_loader_ != nullptr);
+    CHECK(options_.class_filter_ != nullptr);
+    CHECK(options_.method_filter_ != nullptr);
     AddAllOffsets();
   }
 
   ~OatDumper() {
-    delete options_;
     delete disassembler_;
   }
 
@@ -380,7 +397,7 @@
 #define DUMP_OAT_HEADER_OFFSET(label, offset) \
     os << label " OFFSET:\n"; \
     os << StringPrintf("0x%08x", oat_header.offset()); \
-    if (oat_header.offset() != 0 && options_->absolute_addresses_) { \
+    if (oat_header.offset() != 0 && options_.absolute_addresses_) { \
       os << StringPrintf(" (%p)", oat_file_.Begin() + oat_header.offset()); \
     } \
     os << StringPrintf("\n\n");
@@ -426,7 +443,7 @@
       os << "\n";
     }
 
-    if (options_->absolute_addresses_) {
+    if (options_.absolute_addresses_) {
       os << "BEGIN:\n";
       os << reinterpret_cast<const void*>(oat_file_.Begin()) << "\n\n";
 
@@ -439,11 +456,26 @@
 
     os << std::flush;
 
+    // If set, adjust relative address to be searched
+    if (options_.addr2instr_ != 0) {
+      resolved_addr2instr_ = options_.addr2instr_ + oat_header.GetExecutableOffset();
+      os << "SEARCH ADDRESS (executable offset + input):\n";
+      os << StringPrintf("0x%08x\n\n", resolved_addr2instr_);
+    }
+
     for (size_t i = 0; i < oat_dex_files_.size(); i++) {
       const OatFile::OatDexFile* oat_dex_file = oat_dex_files_[i];
       CHECK(oat_dex_file != nullptr);
-      if (!DumpOatDexFile(os, *oat_dex_file)) {
-        success = false;
+
+      // If file export selected skip file analysis
+      if (options_.export_dex_location_) {
+        if (!ExportDexFile(os, *oat_dex_file)) {
+          success = false;
+        }
+      } else {
+        if (!DumpOatDexFile(os, *oat_dex_file)) {
+          success = false;
+        }
       }
     }
     os << std::flush;
@@ -553,6 +585,7 @@
 
   bool DumpOatDexFile(std::ostream& os, const OatFile::OatDexFile& oat_dex_file) {
     bool success = true;
+    bool stop_analysis = false;
     os << "OatDexFile:\n";
     os << StringPrintf("location: %s\n", oat_dex_file.GetDexFileLocation().c_str());
     os << StringPrintf("checksum: 0x%08x\n", oat_dex_file.GetDexFileLocationChecksum());
@@ -571,6 +604,12 @@
          class_def_index++) {
       const DexFile::ClassDef& class_def = dex_file->GetClassDef(class_def_index);
       const char* descriptor = dex_file->GetClassDescriptor(class_def);
+
+      // TODO: Support regex
+      if (DescriptorToDot(descriptor).find(options_.class_filter_) == std::string::npos) {
+        continue;
+      }
+
       uint32_t oat_class_offset = oat_dex_file.GetOatClassOffset(class_def_index);
       const OatFile::OatClass oat_class = oat_dex_file.GetOatClass(class_def_index);
       os << StringPrintf("%zd: %s (offset=0x%08x) (type_idx=%d)",
@@ -580,15 +619,98 @@
       // TODO: include bitmap here if type is kOatClassSomeCompiled?
       Indenter indent_filter(os.rdbuf(), kIndentChar, kIndentBy1Count);
       std::ostream indented_os(&indent_filter);
-      if (!DumpOatClass(indented_os, oat_class, *(dex_file.get()), class_def)) {
+      if (options_.list_classes_) continue;
+      if (!DumpOatClass(indented_os, oat_class, *(dex_file.get()), class_def, &stop_analysis)) {
         success = false;
       }
+      if (stop_analysis) {
+        os << std::flush;
+        return success;
+      }
     }
 
     os << std::flush;
     return success;
   }
 
+  bool ExportDexFile(std::ostream& os, const OatFile::OatDexFile& oat_dex_file) {
+    std::string error_msg;
+    std::string dex_file_location = oat_dex_file.GetDexFileLocation();
+
+    std::unique_ptr<const DexFile> dex_file(oat_dex_file.OpenDexFile(&error_msg));
+    if (dex_file == nullptr) {
+      os << "Failed to open dex file '" << dex_file_location << "': " << error_msg;
+      return false;
+    }
+    size_t fsize = oat_dex_file.FileSize();
+
+    // Some quick checks just in case
+    if (fsize == 0 || fsize < sizeof(DexFile::Header)) {
+      os << "Invalid dex file\n";
+      return false;
+    }
+
+    // Verify output directory exists
+    if (!OS::DirectoryExists(options_.export_dex_location_)) {
+      // TODO: Extend OS::DirectoryExists if symlink support is required
+      os << options_.export_dex_location_ << " output directory not found or symlink\n";
+      return false;
+    }
+
+    // Beautify path names
+    if (dex_file_location.size() > PATH_MAX || dex_file_location.size() <= 0) {
+      return false;
+    }
+
+    std::string dex_orig_name;
+    size_t dex_orig_pos = dex_file_location.rfind('/');
+    if (dex_orig_pos == std::string::npos)
+      dex_orig_name = dex_file_location;
+    else
+      dex_orig_name = dex_file_location.substr(dex_orig_pos + 1);
+
+    // A more elegant approach to efficiently name user installed apps is welcome
+    if (dex_orig_name.size() == 8 && !dex_orig_name.compare("base.apk")) {
+      dex_file_location.erase(dex_orig_pos, strlen("base.apk") + 1);
+      size_t apk_orig_pos = dex_file_location.rfind('/');
+      if (apk_orig_pos != std::string::npos) {
+        dex_orig_name = dex_file_location.substr(++apk_orig_pos);
+      }
+    }
+
+    std::string out_dex_path(options_.export_dex_location_);
+    if (out_dex_path.back() != '/') {
+      out_dex_path.append("/");
+    }
+    out_dex_path.append(dex_orig_name);
+    out_dex_path.append("_export.dex");
+    if (out_dex_path.length() > PATH_MAX) {
+      return false;
+    }
+
+    std::unique_ptr<File> file(OS::CreateEmptyFile(out_dex_path.c_str()));
+    if (file.get() == nullptr) {
+      os << "Failed to open output dex file " << out_dex_path;
+      return false;
+    }
+
+    if (!file->WriteFully(dex_file->Begin(), fsize)) {
+      os << "Failed to write dex file";
+      file->Erase();
+      return false;
+    }
+
+    if (file->FlushCloseOrErase() != 0) {
+      os << "Flush and close failed";
+      return false;
+    }
+
+    os << StringPrintf("Dex file exported at %s (%zd bytes)\n", out_dex_path.c_str(), fsize);
+    os << std::flush;
+
+    return true;
+  }
+
   static void SkipAllFields(ClassDataItemIterator& it) {
     while (it.HasNextStaticField()) {
       it.Next();
@@ -599,8 +721,9 @@
   }
 
   bool DumpOatClass(std::ostream& os, const OatFile::OatClass& oat_class, const DexFile& dex_file,
-                    const DexFile::ClassDef& class_def) {
+                    const DexFile::ClassDef& class_def, bool* stop_analysis) {
     bool success = true;
+    bool addr_found = false;
     const uint8_t* class_data = dex_file.GetClassData(class_def);
     if (class_data == nullptr) {  // empty class such as a marker interface?
       os << std::flush;
@@ -612,18 +735,26 @@
     while (it.HasNextDirectMethod()) {
       if (!DumpOatMethod(os, class_def, class_method_index, oat_class, dex_file,
                          it.GetMemberIndex(), it.GetMethodCodeItem(),
-                         it.GetRawMemberAccessFlags())) {
+                         it.GetRawMemberAccessFlags(), &addr_found)) {
         success = false;
       }
+      if (addr_found) {
+        *stop_analysis = true;
+        return success;
+      }
       class_method_index++;
       it.Next();
     }
     while (it.HasNextVirtualMethod()) {
       if (!DumpOatMethod(os, class_def, class_method_index, oat_class, dex_file,
                          it.GetMemberIndex(), it.GetMethodCodeItem(),
-                         it.GetRawMemberAccessFlags())) {
+                         it.GetRawMemberAccessFlags(), &addr_found)) {
         success = false;
       }
+      if (addr_found) {
+        *stop_analysis = true;
+        return success;
+      }
       class_method_index++;
       it.Next();
     }
@@ -641,20 +772,39 @@
                      uint32_t class_method_index,
                      const OatFile::OatClass& oat_class, const DexFile& dex_file,
                      uint32_t dex_method_idx, const DexFile::CodeItem* code_item,
-                     uint32_t method_access_flags) {
+                     uint32_t method_access_flags, bool* addr_found) {
     bool success = true;
-    std::string pretty_method = PrettyMethod(dex_method_idx, dex_file, true);
-    if (pretty_method.find(options_->method_filter_) == std::string::npos) {
+
+    // TODO: Support regex
+    std::string method_name = dex_file.GetMethodName(dex_file.GetMethodId(dex_method_idx));
+    if (method_name.find(options_.method_filter_) == std::string::npos) {
       return success;
     }
 
+    std::string pretty_method = PrettyMethod(dex_method_idx, dex_file, true);
     os << StringPrintf("%d: %s (dex_method_idx=%d)\n",
                        class_method_index, pretty_method.c_str(),
                        dex_method_idx);
+    if (options_.list_methods_) return success;
+
     Indenter indent1_filter(os.rdbuf(), kIndentChar, kIndentBy1Count);
     std::unique_ptr<std::ostream> indent1_os(new std::ostream(&indent1_filter));
     Indenter indent2_filter(indent1_os->rdbuf(), kIndentChar, kIndentBy1Count);
     std::unique_ptr<std::ostream> indent2_os(new std::ostream(&indent2_filter));
+
+    uint32_t oat_method_offsets_offset = oat_class.GetOatMethodOffsetsOffset(class_method_index);
+    const OatMethodOffsets* oat_method_offsets = oat_class.GetOatMethodOffsets(class_method_index);
+    const OatFile::OatMethod oat_method = oat_class.GetOatMethod(class_method_index);
+    uint32_t code_offset = oat_method.GetCodeOffset();
+    uint32_t code_size = oat_method.GetQuickCodeSize();
+    if (resolved_addr2instr_ != 0) {
+      if (resolved_addr2instr_ > code_offset + code_size) {
+        return success;
+      } else {
+        *addr_found = true;  // stop analyzing file at next iteration
+      }
+    }
+
     {
       *indent1_os << "DEX CODE:\n";
       DumpDexCode(*indent2_os, dex_file, code_item);
@@ -666,13 +816,9 @@
       verifier.reset(DumpVerifier(*indent2_os, dex_method_idx, &dex_file, class_def, code_item,
                                   method_access_flags));
     }
-
-    uint32_t oat_method_offsets_offset = oat_class.GetOatMethodOffsetsOffset(class_method_index);
-    const OatMethodOffsets* oat_method_offsets = oat_class.GetOatMethodOffsets(class_method_index);
-    const OatFile::OatMethod oat_method = oat_class.GetOatMethod(class_method_index);
     {
       *indent1_os << "OatMethodOffsets ";
-      if (options_->absolute_addresses_) {
+      if (options_.absolute_addresses_) {
         *indent1_os << StringPrintf("%p ", oat_method_offsets);
       }
       *indent1_os << StringPrintf("(offset=0x%08x)\n", oat_method_offsets_offset);
@@ -685,7 +831,6 @@
         return false;
       }
 
-      uint32_t code_offset = oat_method.GetCodeOffset();
       *indent2_os << StringPrintf("code_offset: 0x%08x ", code_offset);
       uint32_t aligned_code_begin = AlignCodeOffset(oat_method.GetCodeOffset());
       if (aligned_code_begin > oat_file_.Size()) {
@@ -697,7 +842,7 @@
       *indent2_os << "\n";
 
       *indent2_os << "gc_map: ";
-      if (options_->absolute_addresses_) {
+      if (options_.absolute_addresses_) {
         *indent2_os << StringPrintf("%p ", oat_method.GetGcMap());
       }
       uint32_t gc_map_offset = oat_method.GetGcMapOffset();
@@ -707,7 +852,7 @@
                                     "gc map table offset 0x%08x is past end of file 0x%08zx.\n",
                                     gc_map_offset, oat_file_.Size());
         success = false;
-      } else if (options_->dump_raw_gc_map_) {
+      } else if (options_.dump_raw_gc_map_) {
         Indenter indent3_filter(indent2_os->rdbuf(), kIndentChar, kIndentBy1Count);
         std::ostream indent3_os(&indent3_filter);
         DumpGcMap(indent3_os, oat_method, code_item);
@@ -718,7 +863,7 @@
       uint32_t method_header_offset = oat_method.GetOatQuickMethodHeaderOffset();
       const OatQuickMethodHeader* method_header = oat_method.GetOatQuickMethodHeader();
 
-      if (options_->absolute_addresses_) {
+      if (options_.absolute_addresses_) {
         *indent1_os << StringPrintf("%p ", method_header);
       }
       *indent1_os << StringPrintf("(offset=0x%08x)\n", method_header_offset);
@@ -732,7 +877,7 @@
       }
 
       *indent2_os << "mapping_table: ";
-      if (options_->absolute_addresses_) {
+      if (options_.absolute_addresses_) {
         *indent2_os << StringPrintf("%p ", oat_method.GetMappingTable());
       }
       uint32_t mapping_table_offset = oat_method.GetMappingTableOffset();
@@ -744,14 +889,14 @@
                                     mapping_table_offset, oat_file_.Size(),
                                     oat_method.GetMappingTableOffsetOffset());
         success = false;
-      } else if (options_->dump_raw_mapping_table_) {
+      } else if (options_.dump_raw_mapping_table_) {
         Indenter indent3_filter(indent2_os->rdbuf(), kIndentChar, kIndentBy1Count);
         std::ostream indent3_os(&indent3_filter);
         DumpMappingTable(indent3_os, oat_method);
       }
 
       *indent2_os << "vmap_table: ";
-      if (options_->absolute_addresses_) {
+      if (options_.absolute_addresses_) {
         *indent2_os << StringPrintf("%p ", oat_method.GetVmapTable());
       }
       uint32_t vmap_table_offset = oat_method.GetVmapTableOffset();
@@ -763,7 +908,7 @@
                                     vmap_table_offset, oat_file_.Size(),
                                     oat_method.GetVmapTableOffsetOffset());
         success = false;
-      } else if (options_->dump_vmap_) {
+      } else if (options_.dump_vmap_) {
         DumpVmapData(*indent2_os, oat_method, code_item);
       }
     }
@@ -794,12 +939,10 @@
         success = false;
       } else {
         const void* code = oat_method.GetQuickCode();
-        uint32_t code_size = oat_method.GetQuickCodeSize();
-        uint32_t code_offset = oat_method.GetCodeOffset();
         uint32_t aligned_code_begin = AlignCodeOffset(code_offset);
         uint64_t aligned_code_end = aligned_code_begin + code_size;
 
-        if (options_->absolute_addresses_) {
+        if (options_.absolute_addresses_) {
           *indent1_os << StringPrintf("%p ", code);
         }
         *indent1_os << StringPrintf("(code_offset=0x%08x size_offset=0x%08x size=%u)%s\n",
@@ -820,7 +963,7 @@
                                       aligned_code_end, oat_file_.Size(),
                                       code_size, code_size_offset);
           success = false;
-          if (options_->disassemble_code_) {
+          if (options_.disassemble_code_) {
             if (code_size_offset + kPrologueBytes <= oat_file_.Size()) {
               DumpCode(*indent2_os, verifier.get(), oat_method, code_item, true, kPrologueBytes);
             }
@@ -832,12 +975,12 @@
                                       code_size, kMaxCodeSize,
                                       code_size, code_size_offset);
           success = false;
-          if (options_->disassemble_code_) {
+          if (options_.disassemble_code_) {
             if (code_size_offset + kPrologueBytes <= oat_file_.Size()) {
               DumpCode(*indent2_os, verifier.get(), oat_method, code_item, true, kPrologueBytes);
             }
           }
-        } else if (options_->disassemble_code_) {
+        } else if (options_.disassemble_code_) {
           DumpCode(*indent2_os, verifier.get(), oat_method, code_item, !success, 0);
         }
       }
@@ -1175,7 +1318,8 @@
       size_t i = 0;
       while (i < code_item->insns_size_in_code_units_) {
         const Instruction* instruction = Instruction::At(&code_item->insns_[i]);
-        os << StringPrintf("0x%04zx: %s\n", i, instruction->DumpString(&dex_file).c_str());
+        os << StringPrintf("0x%04zx: ", i) << instruction->DumpHexLE(5)
+           << StringPrintf("\t| %s\n", instruction->DumpString(&dex_file).c_str());
         i += instruction->SizeInCodeUnits();
       }
     }
@@ -1191,10 +1335,10 @@
       StackHandleScope<1> hs(soa.Self());
       Handle<mirror::DexCache> dex_cache(
           hs.NewHandle(Runtime::Current()->GetClassLinker()->FindDexCache(*dex_file)));
-      DCHECK(options_->class_loader_ != nullptr);
+      DCHECK(options_.class_loader_ != nullptr);
       return verifier::MethodVerifier::VerifyMethodAndDump(soa.Self(), os, dex_method_idx, dex_file,
                                                            dex_cache,
-                                                           *options_->class_loader_,
+                                                           *options_.class_loader_,
                                                            &class_def, code_item,
                                                            NullHandle<mirror::ArtMethod>(),
                                                            method_access_flags);
@@ -1237,7 +1381,8 @@
 
   const OatFile& oat_file_;
   const std::vector<const OatFile::OatDexFile*> oat_dex_files_;
-  const OatDumperOptions* options_;
+  const OatDumperOptions& options_;
+  uint32_t resolved_addr2instr_;
   InstructionSet instruction_set_;
   std::set<uintptr_t> offsets_;
   Disassembler* disassembler_;
@@ -1335,7 +1480,7 @@
 
     stats_.oat_file_bytes = oat_file->Size();
 
-    oat_dumper_.reset(new OatDumper(*oat_file, oat_dumper_options_.release()));
+    oat_dumper_.reset(new OatDumper(*oat_file, *oat_dumper_options_));
 
     for (const OatFile::OatDexFile* oat_dex_file : oat_file->GetOatDexFiles()) {
       CHECK(oat_dex_file != nullptr);
@@ -2045,17 +2190,18 @@
       soa.Decode<mirror::ClassLoader*>(class_loader));
   options->class_loader_ = &loader_handle;
 
-  OatDumper oat_dumper(*oat_file, options);
+  OatDumper oat_dumper(*oat_file, *options);
   bool success = oat_dumper.Dump(*os);
   return (success) ? EXIT_SUCCESS : EXIT_FAILURE;
 }
 
 static int DumpOatWithoutRuntime(OatFile* oat_file, OatDumperOptions* options, std::ostream* os) {
+  CHECK(oat_file != nullptr && options != nullptr);
   // No image = no class loader.
   NullHandle<mirror::ClassLoader> null_class_loader;
   options->class_loader_ = &null_class_loader;
 
-  OatDumper oat_dumper(*oat_file, options);
+  OatDumper oat_dumper(*oat_file, *options);
   bool success = oat_dumper.Dump(*os);
   return (success) ? EXIT_SUCCESS : EXIT_FAILURE;
 }
@@ -2127,8 +2273,21 @@
     } else if (option.starts_with("--symbolize=")) {
       oat_filename_ = option.substr(strlen("--symbolize=")).data();
       symbolize_ = true;
+    } else if (option.starts_with("--class-filter=")) {
+      class_filter_ = option.substr(strlen("--class-filter=")).data();
     } else if (option.starts_with("--method-filter=")) {
       method_filter_ = option.substr(strlen("--method-filter=")).data();
+    } else if (option.starts_with("--list-classes")) {
+      list_classes_ = true;
+    } else if (option.starts_with("--list-methods")) {
+      list_methods_ = true;
+    } else if (option.starts_with("--export-dex-to=")) {
+      export_dex_location_ = option.substr(strlen("--export-dex-to=")).data();
+    } else if (option.starts_with("--addr2instr=")) {
+      if (!ParseUint(option.substr(strlen("--addr2instr=")).data(), &addr2instr_)) {
+        *error_msg = "Address conversion failed";
+        return kParseError;
+      }
     } else {
       return kParseUnknownArgument;
     }
@@ -2191,8 +2350,29 @@
         "  --no-disassemble may be used to disable disassembly.\n"
         "      Example: --no-disassemble\n"
         "\n"
+        "  --list-classes may be used to list target file classes (can be used with filters).\n"
+        "      Example: --list-classes\n"
+        "      Example: --list-classes --class-filter=com.example.foo\n"
+        "\n"
+        "  --list-methods may be used to list target file methods (can be used with filters).\n"
+        "      Example: --list-methods\n"
+        "      Example: --list-methods --class-filter=com.example --method-filter=foo\n"
+        "\n"
+        "  --symbolize=<file.oat>: output a copy of file.oat with elf symbols included.\n"
+        "      Example: --symbolize=/system/framework/boot.oat\n"
+        "\n"
+        "  --class-filter=<class name>: only dumps classes that contain the filter.\n"
+        "      Example: --class-filter=com.example.foo\n"
+        "\n"
         "  --method-filter=<method name>: only dumps methods that contain the filter.\n"
         "      Example: --method-filter=foo\n"
+        "\n"
+        "  --export-dex-to=<directory>: may be used to export oat embedded dex files.\n"
+        "      Example: --export-dex-to=/data/local/tmp\n"
+        "\n"
+        "  --addr2instr=<address>: output matching method disassembled code from relative\n"
+        "                          address (e.g. PC from crash dump)\n"
+        "      Example: --addr2instr=0x00001a3b\n"
         "\n";
 
     return usage;
@@ -2200,6 +2380,7 @@
 
  public:
   const char* oat_filename_ = nullptr;
+  const char* class_filter_ = "";
   const char* method_filter_ = "";
   const char* image_location_ = nullptr;
   std::string elf_filename_prefix_;
@@ -2208,6 +2389,10 @@
   bool dump_vmap_ = true;
   bool disassemble_code_ = true;
   bool symbolize_ = false;
+  bool list_classes_ = false;
+  bool list_methods_ = false;
+  uint32_t addr2instr_ = 0;
+  const char* export_dex_location_ = nullptr;
 };
 
 struct OatdumpMain : public CmdlineMain<OatdumpArgs> {
@@ -2223,7 +2408,12 @@
         args_->dump_vmap_,
         args_->disassemble_code_,
         absolute_addresses,
-        args_->method_filter_));
+        args_->class_filter_,
+        args_->method_filter_,
+        args_->list_classes_,
+        args_->list_methods_,
+        args_->export_dex_location_,
+        args_->addr2instr_));
 
     return (args_->boot_image_location_ != nullptr || args_->image_location_ != nullptr) &&
           !args_->symbolize_;
@@ -2240,7 +2430,7 @@
     } else {
       return DumpOat(nullptr,
                      args_->oat_filename_,
-                     oat_dumper_options_.release(),
+                     oat_dumper_options_.get(),
                      args_->os_) == EXIT_SUCCESS;
     }
   }
@@ -2251,11 +2441,11 @@
     if (args_->oat_filename_ != nullptr) {
       return DumpOat(runtime,
                      args_->oat_filename_,
-                     oat_dumper_options_.release(),
+                     oat_dumper_options_.get(),
                      args_->os_) == EXIT_SUCCESS;
     }
 
-    return DumpImage(runtime, args_->image_location_, oat_dumper_options_.release(), args_->os_)
+    return DumpImage(runtime, args_->image_location_, oat_dumper_options_.get(), args_->os_)
       == EXIT_SUCCESS;
   }
 
diff --git a/runtime/dex_instruction.cc b/runtime/dex_instruction.cc
index a802759..92e0f07 100644
--- a/runtime/dex_instruction.cc
+++ b/runtime/dex_instruction.cc
@@ -134,6 +134,23 @@
   return os.str();
 }
 
+std::string Instruction::DumpHexLE(size_t instr_code_units) const {
+  size_t inst_length = SizeInCodeUnits();
+  if (inst_length > instr_code_units) {
+    inst_length = instr_code_units;
+  }
+  std::ostringstream os;
+  const uint16_t* insn = reinterpret_cast<const uint16_t*>(this);
+  for (size_t i = 0; i < inst_length; i++) {
+    os << StringPrintf("%02x%02x", (uint8_t)(insn[i] & 0x00FF),
+                       (uint8_t)((insn[i] & 0xFF00)>>8)) << " ";
+  }
+  for (size_t i = inst_length; i < instr_code_units; i++) {
+    os << "       ";
+  }
+  return os.str();
+}
+
 std::string Instruction::DumpString(const DexFile* file) const {
   std::ostringstream os;
   const char* opcode = kInstructionNames[Opcode()];
diff --git a/runtime/dex_instruction.h b/runtime/dex_instruction.h
index af5d9d0..d3b9eb4 100644
--- a/runtime/dex_instruction.h
+++ b/runtime/dex_instruction.h
@@ -525,6 +525,10 @@
   // Dump code_units worth of this instruction, padding to code_units for shorter instructions
   std::string DumpHex(size_t code_units) const;
 
+  // Little-endian dump code_units worth of this instruction, padding to code_units for
+  // shorter instructions
+  std::string DumpHexLE(size_t instr_code_units) const;
+
   uint16_t Fetch16(size_t offset) const {
     const uint16_t* insns = reinterpret_cast<const uint16_t*>(this);
     return insns[offset];