Home | History | Annotate | Download | only in utils
      1 /*
      2  * Copyright (C) 2014 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 #ifndef ART_COMPILER_UTILS_ASSEMBLER_TEST_H_
     18 #define ART_COMPILER_UTILS_ASSEMBLER_TEST_H_
     19 
     20 #include "assembler.h"
     21 
     22 #include "common_runtime_test.h"  // For ScratchFile
     23 
     24 #include <cstdio>
     25 #include <cstdlib>
     26 #include <fstream>
     27 #include <iostream>
     28 #include <iterator>
     29 #include <sys/stat.h>
     30 
     31 namespace art {
     32 
     33 // Use a glocal static variable to keep the same name for all test data. Else we'll just spam the
     34 // temp directory.
     35 static std::string tmpnam_;
     36 
     37 template<typename Ass, typename Reg, typename Imm>
     38 class AssemblerTest : public testing::Test {
     39  public:
     40   Ass* GetAssembler() {
     41     return assembler_.get();
     42   }
     43 
     44   typedef std::string (*TestFn)(Ass* assembler);
     45 
     46   void DriverFn(TestFn f, std::string test_name) {
     47     Driver(f(assembler_.get()), test_name);
     48   }
     49 
     50   // This driver assumes the assembler has already been called.
     51   void DriverStr(std::string assembly_string, std::string test_name) {
     52     Driver(assembly_string, test_name);
     53   }
     54 
     55   std::string RepeatR(void (Ass::*f)(Reg), std::string fmt) {
     56     const std::vector<Reg*> registers = GetRegisters();
     57     std::string str;
     58     for (auto reg : registers) {
     59       (assembler_.get()->*f)(*reg);
     60       std::string base = fmt;
     61 
     62       size_t reg_index = base.find("{reg}");
     63       if (reg_index != std::string::npos) {
     64         std::ostringstream sreg;
     65         sreg << *reg;
     66         std::string reg_string = sreg.str();
     67         base.replace(reg_index, 5, reg_string);
     68       }
     69 
     70       if (str.size() > 0) {
     71         str += "\n";
     72       }
     73       str += base;
     74     }
     75     // Add a newline at the end.
     76     str += "\n";
     77     return str;
     78   }
     79 
     80   std::string RepeatRR(void (Ass::*f)(Reg, Reg), std::string fmt) {
     81     const std::vector<Reg*> registers = GetRegisters();
     82     std::string str;
     83     for (auto reg1 : registers) {
     84       for (auto reg2 : registers) {
     85         (assembler_.get()->*f)(*reg1, *reg2);
     86         std::string base = fmt;
     87 
     88         size_t reg1_index = base.find("{reg1}");
     89         if (reg1_index != std::string::npos) {
     90           std::ostringstream sreg;
     91           sreg << *reg1;
     92           std::string reg_string = sreg.str();
     93           base.replace(reg1_index, 6, reg_string);
     94         }
     95 
     96         size_t reg2_index = base.find("{reg2}");
     97         if (reg2_index != std::string::npos) {
     98           std::ostringstream sreg;
     99           sreg << *reg2;
    100           std::string reg_string = sreg.str();
    101           base.replace(reg2_index, 6, reg_string);
    102         }
    103 
    104         if (str.size() > 0) {
    105           str += "\n";
    106         }
    107         str += base;
    108       }
    109     }
    110     // Add a newline at the end.
    111     str += "\n";
    112     return str;
    113   }
    114 
    115   std::string RepeatRI(void (Ass::*f)(Reg, const Imm&), size_t imm_bytes, std::string fmt) {
    116     const std::vector<Reg*> registers = GetRegisters();
    117     std::string str;
    118     std::vector<int64_t> imms = CreateImmediateValues(imm_bytes);
    119     for (auto reg : registers) {
    120       for (int64_t imm : imms) {
    121         Imm* new_imm = CreateImmediate(imm);
    122         (assembler_.get()->*f)(*reg, *new_imm);
    123         delete new_imm;
    124         std::string base = fmt;
    125 
    126         size_t reg_index = base.find("{reg}");
    127         if (reg_index != std::string::npos) {
    128           std::ostringstream sreg;
    129           sreg << *reg;
    130           std::string reg_string = sreg.str();
    131           base.replace(reg_index, 5, reg_string);
    132         }
    133 
    134         size_t imm_index = base.find("{imm}");
    135         if (imm_index != std::string::npos) {
    136           std::ostringstream sreg;
    137           sreg << imm;
    138           std::string imm_string = sreg.str();
    139           base.replace(imm_index, 5, imm_string);
    140         }
    141 
    142         if (str.size() > 0) {
    143           str += "\n";
    144         }
    145         str += base;
    146       }
    147     }
    148     // Add a newline at the end.
    149     str += "\n";
    150     return str;
    151   }
    152 
    153   std::string RepeatI(void (Ass::*f)(const Imm&), size_t imm_bytes, std::string fmt) {
    154     std::string str;
    155     std::vector<int64_t> imms = CreateImmediateValues(imm_bytes);
    156     for (int64_t imm : imms) {
    157       Imm* new_imm = CreateImmediate(imm);
    158       (assembler_.get()->*f)(*new_imm);
    159       delete new_imm;
    160       std::string base = fmt;
    161 
    162       size_t imm_index = base.find("{imm}");
    163       if (imm_index != std::string::npos) {
    164         std::ostringstream sreg;
    165         sreg << imm;
    166         std::string imm_string = sreg.str();
    167         base.replace(imm_index, 5, imm_string);
    168       }
    169 
    170       if (str.size() > 0) {
    171         str += "\n";
    172       }
    173       str += base;
    174     }
    175     // Add a newline at the end.
    176     str += "\n";
    177     return str;
    178   }
    179 
    180   // This is intended to be run as a test.
    181   bool CheckTools() {
    182     if (!FileExists(GetAssemblerCommand())) {
    183       return false;
    184     }
    185     LOG(INFO) << "Chosen assembler command: " << GetAssemblerCommand();
    186 
    187     if (!FileExists(GetObjdumpCommand())) {
    188       return false;
    189     }
    190     LOG(INFO) << "Chosen objdump command: " << GetObjdumpCommand();
    191 
    192     // Disassembly is optional.
    193     std::string disassembler = GetDisassembleCommand();
    194     if (disassembler.length() != 0) {
    195       if (!FileExists(disassembler)) {
    196         return false;
    197       }
    198       LOG(INFO) << "Chosen disassemble command: " << GetDisassembleCommand();
    199     } else {
    200       LOG(INFO) << "No disassembler given.";
    201     }
    202 
    203     return true;
    204   }
    205 
    206  protected:
    207   void SetUp() OVERRIDE {
    208     assembler_.reset(new Ass());
    209 
    210     // Fake a runtime test for ScratchFile
    211     CommonRuntimeTest::SetUpAndroidData(android_data_);
    212 
    213     SetUpHelpers();
    214   }
    215 
    216   void TearDown() OVERRIDE {
    217     // We leave temporaries in case this failed so we can debug issues.
    218     CommonRuntimeTest::TearDownAndroidData(android_data_, false);
    219     tmpnam_ = "";
    220   }
    221 
    222   // Override this to set up any architecture-specific things, e.g., register vectors.
    223   virtual void SetUpHelpers() {}
    224 
    225   virtual std::vector<Reg*> GetRegisters() = 0;
    226 
    227   // Get the typically used name for this architecture, e.g., aarch64, x86_64, ...
    228   virtual std::string GetArchitectureString() = 0;
    229 
    230   // Get the name of the assembler, e.g., "as" by default.
    231   virtual std::string GetAssemblerCmdName() {
    232     return "as";
    233   }
    234 
    235   // Switches to the assembler command. Default none.
    236   virtual std::string GetAssemblerParameters() {
    237     return "";
    238   }
    239 
    240   // Return the host assembler command for this test.
    241   virtual std::string GetAssemblerCommand() {
    242     // Already resolved it once?
    243     if (resolved_assembler_cmd_.length() != 0) {
    244       return resolved_assembler_cmd_;
    245     }
    246 
    247     std::string line = FindTool(GetAssemblerCmdName());
    248     if (line.length() == 0) {
    249       return line;
    250     }
    251 
    252     resolved_assembler_cmd_ = line + GetAssemblerParameters();
    253 
    254     return line;
    255   }
    256 
    257   // Get the name of the objdump, e.g., "objdump" by default.
    258   virtual std::string GetObjdumpCmdName() {
    259     return "objdump";
    260   }
    261 
    262   // Switches to the objdump command. Default is " -h".
    263   virtual std::string GetObjdumpParameters() {
    264     return " -h";
    265   }
    266 
    267   // Return the host objdump command for this test.
    268   virtual std::string GetObjdumpCommand() {
    269     // Already resolved it once?
    270     if (resolved_objdump_cmd_.length() != 0) {
    271       return resolved_objdump_cmd_;
    272     }
    273 
    274     std::string line = FindTool(GetObjdumpCmdName());
    275     if (line.length() == 0) {
    276       return line;
    277     }
    278 
    279     resolved_objdump_cmd_ = line + GetObjdumpParameters();
    280 
    281     return line;
    282   }
    283 
    284   // Get the name of the objdump, e.g., "objdump" by default.
    285   virtual std::string GetDisassembleCmdName() {
    286     return "objdump";
    287   }
    288 
    289   // Switches to the objdump command. As it's a binary, one needs to push the architecture and
    290   // such to objdump, so it's architecture-specific and there is no default.
    291   virtual std::string GetDisassembleParameters() = 0;
    292 
    293   // Return the host disassembler command for this test.
    294   virtual std::string GetDisassembleCommand() {
    295     // Already resolved it once?
    296     if (resolved_disassemble_cmd_.length() != 0) {
    297       return resolved_disassemble_cmd_;
    298     }
    299 
    300     std::string line = FindTool(GetDisassembleCmdName());
    301     if (line.length() == 0) {
    302       return line;
    303     }
    304 
    305     resolved_disassemble_cmd_ = line + GetDisassembleParameters();
    306 
    307     return line;
    308   }
    309 
    310   // Create a couple of immediate values up to the number of bytes given.
    311   virtual std::vector<int64_t> CreateImmediateValues(size_t imm_bytes) {
    312     std::vector<int64_t> res;
    313     res.push_back(0);
    314     res.push_back(-1);
    315     res.push_back(0x12);
    316     if (imm_bytes >= 2) {
    317       res.push_back(0x1234);
    318       res.push_back(-0x1234);
    319       if (imm_bytes >= 4) {
    320         res.push_back(0x12345678);
    321         res.push_back(-0x12345678);
    322         if (imm_bytes >= 6) {
    323           res.push_back(0x123456789ABC);
    324           res.push_back(-0x123456789ABC);
    325           if (imm_bytes >= 8) {
    326             res.push_back(0x123456789ABCDEF0);
    327             res.push_back(-0x123456789ABCDEF0);
    328           }
    329         }
    330       }
    331     }
    332     return res;
    333   }
    334 
    335   // Create an immediate from the specific value.
    336   virtual Imm* CreateImmediate(int64_t imm_value) = 0;
    337 
    338  private:
    339   // Driver() assembles and compares the results. If the results are not equal and we have a
    340   // disassembler, disassemble both and check whether they have the same mnemonics (in which case
    341   // we just warn).
    342   void Driver(std::string assembly_text, std::string test_name) {
    343     EXPECT_NE(assembly_text.length(), 0U) << "Empty assembly";
    344 
    345     NativeAssemblerResult res;
    346     Compile(assembly_text, &res, test_name);
    347 
    348     EXPECT_TRUE(res.ok) << res.error_msg;
    349     if (!res.ok) {
    350       // No way of continuing.
    351       return;
    352     }
    353 
    354     size_t cs = assembler_->CodeSize();
    355     std::unique_ptr<std::vector<uint8_t>> data(new std::vector<uint8_t>(cs));
    356     MemoryRegion code(&(*data)[0], data->size());
    357     assembler_->FinalizeInstructions(code);
    358 
    359     if (*data == *res.code) {
    360       Clean(&res);
    361     } else {
    362       if (DisassembleBinaries(*data, *res.code, test_name)) {
    363         if (data->size() > res.code->size()) {
    364           // Fail this test with a fancy colored warning being printed.
    365           EXPECT_TRUE(false) << "Assembly code is not identical, but disassembly of machine code "
    366               "is equal: this implies sub-optimal encoding! Our code size=" << data->size() <<
    367               ", gcc size=" << res.code->size();
    368         } else {
    369           // Otherwise just print an info message and clean up.
    370           LOG(INFO) << "GCC chose a different encoding than ours, but the overall length is the "
    371               "same.";
    372           Clean(&res);
    373         }
    374       } else {
    375         // This will output the assembly.
    376         EXPECT_EQ(*data, *res.code) << "Outputs (and disassembly) not identical.";
    377       }
    378     }
    379   }
    380 
    381   // Structure to store intermediates and results.
    382   struct NativeAssemblerResult {
    383     bool ok;
    384     std::string error_msg;
    385     std::string base_name;
    386     std::unique_ptr<std::vector<uint8_t>> code;
    387     uintptr_t length;
    388   };
    389 
    390   // Compile the assembly file from_file to a binary file to_file. Returns true on success.
    391   bool Assemble(const char* from_file, const char* to_file, std::string* error_msg) {
    392     bool have_assembler = FileExists(GetAssemblerCommand());
    393     EXPECT_TRUE(have_assembler) << "Cannot find assembler:" << GetAssemblerCommand();
    394     if (!have_assembler) {
    395       return false;
    396     }
    397 
    398     std::vector<std::string> args;
    399 
    400     args.push_back(GetAssemblerCommand());
    401     args.push_back("-o");
    402     args.push_back(to_file);
    403     args.push_back(from_file);
    404 
    405     return Exec(args, error_msg);
    406   }
    407 
    408   // Runs objdump -h on the binary file and extracts the first line with .text.
    409   // Returns "" on failure.
    410   std::string Objdump(std::string file) {
    411     bool have_objdump = FileExists(GetObjdumpCommand());
    412     EXPECT_TRUE(have_objdump) << "Cannot find objdump: " << GetObjdumpCommand();
    413     if (!have_objdump) {
    414       return "";
    415     }
    416 
    417     std::string error_msg;
    418     std::vector<std::string> args;
    419 
    420     args.push_back(GetObjdumpCommand());
    421     args.push_back(file);
    422     args.push_back(">");
    423     args.push_back(file+".dump");
    424     std::string cmd = Join(args, ' ');
    425 
    426     args.clear();
    427     args.push_back("/bin/sh");
    428     args.push_back("-c");
    429     args.push_back(cmd);
    430 
    431     if (!Exec(args, &error_msg)) {
    432       EXPECT_TRUE(false) << error_msg;
    433     }
    434 
    435     std::ifstream dump(file+".dump");
    436 
    437     std::string line;
    438     bool found = false;
    439     while (std::getline(dump, line)) {
    440       if (line.find(".text") != line.npos) {
    441         found = true;
    442         break;
    443       }
    444     }
    445 
    446     dump.close();
    447 
    448     if (found) {
    449       return line;
    450     } else {
    451       return "";
    452     }
    453   }
    454 
    455   // Disassemble both binaries and compare the text.
    456   bool DisassembleBinaries(std::vector<uint8_t>& data, std::vector<uint8_t>& as,
    457                            std::string test_name) {
    458     std::string disassembler = GetDisassembleCommand();
    459     if (disassembler.length() == 0) {
    460       LOG(WARNING) << "No dissassembler command.";
    461       return false;
    462     }
    463 
    464     std::string data_name = WriteToFile(data, test_name + ".ass");
    465     std::string error_msg;
    466     if (!DisassembleBinary(data_name, &error_msg)) {
    467       LOG(INFO) << "Error disassembling: " << error_msg;
    468       std::remove(data_name.c_str());
    469       return false;
    470     }
    471 
    472     std::string as_name = WriteToFile(as, test_name + ".gcc");
    473     if (!DisassembleBinary(as_name, &error_msg)) {
    474       LOG(INFO) << "Error disassembling: " << error_msg;
    475       std::remove(data_name.c_str());
    476       std::remove((data_name + ".dis").c_str());
    477       std::remove(as_name.c_str());
    478       return false;
    479     }
    480 
    481     bool result = CompareFiles(data_name + ".dis", as_name + ".dis");
    482 
    483     if (result) {
    484       std::remove(data_name.c_str());
    485       std::remove(as_name.c_str());
    486       std::remove((data_name + ".dis").c_str());
    487       std::remove((as_name + ".dis").c_str());
    488     }
    489 
    490     return result;
    491   }
    492 
    493   bool DisassembleBinary(std::string file, std::string* error_msg) {
    494     std::vector<std::string> args;
    495 
    496     args.push_back(GetDisassembleCommand());
    497     args.push_back(file);
    498     args.push_back("| sed -n \'/<.data>/,$p\' | sed -e \'s/.*://\'");
    499     args.push_back(">");
    500     args.push_back(file+".dis");
    501     std::string cmd = Join(args, ' ');
    502 
    503     args.clear();
    504     args.push_back("/bin/sh");
    505     args.push_back("-c");
    506     args.push_back(cmd);
    507 
    508     return Exec(args, error_msg);
    509   }
    510 
    511   std::string WriteToFile(std::vector<uint8_t>& buffer, std::string test_name) {
    512     std::string file_name = GetTmpnam() + std::string("---") + test_name;
    513     const char* data = reinterpret_cast<char*>(buffer.data());
    514     std::ofstream s_out(file_name + ".o");
    515     s_out.write(data, buffer.size());
    516     s_out.close();
    517     return file_name + ".o";
    518   }
    519 
    520   bool CompareFiles(std::string f1, std::string f2) {
    521     std::ifstream f1_in(f1);
    522     std::ifstream f2_in(f2);
    523 
    524     bool result = std::equal(std::istreambuf_iterator<char>(f1_in),
    525                              std::istreambuf_iterator<char>(),
    526                              std::istreambuf_iterator<char>(f2_in));
    527 
    528     f1_in.close();
    529     f2_in.close();
    530 
    531     return result;
    532   }
    533 
    534   // Compile the given assembly code and extract the binary, if possible. Put result into res.
    535   bool Compile(std::string assembly_code, NativeAssemblerResult* res, std::string test_name) {
    536     res->ok = false;
    537     res->code.reset(nullptr);
    538 
    539     res->base_name = GetTmpnam() + std::string("---") + test_name;
    540 
    541     // TODO: Lots of error checking.
    542 
    543     std::ofstream s_out(res->base_name + ".S");
    544     s_out << assembly_code;
    545     s_out.close();
    546 
    547     if (!Assemble((res->base_name + ".S").c_str(), (res->base_name + ".o").c_str(),
    548                   &res->error_msg)) {
    549       res->error_msg = "Could not compile.";
    550       return false;
    551     }
    552 
    553     std::string odump = Objdump(res->base_name + ".o");
    554     if (odump.length() == 0) {
    555       res->error_msg = "Objdump failed.";
    556       return false;
    557     }
    558 
    559     std::istringstream iss(odump);
    560     std::istream_iterator<std::string> start(iss);
    561     std::istream_iterator<std::string> end;
    562     std::vector<std::string> tokens(start, end);
    563 
    564     if (tokens.size() < OBJDUMP_SECTION_LINE_MIN_TOKENS) {
    565       res->error_msg = "Objdump output not recognized: too few tokens.";
    566       return false;
    567     }
    568 
    569     if (tokens[1] != ".text") {
    570       res->error_msg = "Objdump output not recognized: .text not second token.";
    571       return false;
    572     }
    573 
    574     std::string lengthToken = "0x" + tokens[2];
    575     std::istringstream(lengthToken) >> std::hex >> res->length;
    576 
    577     std::string offsetToken = "0x" + tokens[5];
    578     uintptr_t offset;
    579     std::istringstream(offsetToken) >> std::hex >> offset;
    580 
    581     std::ifstream obj(res->base_name + ".o");
    582     obj.seekg(offset);
    583     res->code.reset(new std::vector<uint8_t>(res->length));
    584     obj.read(reinterpret_cast<char*>(&(*res->code)[0]), res->length);
    585     obj.close();
    586 
    587     res->ok = true;
    588     return true;
    589   }
    590 
    591   // Remove temporary files.
    592   void Clean(const NativeAssemblerResult* res) {
    593     std::remove((res->base_name + ".S").c_str());
    594     std::remove((res->base_name + ".o").c_str());
    595     std::remove((res->base_name + ".o.dump").c_str());
    596   }
    597 
    598   // Check whether file exists. Is used for commands, so strips off any parameters: anything after
    599   // the first space. We skip to the last slash for this, so it should work with directories with
    600   // spaces.
    601   static bool FileExists(std::string file) {
    602     if (file.length() == 0) {
    603       return false;
    604     }
    605 
    606     // Need to strip any options.
    607     size_t last_slash = file.find_last_of('/');
    608     if (last_slash == std::string::npos) {
    609       // No slash, start looking at the start.
    610       last_slash = 0;
    611     }
    612     size_t space_index = file.find(' ', last_slash);
    613 
    614     if (space_index == std::string::npos) {
    615       std::ifstream infile(file.c_str());
    616       return infile.good();
    617     } else {
    618       std::string copy = file.substr(0, space_index - 1);
    619 
    620       struct stat buf;
    621       return stat(copy.c_str(), &buf) == 0;
    622     }
    623   }
    624 
    625   static std::string GetGCCRootPath() {
    626     return "prebuilts/gcc/linux-x86";
    627   }
    628 
    629   static std::string GetRootPath() {
    630     // 1) Check ANDROID_BUILD_TOP
    631     char* build_top = getenv("ANDROID_BUILD_TOP");
    632     if (build_top != nullptr) {
    633       return std::string(build_top) + "/";
    634     }
    635 
    636     // 2) Do cwd
    637     char temp[1024];
    638     return getcwd(temp, 1024) ? std::string(temp) + "/" : std::string("");
    639   }
    640 
    641   std::string FindTool(std::string tool_name) {
    642     // Find the current tool. Wild-card pattern is "arch-string*tool-name".
    643     std::string gcc_path = GetRootPath() + GetGCCRootPath();
    644     std::vector<std::string> args;
    645     args.push_back("find");
    646     args.push_back(gcc_path);
    647     args.push_back("-name");
    648     args.push_back(GetArchitectureString() + "*" + tool_name);
    649     args.push_back("|");
    650     args.push_back("sort");
    651     args.push_back("|");
    652     args.push_back("tail");
    653     args.push_back("-n");
    654     args.push_back("1");
    655     std::string tmp_file = GetTmpnam();
    656     args.push_back(">");
    657     args.push_back(tmp_file);
    658     std::string sh_args = Join(args, ' ');
    659 
    660     args.clear();
    661     args.push_back("/bin/sh");
    662     args.push_back("-c");
    663     args.push_back(sh_args);
    664 
    665     std::string error_msg;
    666     if (!Exec(args, &error_msg)) {
    667       EXPECT_TRUE(false) << error_msg;
    668       return "";
    669     }
    670 
    671     std::ifstream in(tmp_file.c_str());
    672     std::string line;
    673     if (!std::getline(in, line)) {
    674       in.close();
    675       std::remove(tmp_file.c_str());
    676       return "";
    677     }
    678     in.close();
    679     std::remove(tmp_file.c_str());
    680     return line;
    681   }
    682 
    683   // Use a consistent tmpnam, so store it.
    684   std::string GetTmpnam() {
    685     if (tmpnam_.length() == 0) {
    686       ScratchFile tmp;
    687       tmpnam_ = tmp.GetFilename() + "asm";
    688     }
    689     return tmpnam_;
    690   }
    691 
    692   std::unique_ptr<Ass> assembler_;
    693 
    694   std::string resolved_assembler_cmd_;
    695   std::string resolved_objdump_cmd_;
    696   std::string resolved_disassemble_cmd_;
    697 
    698   std::string android_data_;
    699 
    700   static constexpr size_t OBJDUMP_SECTION_LINE_MIN_TOKENS = 6;
    701 };
    702 
    703 }  // namespace art
    704 
    705 #endif  // ART_COMPILER_UTILS_ASSEMBLER_TEST_H_
    706