Rewrite assembler_test_base.h

Simplify the code in preparation of move to LLVM prebuilt tools.

Bug: 147817558
Test: m test-art-host-gtest
Change-Id: Iba277235255fd7d7f0965749b0b2d4a9567ced1f
diff --git a/compiler/utils/assembler_test.h b/compiler/utils/assembler_test.h
index 9e23d11..9fffda5 100644
--- a/compiler/utils/assembler_test.h
+++ b/compiler/utils/assembler_test.h
@@ -53,7 +53,7 @@
          typename FPReg,
          typename Imm,
          typename VecReg = NoVectorRegs>
-class AssemblerTest : public testing::Test {
+class AssemblerTest : public AssemblerTestBase {
  public:
   Ass* GetAssembler() {
     return assembler_.get();
@@ -683,11 +683,6 @@
                                                                     bias);
   }
 
-  // This is intended to be run as a test.
-  bool CheckTools() {
-    return test_helper_->CheckTools();
-  }
-
   // The following functions are public so that TestFn can use them...
 
   // Returns a vector of address used by any of the repeat methods
@@ -738,23 +733,14 @@
   AssemblerTest() {}
 
   void SetUp() override {
+    AssemblerTestBase::SetUp();
     allocator_.reset(new ArenaAllocator(&pool_));
     assembler_.reset(CreateAssembler(allocator_.get()));
-    test_helper_.reset(
-        new AssemblerTestInfrastructure(GetArchitectureString(),
-                                        GetAssemblerCmdName(),
-                                        GetAssemblerParameters(),
-                                        GetObjdumpCmdName(),
-                                        GetObjdumpParameters(),
-                                        GetDisassembleCmdName(),
-                                        GetDisassembleParameters(),
-                                        GetAssemblyHeader()));
-
     SetUpHelpers();
   }
 
   void TearDown() override {
-    test_helper_.reset();  // Clean up the helper.
+    AssemblerTestBase::TearDown();
     assembler_.reset();
     allocator_.reset();
   }
@@ -767,38 +753,6 @@
   // Override this to set up any architecture-specific things, e.g., register vectors.
   virtual void SetUpHelpers() {}
 
-  // Get the typically used name for this architecture, e.g., aarch64, x86_64, ...
-  virtual std::string GetArchitectureString() = 0;
-
-  // Get the name of the assembler, e.g., "as" by default.
-  virtual std::string GetAssemblerCmdName() {
-    return "as";
-  }
-
-  // Switches to the assembler command. Default none.
-  virtual std::string GetAssemblerParameters() {
-    return "";
-  }
-
-  // Get the name of the objdump, e.g., "objdump" by default.
-  virtual std::string GetObjdumpCmdName() {
-    return "objdump";
-  }
-
-  // Switches to the objdump command. Default is " -h".
-  virtual std::string GetObjdumpParameters() {
-    return " -h";
-  }
-
-  // Get the name of the objdump, e.g., "objdump" by default.
-  virtual std::string GetDisassembleCmdName() {
-    return "objdump";
-  }
-
-  // Switches to the objdump command. As it's a binary, one needs to push the architecture and
-  // such to objdump, so it's architecture-specific and there is no default.
-  virtual std::string GetDisassembleParameters() = 0;
-
   // Create a couple of immediate values up to the number of bytes given.
   virtual std::vector<int64_t> CreateImmediateValues(size_t imm_bytes, bool as_uint = false) {
     std::vector<int64_t> res;
@@ -1529,11 +1483,6 @@
     return sreg.str();
   }
 
-  // If the assembly file needs a header, return it in a sub-class.
-  virtual const char* GetAssemblyHeader() {
-    return nullptr;
-  }
-
   void WarnOnCombinations(size_t count) {
     if (count > kWarnManyCombinationsThreshold) {
       GTEST_LOG_(WARNING) << "Many combinations (" << count << "), test generation might be slow.";
@@ -1602,7 +1551,7 @@
     MemoryRegion code(&(*data)[0], data->size());
     assembler_->FinalizeInstructions(code);
     Pad(*data);
-    test_helper_->Driver(*data, assembly_text, test_name);
+    Driver(*data, assembly_text, test_name);
   }
 
   static constexpr size_t kWarnManyCombinationsThreshold = 500;
@@ -1610,7 +1559,6 @@
   MallocArenaPool pool_;
   std::unique_ptr<ArenaAllocator> allocator_;
   std::unique_ptr<Ass> assembler_;
-  std::unique_ptr<AssemblerTestInfrastructure> test_helper_;
 
   DISALLOW_COPY_AND_ASSIGN(AssemblerTest);
 };
diff --git a/compiler/utils/assembler_test_base.h b/compiler/utils/assembler_test_base.h
index 0a7cf11..7eb3d04 100644
--- a/compiler/utils/assembler_test_base.h
+++ b/compiler/utils/assembler_test_base.h
@@ -25,452 +25,186 @@
 
 #include "android-base/strings.h"
 
+#include "base/os.h"
 #include "base/utils.h"
-#include "common_runtime_test.h"  // For ScratchFile
+#include "common_runtime_test.h"  // For ScratchDir.
+#include "elf/elf_builder.h"
+#include "elf/elf_debug_reader.h"
 #include "exec_utils.h"
+#include "stream/file_output_stream.h"
 
 namespace art {
 
-// If you want to take a look at the differences between the ART assembler and GCC, set this flag
-// to true. The disassembled files will then remain in the tmp directory.
+// Location of prebuilt tools (e.g. objdump).
+// The path needs to be updated when the prebuilt tools are updated.
+// TODO: Consider moving this logic to the build system.
+static constexpr char kPrebuiltToolsPath[] =
+    "prebuilts/gcc/linux-x86/x86/x86_64-linux-android-4.9/x86_64-linux-android/bin/";
+
+// If you want to take a look at the differences between the ART assembler and clang,
+// set this flag to true. The disassembled files will then remain in the tmp directory.
 static constexpr bool kKeepDisassembledFiles = false;
 
-// Use a glocal static variable to keep the same name for all test data. Else we'll just spam the
-// temp directory.
-static std::string tmpnam_;  // NOLINT [runtime/string] [4]
-
 // We put this into a class as gtests are self-contained, so this helper needs to be in an h-file.
-class AssemblerTestInfrastructure {
+class AssemblerTestBase : public testing::Test {
  public:
-  AssemblerTestInfrastructure(std::string architecture,
-                              std::string as,
-                              std::string as_params,
-                              std::string objdump,
-                              std::string objdump_params,
-                              std::string disasm,
-                              std::string disasm_params,
-                              const char* asm_header) :
-      architecture_string_(architecture),
-      asm_header_(asm_header),
-      assembler_cmd_name_(as),
-      assembler_parameters_(as_params),
-      objdump_cmd_name_(objdump),
-      objdump_parameters_(objdump_params),
-      disassembler_cmd_name_(disasm),
-      disassembler_parameters_(disasm_params) {
-    // Fake a runtime test for ScratchFile
+  AssemblerTestBase() {}
+
+  void SetUp() override {
+    // Fake a runtime test for ScratchDir.
+    CommonArtTest::SetUpAndroidRootEnvVars();
     CommonRuntimeTest::SetUpAndroidDataDir(android_data_);
+    scratch_dir_.emplace(/*keep_files=*/ kKeepDisassembledFiles);
   }
 
-  virtual ~AssemblerTestInfrastructure() {
+  void TearDown() override {
     // We leave temporaries in case this failed so we can debug issues.
     CommonRuntimeTest::TearDownAndroidDataDir(android_data_, false);
-    tmpnam_ = "";
   }
 
   // This is intended to be run as a test.
   bool CheckTools() {
-    std::string asm_tool = FindTool(assembler_cmd_name_);
-    if (!FileExists(asm_tool)) {
-      LOG(ERROR) << "Could not find assembler from " << assembler_cmd_name_;
-      LOG(ERROR) << "FindTool returned " << asm_tool;
-      FindToolDump(assembler_cmd_name_);
-      return false;
-    }
-    LOG(INFO) << "Chosen assembler command: " << GetAssemblerCommand();
-
-    std::string objdump_tool = FindTool(objdump_cmd_name_);
-    if (!FileExists(objdump_tool)) {
-      LOG(ERROR) << "Could not find objdump from " << objdump_cmd_name_;
-      LOG(ERROR) << "FindTool returned " << objdump_tool;
-      FindToolDump(objdump_cmd_name_);
-      return false;
-    }
-    LOG(INFO) << "Chosen objdump command: " << GetObjdumpCommand();
-
-    // Disassembly is optional.
-    std::string disassembler = GetDisassembleCommand();
-    if (disassembler.length() != 0) {
-      std::string disassembler_tool = FindTool(disassembler_cmd_name_);
-      if (!FileExists(disassembler_tool)) {
-        LOG(ERROR) << "Could not find disassembler from " << disassembler_cmd_name_;
-        LOG(ERROR) << "FindTool returned " << disassembler_tool;
-        FindToolDump(disassembler_cmd_name_);
+    for (auto cmd : { GetAssemblerCommand()[0], GetDisassemblerCommand()[0] }) {
+      if (!OS::FileExists(cmd.c_str())) {
+        LOG(ERROR) << "Could not find " << cmd;
         return false;
       }
-      LOG(INFO) << "Chosen disassemble command: " << GetDisassembleCommand();
-    } else {
-      LOG(INFO) << "No disassembler given.";
     }
-
     return true;
   }
 
   // Driver() assembles and compares the results. If the results are not equal and we have a
   // disassembler, disassemble both and check whether they have the same mnemonics (in which case
   // we just warn).
-  void Driver(const std::vector<uint8_t>& data,
+  void Driver(const std::vector<uint8_t>& art_code,
               const std::string& assembly_text,
               const std::string& test_name) {
-    EXPECT_NE(assembly_text.length(), 0U) << "Empty assembly";
+    ASSERT_NE(assembly_text.length(), 0U) << "Empty assembly";
+    InstructionSet isa = GetIsa();
+    auto test_path = [&](const char* ext) { return scratch_dir_->GetPath() + test_name + ext; };
 
-    NativeAssemblerResult res;
-    Compile(assembly_text, &res, test_name);
+    // Create file containing the reference source code.
+    std::string ref_asm_file = test_path(".ref.S");
+    WriteFile(ref_asm_file, assembly_text.data(), assembly_text.size());
 
-    EXPECT_TRUE(res.ok) << res.error_msg;
-    if (!res.ok) {
-      // No way of continuing.
-      return;
+    // Assemble reference object file.
+    std::string ref_obj_file = test_path(".ref.o");
+    ASSERT_TRUE(Assemble(ref_asm_file.c_str(), ref_obj_file.c_str()));
+
+    // Read the code produced by assembler from the ELF file.
+    std::vector<uint8_t> ref_code;
+    if (Is64BitInstructionSet(isa)) {
+      ReadElf</*IsElf64=*/true>(ref_obj_file, &ref_code);
+    } else {
+      ReadElf</*IsElf64=*/false>(ref_obj_file, &ref_code);
     }
 
-    if (data == *res.code) {
-      Clean(&res);
+    // Compare the ART generated code to the expected reference code.
+    if (art_code == ref_code) {
+      return;  // Success!
+    }
+
+    // Create ELF file containing the ART code.
+    std::string art_obj_file = test_path(".art.o");
+    if (Is64BitInstructionSet(isa)) {
+      WriteElf</*IsElf64=*/true>(art_obj_file, isa, art_code);
     } else {
-      if (DisassembleBinaries(data, *res.code, test_name)) {
-        if (data.size() > res.code->size()) {
-          // Fail this test with a fancy colored warning being printed.
-          EXPECT_TRUE(false) << "Assembly code is not identical, but disassembly of machine code "
-              "is equal: this implies sub-optimal encoding! Our code size=" << data.size() <<
-              ", gcc size=" << res.code->size();
-        } else {
-          // Otherwise just print an info message and clean up.
-          LOG(INFO) << "GCC chose a different encoding than ours, but the overall length is the "
-              "same.";
-          Clean(&res);
-        }
-      } else {
-        // This will output the assembly.
-        EXPECT_EQ(*res.code, data) << "Outputs (and disassembly) not identical.";
-      }
+      WriteElf</*IsElf64=*/false>(art_obj_file, isa, art_code);
+    }
+
+    // Disassemble both object files, and check that the outputs match.
+    std::string art_disassembly;
+    ASSERT_TRUE(Disassemble(art_obj_file, &art_disassembly));
+    art_disassembly = Replace(art_disassembly, art_obj_file, test_path("<extension-redacted>"));
+    std::string ref_disassembly;
+    ASSERT_TRUE(Disassemble(ref_obj_file, &ref_disassembly));
+    ref_disassembly = Replace(ref_disassembly, ref_obj_file, test_path("<extension-redacted>"));
+    ASSERT_EQ(art_disassembly, ref_disassembly) << "Outputs (and disassembly) not identical.";
+
+    // ART produced different (but valid) code than the reference assembler, report it.
+    if (art_code.size() > ref_code.size()) {
+      EXPECT_TRUE(false) << "ART code is larger then the reference code, but the disassembly"
+          "of machine code is equal: this means that ART is generating sub-optimal encoding! "
+          "ART code size=" << art_code.size() << ", reference code size=" << ref_code.size();
+    } else if (art_code.size() < ref_code.size()) {
+      EXPECT_TRUE(false) << "ART code is smaller than the reference code. Too good to be true?";
+    } else {
+      LOG(INFO) << "Reference assembler chose a different encoding than ART (of the same size)";
     }
   }
 
  protected:
-  // Return the host assembler command for this test.
-  virtual std::string GetAssemblerCommand() {
-    // Already resolved it once?
-    if (resolved_assembler_cmd_.length() != 0) {
-      return resolved_assembler_cmd_;
-    }
+  virtual InstructionSet GetIsa() = 0;
 
-    std::string line = FindTool(assembler_cmd_name_);
-    if (line.length() == 0) {
-      return line;
-    }
-
-    resolved_assembler_cmd_ = line + assembler_parameters_;
-
-    return resolved_assembler_cmd_;
+  std::string FindTool(const std::string& tool_name) {
+    return GetRootPath() + kPrebuiltToolsPath + tool_name;
   }
 
-  // Return the host objdump command for this test.
-  virtual std::string GetObjdumpCommand() {
-    // Already resolved it once?
-    if (resolved_objdump_cmd_.length() != 0) {
-      return resolved_objdump_cmd_;
-    }
-
-    std::string line = FindTool(objdump_cmd_name_);
-    if (line.length() == 0) {
-      return line;
-    }
-
-    resolved_objdump_cmd_ = line + objdump_parameters_;
-
-    return resolved_objdump_cmd_;
+  virtual std::vector<std::string> GetAssemblerCommand() {
+    return {FindTool("as"), Is64BitInstructionSet(GetIsa()) ? "--64" : "--32"};
   }
 
-  // Return the host disassembler command for this test.
-  virtual std::string GetDisassembleCommand() {
-    // Already resolved it once?
-    if (resolved_disassemble_cmd_.length() != 0) {
-      return resolved_disassemble_cmd_;
-    }
-
-    std::string line = FindTool(disassembler_cmd_name_);
-    if (line.length() == 0) {
-      return line;
-    }
-
-    resolved_disassemble_cmd_ = line + disassembler_parameters_;
-
-    return resolved_disassemble_cmd_;
+  virtual std::vector<std::string> GetDisassemblerCommand() {
+    return {FindTool("objdump"), "--disassemble", "--no-show-raw-insn"};
   }
 
  private:
-  // Structure to store intermediates and results.
-  struct NativeAssemblerResult {
-    bool ok;
-    std::string error_msg;
-    std::string base_name;
-    std::unique_ptr<std::vector<uint8_t>> code;
-    uintptr_t length;
-  };
-
-  // Compile the assembly file from_file to a binary file to_file. Returns true on success.
-  bool Assemble(const char* from_file, const char* to_file, std::string* error_msg) {
-    bool have_assembler = FileExists(FindTool(assembler_cmd_name_));
-    EXPECT_TRUE(have_assembler) << "Cannot find assembler:" << GetAssemblerCommand();
-    if (!have_assembler) {
-      return false;
-    }
-
-    std::vector<std::string> args;
-
-    // Encaspulate the whole command line in a single string passed to
-    // the shell, so that GetAssemblerCommand() may contain arguments
-    // in addition to the program name.
-    args.push_back(GetAssemblerCommand());
-    args.push_back("-o");
-    args.push_back(to_file);
-    args.push_back(from_file);
-    std::string cmd = android::base::Join(args, ' ');
-
-    args.clear();
-    args.push_back("/bin/sh");
-    args.push_back("-c");
-    args.push_back(cmd);
-
-    bool success = Exec(args, error_msg);
-    if (!success) {
-      LOG(ERROR) << "Assembler command line:";
-      for (const std::string& arg : args) {
-        LOG(ERROR) << arg;
-      }
-    }
-    return success;
+  bool Assemble(const std::string& asm_file, const std::string& obj_file) {
+    std::vector<std::string> args = GetAssemblerCommand();
+    args.insert(args.end(), {"-o", obj_file, asm_file});
+    std::string output;
+    return CommonArtTestImpl::ForkAndExec(args, [](){ return true; }, &output).StandardSuccess();
   }
 
-  // Runs objdump -h on the binary file and extracts the first line with .text.
-  // Returns "" on failure.
-  std::string Objdump(const std::string& file) {
-    bool have_objdump = FileExists(FindTool(objdump_cmd_name_));
-    EXPECT_TRUE(have_objdump) << "Cannot find objdump: " << GetObjdumpCommand();
-    if (!have_objdump) {
-      return "";
-    }
-
-    std::string error_msg;
-    std::vector<std::string> args;
-
-    // Encaspulate the whole command line in a single string passed to
-    // the shell, so that GetObjdumpCommand() may contain arguments
-    // in addition to the program name.
-    args.push_back(GetObjdumpCommand());
-    args.push_back(file);
-    args.push_back(">");
-    args.push_back(file+".dump");
-    std::string cmd = android::base::Join(args, ' ');
-
-    args.clear();
-    args.push_back("/bin/sh");
-    args.push_back("-c");
-    args.push_back(cmd);
-
-    if (!Exec(args, &error_msg)) {
-      EXPECT_TRUE(false) << error_msg;
-    }
-
-    std::ifstream dump(file+".dump");
-
-    std::string line;
-    bool found = false;
-    while (std::getline(dump, line)) {
-      if (line.find(".text") != line.npos) {
-        found = true;
-        break;
-      }
-    }
-
-    dump.close();
-
-    if (found) {
-      return line;
-    } else {
-      return "";
-    }
+  bool Disassemble(const std::string& obj_file, std::string* output) {
+    std::vector<std::string> args = GetDisassemblerCommand();
+    args.insert(args.end(), {obj_file});
+    return CommonArtTestImpl::ForkAndExec(args, [](){ return true; }, output).StandardSuccess();
   }
 
-  // Disassemble both binaries and compare the text.
-  bool DisassembleBinaries(const std::vector<uint8_t>& data,
-                           const std::vector<uint8_t>& as,
-                           const std::string& test_name) {
-    std::string disassembler = GetDisassembleCommand();
-    if (disassembler.length() == 0) {
-      LOG(WARNING) << "No dissassembler command.";
-      return false;
-    }
-
-    std::string data_name = WriteToFile(data, test_name + ".ass");
-    std::string error_msg;
-    if (!DisassembleBinary(data_name, &error_msg)) {
-      LOG(INFO) << "Error disassembling: " << error_msg;
-      std::remove(data_name.c_str());
-      return false;
-    }
-
-    std::string as_name = WriteToFile(as, test_name + ".gcc");
-    if (!DisassembleBinary(as_name, &error_msg)) {
-      LOG(INFO) << "Error disassembling: " << error_msg;
-      std::remove(data_name.c_str());
-      std::remove((data_name + ".dis").c_str());
-      std::remove(as_name.c_str());
-      return false;
-    }
-
-    bool result = CompareFiles(data_name + ".dis", as_name + ".dis");
-
-    if (!kKeepDisassembledFiles) {
-      std::remove(data_name.c_str());
-      std::remove(as_name.c_str());
-      std::remove((data_name + ".dis").c_str());
-      std::remove((as_name + ".dis").c_str());
-    }
-
-    return result;
+  std::vector<uint8_t> ReadFile(const std::string& filename) {
+    std::unique_ptr<File> file(OS::OpenFileForReading(filename.c_str()));
+    CHECK(file.get() != nullptr);
+    std::vector<uint8_t> data(file->GetLength());
+    bool success = file->ReadFully(&data[0], data.size());
+    CHECK(success) << filename;
+    return data;
   }
 
-  bool DisassembleBinary(const std::string& file, std::string* error_msg) {
-    std::vector<std::string> args;
-
-    // Encaspulate the whole command line in a single string passed to
-    // the shell, so that GetDisassembleCommand() may contain arguments
-    // in addition to the program name.
-    args.push_back(GetDisassembleCommand());
-    args.push_back(file);
-    args.push_back("| sed -n \'/<.data>/,$p\' | sed -e \'s/.*://\'");
-    args.push_back(">");
-    args.push_back(file+".dis");
-    std::string cmd = android::base::Join(args, ' ');
-
-    args.clear();
-    args.push_back("/bin/sh");
-    args.push_back("-c");
-    args.push_back(cmd);
-
-    return Exec(args, error_msg);
+  void WriteFile(const std::string& filename, const void* data, size_t size) {
+    std::unique_ptr<File> file(OS::CreateEmptyFile(filename.c_str()));
+    CHECK(file.get() != nullptr);
+    bool success = file->WriteFully(data, size);
+    CHECK(success) << filename;
+    CHECK_EQ(file->FlushClose(), 0);
   }
 
-  std::string WriteToFile(const std::vector<uint8_t>& buffer, const std::string& test_name) {
-    std::string file_name = GetTmpnam() + std::string("---") + test_name;
-    const char* data = reinterpret_cast<const char*>(buffer.data());
-    std::ofstream s_out(file_name + ".o");
-    s_out.write(data, buffer.size());
-    s_out.close();
-    return file_name + ".o";
+  // Helper method which reads the content of .text section from ELF file.
+  template<bool IsElf64>
+  void ReadElf(const std::string& filename, /*out*/ std::vector<uint8_t>* code) {
+    using ElfTypes = typename std::conditional<IsElf64, ElfTypes64, ElfTypes32>::type;
+    std::vector<uint8_t> data = ReadFile(filename);
+    ElfDebugReader<ElfTypes> reader((ArrayRef<const uint8_t>(data)));
+    const typename ElfTypes::Shdr* text = reader.GetSection(".text");
+    CHECK(text != nullptr);
+    *code = std::vector<uint8_t>(&data[text->sh_offset], &data[text->sh_offset + text->sh_size]);
   }
 
-  bool CompareFiles(const std::string& f1, const std::string& f2) {
-    std::ifstream f1_in(f1);
-    std::ifstream f2_in(f2);
-
-    bool read1_ok, read2_ok;
-    char c1, c2;
-    do {
-      read1_ok = static_cast<bool>(f1_in >> c1);
-      read2_ok = static_cast<bool>(f2_in >> c2);
-    } while (read1_ok && read2_ok && c1 == c2);
-    return !read1_ok && !read2_ok;  // Did we reach the end of both streams?
-  }
-
-  // Compile the given assembly code and extract the binary, if possible. Put result into res.
-  bool Compile(const std::string& assembly_code,
-               NativeAssemblerResult* res,
-               const std::string& test_name) {
-    res->ok = false;
-    res->code.reset(nullptr);
-
-    res->base_name = GetTmpnam() + std::string("---") + test_name;
-
-    // TODO: Lots of error checking.
-
-    std::ofstream s_out(res->base_name + ".S");
-    if (asm_header_ != nullptr) {
-      s_out << asm_header_;
-    }
-    s_out << assembly_code;
-    s_out.close();
-
-    if (!Assemble((res->base_name + ".S").c_str(), (res->base_name + ".o").c_str(),
-                  &res->error_msg)) {
-      res->error_msg = "Could not compile.";
-      return false;
-    }
-
-    std::string odump = Objdump(res->base_name + ".o");
-    if (odump.length() == 0) {
-      res->error_msg = "Objdump failed.";
-      return false;
-    }
-
-    std::istringstream iss(odump);
-    std::istream_iterator<std::string> start(iss);
-    std::istream_iterator<std::string> end;
-    std::vector<std::string> tokens(start, end);
-
-    if (tokens.size() < OBJDUMP_SECTION_LINE_MIN_TOKENS) {
-      res->error_msg = "Objdump output not recognized: too few tokens.";
-      return false;
-    }
-
-    if (tokens[1] != ".text") {
-      res->error_msg = "Objdump output not recognized: .text not second token.";
-      return false;
-    }
-
-    std::string lengthToken = "0x" + tokens[2];
-    std::istringstream(lengthToken) >> std::hex >> res->length;
-
-    std::string offsetToken = "0x" + tokens[5];
-    uintptr_t offset;
-    std::istringstream(offsetToken) >> std::hex >> offset;
-
-    std::ifstream obj(res->base_name + ".o");
-    obj.seekg(offset);
-    res->code.reset(new std::vector<uint8_t>(res->length));
-    obj.read(reinterpret_cast<char*>(&(*res->code)[0]), res->length);
-    obj.close();
-
-    res->ok = true;
-    return true;
-  }
-
-  // Remove temporary files.
-  void Clean(const NativeAssemblerResult* res) {
-    std::remove((res->base_name + ".S").c_str());
-    std::remove((res->base_name + ".o").c_str());
-    std::remove((res->base_name + ".o.dump").c_str());
-  }
-
-  // Check whether file exists. Is used for commands, so strips off any parameters: anything after
-  // the first space. We skip to the last slash for this, so it should work with directories with
-  // spaces.
-  static bool FileExists(const std::string& file) {
-    if (file.length() == 0) {
-      return false;
-    }
-
-    // Need to strip any options.
-    size_t last_slash = file.find_last_of('/');
-    if (last_slash == std::string::npos) {
-      // No slash, start looking at the start.
-      last_slash = 0;
-    }
-    size_t space_index = file.find(' ', last_slash);
-
-    if (space_index == std::string::npos) {
-      std::ifstream infile(file.c_str());
-      return infile.good();
-    } else {
-      std::string copy = file.substr(0, space_index - 1);
-
-      struct stat buf;
-      return stat(copy.c_str(), &buf) == 0;
-    }
-  }
-
-  static std::string GetGCCRootPath() {
-    return "prebuilts/gcc/linux-x86";
+  // Helper method to create an ELF file containing only the given code in the .text section.
+  template<bool IsElf64>
+  void WriteElf(const std::string& filename, InstructionSet isa, const std::vector<uint8_t>& code) {
+    using ElfTypes = typename std::conditional<IsElf64, ElfTypes64, ElfTypes32>::type;
+    std::unique_ptr<File> file(OS::CreateEmptyFile(filename.c_str()));
+    CHECK(file.get() != nullptr);
+    FileOutputStream out(file.get());
+    std::unique_ptr<ElfBuilder<ElfTypes>> builder(new ElfBuilder<ElfTypes>(isa, &out));
+    builder->Start(/* write_program_headers= */ false);
+    builder->GetText()->Start();
+    builder->GetText()->WriteFully(code.data(), code.size());
+    builder->GetText()->End();
+    builder->End();
+    CHECK(builder->Good());
+    CHECK_EQ(file->Close(), 0);
   }
 
   static std::string GetRootPath() {
@@ -485,127 +219,14 @@
     return getcwd(temp, 1024) ? std::string(temp) + "/" : std::string("");
   }
 
-  std::string FindTool(const std::string& tool_name) {
-    // Find the current tool. Wild-card pattern is "arch-string*tool-name".
-    std::string gcc_path = GetRootPath() + GetGCCRootPath();
-    std::vector<std::string> args;
-    args.push_back("find");
-    args.push_back(gcc_path);
-    args.push_back("-name");
-    args.push_back(architecture_string_ + "*" + tool_name);
-    args.push_back("|");
-    args.push_back("sort");
-    args.push_back("|");
-    args.push_back("tail");
-    args.push_back("-n");
-    args.push_back("1");
-    std::string tmp_file = GetTmpnam();
-    args.push_back(">");
-    args.push_back(tmp_file);
-    std::string sh_args = android::base::Join(args, ' ');
-
-    args.clear();
-    args.push_back("/bin/sh");
-    args.push_back("-c");
-    args.push_back(sh_args);
-
-    std::string error_msg;
-    if (!Exec(args, &error_msg)) {
-      EXPECT_TRUE(false) << error_msg;
-      UNREACHABLE();
-    }
-
-    std::ifstream in(tmp_file.c_str());
-    std::string line;
-    if (!std::getline(in, line)) {
-      in.close();
-      std::remove(tmp_file.c_str());
-      return "";
-    }
-    in.close();
-    std::remove(tmp_file.c_str());
-    return line;
+  std::string& Replace(std::string& str, const std::string& from, const std::string& to) {
+    auto it = str.find(from);
+    return (it != std::string::npos) ? str.replace(it, it + from.size(), to) : str;
   }
 
-  // Helper for below. If name_predicate is empty, search for all files, otherwise use it for the
-  // "-name" option.
-  static void FindToolDumpPrintout(const std::string& name_predicate,
-                                   const std::string& tmp_file) {
-    std::string gcc_path = GetRootPath() + GetGCCRootPath();
-    std::vector<std::string> args;
-    args.push_back("find");
-    args.push_back(gcc_path);
-    if (!name_predicate.empty()) {
-      args.push_back("-name");
-      args.push_back(name_predicate);
-    }
-    args.push_back("|");
-    args.push_back("sort");
-    args.push_back(">");
-    args.push_back(tmp_file);
-    std::string sh_args = android::base::Join(args, ' ');
-
-    args.clear();
-    args.push_back("/bin/sh");
-    args.push_back("-c");
-    args.push_back(sh_args);
-
-    std::string error_msg;
-    if (!Exec(args, &error_msg)) {
-      EXPECT_TRUE(false) << error_msg;
-      UNREACHABLE();
-    }
-
-    LOG(ERROR) << "FindToolDump: gcc_path=" << gcc_path
-               << " cmd=" << sh_args;
-    std::ifstream in(tmp_file.c_str());
-    if (in) {
-      std::string line;
-      while (std::getline(in, line)) {
-        LOG(ERROR) << line;
-      }
-    }
-    in.close();
-    std::remove(tmp_file.c_str());
-  }
-
-  // For debug purposes.
-  void FindToolDump(const std::string& tool_name) {
-    // Check with the tool name.
-    FindToolDumpPrintout(architecture_string_ + "*" + tool_name, GetTmpnam());
-    FindToolDumpPrintout("", GetTmpnam());
-  }
-
-  // Use a consistent tmpnam, so store it.
-  std::string GetTmpnam() {
-    if (tmpnam_.length() == 0) {
-      ScratchFile tmp;
-      tmpnam_ = tmp.GetFilename() + "asm";
-    }
-    return tmpnam_;
-  }
-
-  static constexpr size_t OBJDUMP_SECTION_LINE_MIN_TOKENS = 6;
-
-  std::string architecture_string_;
-  const char* asm_header_;
-
-  std::string assembler_cmd_name_;
-  std::string assembler_parameters_;
-
-  std::string objdump_cmd_name_;
-  std::string objdump_parameters_;
-
-  std::string disassembler_cmd_name_;
-  std::string disassembler_parameters_;
-
-  std::string resolved_assembler_cmd_;
-  std::string resolved_objdump_cmd_;
-  std::string resolved_disassemble_cmd_;
-
+  std::optional<ScratchDir> scratch_dir_;
   std::string android_data_;
-
-  DISALLOW_COPY_AND_ASSIGN(AssemblerTestInfrastructure);
+  DISALLOW_COPY_AND_ASSIGN(AssemblerTestBase);
 };
 
 }  // namespace art
diff --git a/compiler/utils/jni_macro_assembler_test.h b/compiler/utils/jni_macro_assembler_test.h
index 067a595..e77177e 100644
--- a/compiler/utils/jni_macro_assembler_test.h
+++ b/compiler/utils/jni_macro_assembler_test.h
@@ -33,7 +33,7 @@
 namespace art {
 
 template<typename Ass>
-class JNIMacroAssemblerTest : public testing::Test {
+class JNIMacroAssemblerTest : public AssemblerTestBase {
  public:
   Ass* GetAssembler() {
     return assembler_.get();
@@ -50,32 +50,18 @@
     DriverWrapper(assembly_string, test_name);
   }
 
-  // This is intended to be run as a test.
-  bool CheckTools() {
-    return test_helper_->CheckTools();
-  }
-
  protected:
   JNIMacroAssemblerTest() {}
 
   void SetUp() override {
+    AssemblerTestBase::SetUp();
     allocator_.reset(new ArenaAllocator(&pool_));
     assembler_.reset(CreateAssembler(allocator_.get()));
-    test_helper_.reset(
-        new AssemblerTestInfrastructure(GetArchitectureString(),
-                                        GetAssemblerCmdName(),
-                                        GetAssemblerParameters(),
-                                        GetObjdumpCmdName(),
-                                        GetObjdumpParameters(),
-                                        GetDisassembleCmdName(),
-                                        GetDisassembleParameters(),
-                                        GetAssemblyHeader()));
-
     SetUpHelpers();
   }
 
   void TearDown() override {
-    test_helper_.reset();  // Clean up the helper.
+    AssemblerTestBase::TearDown();
     assembler_.reset();
     allocator_.reset();
   }
@@ -88,43 +74,6 @@
   // Override this to set up any architecture-specific things, e.g., register vectors.
   virtual void SetUpHelpers() {}
 
-  // Get the typically used name for this architecture, e.g., aarch64, x86_64, ...
-  virtual std::string GetArchitectureString() = 0;
-
-  // Get the name of the assembler, e.g., "as" by default.
-  virtual std::string GetAssemblerCmdName() {
-    return "as";
-  }
-
-  // Switches to the assembler command. Default none.
-  virtual std::string GetAssemblerParameters() {
-    return "";
-  }
-
-  // Get the name of the objdump, e.g., "objdump" by default.
-  virtual std::string GetObjdumpCmdName() {
-    return "objdump";
-  }
-
-  // Switches to the objdump command. Default is " -h".
-  virtual std::string GetObjdumpParameters() {
-    return " -h";
-  }
-
-  // Get the name of the objdump, e.g., "objdump" by default.
-  virtual std::string GetDisassembleCmdName() {
-    return "objdump";
-  }
-
-  // Switches to the objdump command. As it's a binary, one needs to push the architecture and
-  // such to objdump, so it's architecture-specific and there is no default.
-  virtual std::string GetDisassembleParameters() = 0;
-
-  // If the assembly file needs a header, return it in a sub-class.
-  virtual const char* GetAssemblyHeader() {
-    return nullptr;
-  }
-
  private:
   // Override this to pad the code with NOPs to a certain size if needed.
   virtual void Pad(std::vector<uint8_t>& data ATTRIBUTE_UNUSED) {
@@ -137,13 +86,12 @@
     MemoryRegion code(&(*data)[0], data->size());
     assembler_->FinalizeInstructions(code);
     Pad(*data);
-    test_helper_->Driver(*data, assembly_text, test_name);
+    Driver(*data, assembly_text, test_name);
   }
 
   MallocArenaPool pool_;
   std::unique_ptr<ArenaAllocator> allocator_;
   std::unique_ptr<Ass> assembler_;
-  std::unique_ptr<AssemblerTestInfrastructure> test_helper_;
 
   DISALLOW_COPY_AND_ASSIGN(JNIMacroAssemblerTest);
 };
diff --git a/compiler/utils/x86/assembler_x86_test.cc b/compiler/utils/x86/assembler_x86_test.cc
index baef254..d7bedd0 100644
--- a/compiler/utils/x86/assembler_x86_test.cc
+++ b/compiler/utils/x86/assembler_x86_test.cc
@@ -51,16 +51,8 @@
                              x86::Immediate>;
 
  protected:
-  std::string GetArchitectureString() override {
-    return "x86";
-  }
-
-  std::string GetAssemblerParameters() override {
-    return " --32";
-  }
-
-  std::string GetDisassembleParameters() override {
-    return " -D -bbinary -mi386 --no-show-raw-insn";
+  InstructionSet GetIsa() override {
+    return InstructionSet::kX86;
   }
 
   void SetUpHelpers() override {
diff --git a/compiler/utils/x86_64/assembler_x86_64_test.cc b/compiler/utils/x86_64/assembler_x86_64_test.cc
index c4ca716..30f5ef2 100644
--- a/compiler/utils/x86_64/assembler_x86_64_test.cc
+++ b/compiler/utils/x86_64/assembler_x86_64_test.cc
@@ -144,13 +144,8 @@
                              x86_64::Immediate>;
 
  protected:
-  // Get the typically used name for this architecture, e.g., aarch64, x86-64, ...
-  std::string GetArchitectureString() override {
-    return "x86_64";
-  }
-
-  std::string GetDisassembleParameters() override {
-    return " -D -bbinary -mi386:x86-64 -Mx86-64,addr64,data32 --no-show-raw-insn";
+  InstructionSet GetIsa() override {
+    return InstructionSet::kX86_64;
   }
 
   void SetUpHelpers() override {
@@ -2319,13 +2314,8 @@
   using Base = JNIMacroAssemblerTest<x86_64::X86_64JNIMacroAssembler>;
 
  protected:
-  // Get the typically used name for this architecture, e.g., aarch64, x86-64, ...
-  std::string GetArchitectureString() override {
-    return "x86_64";
-  }
-
-  std::string GetDisassembleParameters() override {
-    return " -D -bbinary -mi386:x86-64 -Mx86-64,addr64,data32 --no-show-raw-insn";
+  InstructionSet GetIsa() override {
+    return InstructionSet::kX86_64;
   }
 
  private:
diff --git a/libartbase/base/common_art_test.cc b/libartbase/base/common_art_test.cc
index e0e44a8..043d35e 100644
--- a/libartbase/base/common_art_test.cc
+++ b/libartbase/base/common_art_test.cc
@@ -53,7 +53,7 @@
 
 using android::base::StringPrintf;
 
-ScratchDir::ScratchDir() {
+ScratchDir::ScratchDir(bool keep_files) : keep_files_(keep_files) {
   // ANDROID_DATA needs to be set
   CHECK_NE(static_cast<char*>(nullptr), getenv("ANDROID_DATA")) <<
       "Are you subclassing RuntimeTest?";
@@ -65,15 +65,17 @@
 }
 
 ScratchDir::~ScratchDir() {
-  // Recursively delete the directory and all its content.
-  nftw(path_.c_str(), [](const char* name, const struct stat*, int type, struct FTW *) {
-    if (type == FTW_F) {
-      unlink(name);
-    } else if (type == FTW_DP) {
-      rmdir(name);
-    }
-    return 0;
-  }, 256 /* max open file descriptors */, FTW_DEPTH);
+  if (!keep_files_) {
+    // Recursively delete the directory and all its content.
+    nftw(path_.c_str(), [](const char* name, const struct stat*, int type, struct FTW *) {
+      if (type == FTW_F) {
+        unlink(name);
+      } else if (type == FTW_DP) {
+        rmdir(name);
+      }
+      return 0;
+    }, 256 /* max open file descriptors */, FTW_DEPTH);
+  }
 }
 
 ScratchFile::ScratchFile() {
diff --git a/libartbase/base/common_art_test.h b/libartbase/base/common_art_test.h
index 50aa326..fe045ca 100644
--- a/libartbase/base/common_art_test.h
+++ b/libartbase/base/common_art_test.h
@@ -45,7 +45,7 @@
 
 class ScratchDir {
  public:
-  ScratchDir();
+  explicit ScratchDir(bool keep_files = false);
 
   ~ScratchDir();
 
@@ -55,6 +55,7 @@
 
  private:
   std::string path_;
+  bool keep_files_;  // Useful for debugging.
 
   DISALLOW_COPY_AND_ASSIGN(ScratchDir);
 };
diff --git a/test/common/gtest_main.cc b/test/common/gtest_main.cc
index 9176001..e1cbdc4 100644
--- a/test/common/gtest_main.cc
+++ b/test/common/gtest_main.cc
@@ -25,8 +25,10 @@
 #include "runtime.h"
 
 extern "C" bool GetInitialArgs(const char*** args, size_t* num_args) {
-  static const char* initial_args[] = {"--deadline_threshold_ms=1200000",  // hwasan takes ~10min.
-                                       "--slow_threshold_ms=300000"};
+  static const char* initial_args[] = {
+      "--deadline_threshold_ms=1200000",  // hwasan takes ~10min.
+      "--slow_threshold_ms=300000",
+  };
   *args = initial_args;
   *num_args = 2;
   return true;