diff options
author | Andreas Gampe <agampe@google.com> | 2015-04-24 21:41:45 -0700 |
---|---|---|
committer | Andreas Gampe <agampe@google.com> | 2015-04-24 22:53:40 -0700 |
commit | 03b9ee44362965efec4a4f3d23e978e390fa842f (patch) | |
tree | 66dd5afc11663eda01c09612f18912148f1c089b /compiler/utils | |
parent | 1e3ab9ad421d471ca8eadac03084ce19fd06d4eb (diff) | |
download | art-03b9ee44362965efec4a4f3d23e978e390fa842f.zip art-03b9ee44362965efec4a4f3d23e978e390fa842f.tar.gz art-03b9ee44362965efec4a4f3d23e978e390fa842f.tar.bz2 |
ART: Refactor utils/assembler test
Split out the part that compares a buffer with the product of a
host assembler. That will allow to reuse this for the Quick
assemblers.
Change-Id: Ie15777cb0a22f7532d8a8ea35403db0f229cd26f
Diffstat (limited to 'compiler/utils')
-rw-r--r-- | compiler/utils/assembler_test.h | 486 | ||||
-rw-r--r-- | compiler/utils/assembler_test_base.h | 544 |
2 files changed, 561 insertions, 469 deletions
diff --git a/compiler/utils/assembler_test.h b/compiler/utils/assembler_test.h index 3fe1a31..a339633 100644 --- a/compiler/utils/assembler_test.h +++ b/compiler/utils/assembler_test.h @@ -19,6 +19,7 @@ #include "assembler.h" +#include "assembler_test_base.h" #include "common_runtime_test.h" // For ScratchFile #include <cstdio> @@ -29,19 +30,11 @@ 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. -static constexpr bool kKeepDisassembledFiles = false; - // Helper for a constexpr string length. constexpr size_t ConstexprStrLen(char const* str, size_t count = 0) { return ('\0' == str[0]) ? count : ConstexprStrLen(str+1, count+1); } -// 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_; - enum class RegisterView { // private kUsePrimaryName, kUseSecondaryName, @@ -59,12 +52,12 @@ class AssemblerTest : public testing::Test { typedef std::string (*TestFn)(AssemblerTest* assembler_test, Ass* assembler); void DriverFn(TestFn f, std::string test_name) { - Driver(f(this, assembler_.get()), test_name); + DriverWrapper(f(this, assembler_.get()), test_name); } // This driver assumes the assembler has already been called. void DriverStr(std::string assembly_string, std::string test_name) { - Driver(assembly_string, test_name); + DriverWrapper(assembly_string, test_name); } std::string RepeatR(void (Ass::*f)(Reg), std::string fmt) { @@ -212,28 +205,7 @@ class AssemblerTest : public testing::Test { // This is intended to be run as a test. bool CheckTools() { - if (!FileExists(FindTool(GetAssemblerCmdName()))) { - return false; - } - LOG(INFO) << "Chosen assembler command: " << GetAssemblerCommand(); - - if (!FileExists(FindTool(GetObjdumpCmdName()))) { - return false; - } - LOG(INFO) << "Chosen objdump command: " << GetObjdumpCommand(); - - // Disassembly is optional. - std::string disassembler = GetDisassembleCommand(); - if (disassembler.length() != 0) { - if (!FileExists(FindTool(GetDisassembleCmdName()))) { - return false; - } - LOG(INFO) << "Chosen disassemble command: " << GetDisassembleCommand(); - } else { - LOG(INFO) << "No disassembler given."; - } - - return true; + return test_helper_->CheckTools(); } // The following functions are public so that TestFn can use them... @@ -272,17 +244,21 @@ class AssemblerTest : public testing::Test { void SetUp() OVERRIDE { assembler_.reset(new Ass()); - - // Fake a runtime test for ScratchFile - CommonRuntimeTest::SetUpAndroidData(android_data_); + test_helper_.reset( + new AssemblerTestInfrastructure(GetArchitectureString(), + GetAssemblerCmdName(), + GetAssemblerParameters(), + GetObjdumpCmdName(), + GetObjdumpParameters(), + GetDisassembleCmdName(), + GetDisassembleParameters(), + GetAssemblyHeader())); SetUpHelpers(); } void TearDown() OVERRIDE { - // We leave temporaries in case this failed so we can debug issues. - CommonRuntimeTest::TearDownAndroidData(android_data_, false); - tmpnam_ = ""; + test_helper_.reset(); // Clean up the helper. } // Override this to set up any architecture-specific things, e.g., register vectors. @@ -301,23 +277,6 @@ class AssemblerTest : public testing::Test { return ""; } - // 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_; - } - - std::string line = FindTool(GetAssemblerCmdName()); - if (line.length() == 0) { - return line; - } - - resolved_assembler_cmd_ = line + GetAssemblerParameters(); - - return resolved_assembler_cmd_; - } - // Get the name of the objdump, e.g., "objdump" by default. virtual std::string GetObjdumpCmdName() { return "objdump"; @@ -328,23 +287,6 @@ class AssemblerTest : public testing::Test { return " -h"; } - // 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(GetObjdumpCmdName()); - if (line.length() == 0) { - return line; - } - - resolved_objdump_cmd_ = line + GetObjdumpParameters(); - - return resolved_objdump_cmd_; - } - // Get the name of the objdump, e.g., "objdump" by default. virtual std::string GetDisassembleCmdName() { return "objdump"; @@ -354,23 +296,6 @@ class AssemblerTest : public testing::Test { // such to objdump, so it's architecture-specific and there is no default. virtual std::string GetDisassembleParameters() = 0; - // 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(GetDisassembleCmdName()); - if (line.length() == 0) { - return line; - } - - resolved_disassemble_cmd_ = line + GetDisassembleParameters(); - - return resolved_disassemble_cmd_; - } - // 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; @@ -618,395 +543,18 @@ class AssemblerTest : public testing::Test { return str; } - // 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(std::string assembly_text, std::string test_name) { - EXPECT_NE(assembly_text.length(), 0U) << "Empty assembly"; - - NativeAssemblerResult res; - Compile(assembly_text, &res, test_name); - - EXPECT_TRUE(res.ok) << res.error_msg; - if (!res.ok) { - // No way of continuing. - return; - } - + void DriverWrapper(std::string assembly_text, std::string test_name) { size_t cs = assembler_->CodeSize(); std::unique_ptr<std::vector<uint8_t>> data(new std::vector<uint8_t>(cs)); MemoryRegion code(&(*data)[0], data->size()); assembler_->FinalizeInstructions(code); - - if (*data == *res.code) { - Clean(&res); - } 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."; - } - } - } - - // 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(GetAssemblerCmdName())); - 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 = 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(INFO) << "Assembler command line:"; - for (std::string arg : args) { - LOG(INFO) << arg; - } - } - return success; - } - - // Runs objdump -h on the binary file and extracts the first line with .text. - // Returns "" on failure. - std::string Objdump(std::string file) { - bool have_objdump = FileExists(FindTool(GetObjdumpCmdName())); - 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 = 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 ""; - } - } - - // Disassemble both binaries and compare the text. - bool DisassembleBinaries(std::vector<uint8_t>& data, std::vector<uint8_t>& as, - 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; - } - - bool DisassembleBinary(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 = Join(args, ' '); - - args.clear(); - args.push_back("/bin/sh"); - args.push_back("-c"); - args.push_back(cmd); - - return Exec(args, error_msg); - } - - std::string WriteToFile(std::vector<uint8_t>& buffer, std::string test_name) { - std::string file_name = GetTmpnam() + std::string("---") + test_name; - const char* data = reinterpret_cast<char*>(buffer.data()); - std::ofstream s_out(file_name + ".o"); - s_out.write(data, buffer.size()); - s_out.close(); - return file_name + ".o"; - } - - bool CompareFiles(std::string f1, std::string f2) { - std::ifstream f1_in(f1); - std::ifstream f2_in(f2); - - bool result = std::equal(std::istreambuf_iterator<char>(f1_in), - std::istreambuf_iterator<char>(), - std::istreambuf_iterator<char>(f2_in)); - - f1_in.close(); - f2_in.close(); - - return result; - } - - // Compile the given assembly code and extract the binary, if possible. Put result into res. - bool Compile(std::string assembly_code, NativeAssemblerResult* res, 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"); - const char* header = GetAssemblyHeader(); - if (header != nullptr) { - s_out << 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(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"; - } - - static std::string GetRootPath() { - // 1) Check ANDROID_BUILD_TOP - char* build_top = getenv("ANDROID_BUILD_TOP"); - if (build_top != nullptr) { - return std::string(build_top) + "/"; - } - - // 2) Do cwd - char temp[1024]; - return getcwd(temp, 1024) ? std::string(temp) + "/" : std::string(""); - } - - std::string FindTool(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(GetArchitectureString() + "*" + 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 = 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; - return ""; - } - - 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; - } - - // Use a consistent tmpnam, so store it. - std::string GetTmpnam() { - if (tmpnam_.length() == 0) { - ScratchFile tmp; - tmpnam_ = tmp.GetFilename() + "asm"; - } - return tmpnam_; + test_helper_->Driver(*data, assembly_text, test_name); } static constexpr size_t kWarnManyCombinationsThreshold = 500; - static constexpr size_t OBJDUMP_SECTION_LINE_MIN_TOKENS = 6; std::unique_ptr<Ass> assembler_; - - std::string resolved_assembler_cmd_; - std::string resolved_objdump_cmd_; - std::string resolved_disassemble_cmd_; - - std::string android_data_; + 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 new file mode 100644 index 0000000..3341151 --- /dev/null +++ b/compiler/utils/assembler_test_base.h @@ -0,0 +1,544 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ART_COMPILER_UTILS_ASSEMBLER_TEST_BASE_H_ +#define ART_COMPILER_UTILS_ASSEMBLER_TEST_BASE_H_ + +#include "common_runtime_test.h" // For ScratchFile + +#include <cstdio> +#include <cstdlib> +#include <fstream> +#include <iterator> +#include <sys/stat.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. +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_; + +// We put this into a class as gtests are self-contained, so this helper needs to be in an h-file. +class AssemblerTestInfrastructure { + 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 + CommonRuntimeTest::SetUpAndroidData(android_data_); + } + + virtual ~AssemblerTestInfrastructure() { + // We leave temporaries in case this failed so we can debug issues. + CommonRuntimeTest::TearDownAndroidData(android_data_, false); + tmpnam_ = ""; + } + + // This is intended to be run as a test. + bool CheckTools() { + if (!FileExists(FindTool(assembler_cmd_name_))) { + return false; + } + LOG(INFO) << "Chosen assembler command: " << GetAssemblerCommand(); + + if (!FileExists(FindTool(objdump_cmd_name_))) { + return false; + } + LOG(INFO) << "Chosen objdump command: " << GetObjdumpCommand(); + + // Disassembly is optional. + std::string disassembler = GetDisassembleCommand(); + if (disassembler.length() != 0) { + if (!FileExists(FindTool(disassembler_cmd_name_))) { + 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, std::string assembly_text, std::string test_name) { + EXPECT_NE(assembly_text.length(), 0U) << "Empty assembly"; + + NativeAssemblerResult res; + Compile(assembly_text, &res, test_name); + + EXPECT_TRUE(res.ok) << res.error_msg; + if (!res.ok) { + // No way of continuing. + return; + } + + if (data == *res.code) { + Clean(&res); + } 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."; + } + } + } + + 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_; + } + + std::string line = FindTool(assembler_cmd_name_); + if (line.length() == 0) { + return line; + } + + resolved_assembler_cmd_ = line + assembler_parameters_; + + return resolved_assembler_cmd_; + } + + // 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_; + } + + // 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_; + } + + 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 = 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(INFO) << "Assembler command line:"; + for (std::string arg : args) { + LOG(INFO) << arg; + } + } + return success; + } + + // Runs objdump -h on the binary file and extracts the first line with .text. + // Returns "" on failure. + std::string Objdump(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 = 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 ""; + } + } + + // Disassemble both binaries and compare the text. + bool DisassembleBinaries(const std::vector<uint8_t>& data, const std::vector<uint8_t>& as, + 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; + } + + bool DisassembleBinary(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 = Join(args, ' '); + + args.clear(); + args.push_back("/bin/sh"); + args.push_back("-c"); + args.push_back(cmd); + + return Exec(args, error_msg); + } + + std::string WriteToFile(const std::vector<uint8_t>& buffer, 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"; + } + + bool CompareFiles(std::string f1, std::string f2) { + std::ifstream f1_in(f1); + std::ifstream f2_in(f2); + + bool result = std::equal(std::istreambuf_iterator<char>(f1_in), + std::istreambuf_iterator<char>(), + std::istreambuf_iterator<char>(f2_in)); + + f1_in.close(); + f2_in.close(); + + return result; + } + + // Compile the given assembly code and extract the binary, if possible. Put result into res. + bool Compile(std::string assembly_code, NativeAssemblerResult* res, 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(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"; + } + + static std::string GetRootPath() { + // 1) Check ANDROID_BUILD_TOP + char* build_top = getenv("ANDROID_BUILD_TOP"); + if (build_top != nullptr) { + return std::string(build_top) + "/"; + } + + // 2) Do cwd + char temp[1024]; + return getcwd(temp, 1024) ? std::string(temp) + "/" : std::string(""); + } + + std::string FindTool(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 = 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; + return ""; + } + + 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; + } + + // 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::string android_data_; + + DISALLOW_COPY_AND_ASSIGN(AssemblerTestInfrastructure); +}; + +} // namespace art + +#endif // ART_COMPILER_UTILS_ASSEMBLER_TEST_BASE_H_ |