• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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_BASE_H_
18 #define ART_COMPILER_UTILS_ASSEMBLER_TEST_BASE_H_
19 
20 #include <cstdio>
21 #include <cstdlib>
22 #include <fstream>
23 #include <iterator>
24 #include <sys/stat.h>
25 
26 #include "common_runtime_test.h"  // For ScratchFile
27 #include "utils.h"
28 
29 namespace art {
30 
31 // If you want to take a look at the differences between the ART assembler and GCC, set this flag
32 // to true. The disassembled files will then remain in the tmp directory.
33 static constexpr bool kKeepDisassembledFiles = false;
34 
35 // Use a glocal static variable to keep the same name for all test data. Else we'll just spam the
36 // temp directory.
37 static std::string tmpnam_;
38 
39 // We put this into a class as gtests are self-contained, so this helper needs to be in an h-file.
40 class AssemblerTestInfrastructure {
41  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)42   AssemblerTestInfrastructure(std::string architecture,
43                               std::string as,
44                               std::string as_params,
45                               std::string objdump,
46                               std::string objdump_params,
47                               std::string disasm,
48                               std::string disasm_params,
49                               const char* asm_header) :
50       architecture_string_(architecture),
51       asm_header_(asm_header),
52       assembler_cmd_name_(as),
53       assembler_parameters_(as_params),
54       objdump_cmd_name_(objdump),
55       objdump_parameters_(objdump_params),
56       disassembler_cmd_name_(disasm),
57       disassembler_parameters_(disasm_params) {
58     // Fake a runtime test for ScratchFile
59     CommonRuntimeTest::SetUpAndroidData(android_data_);
60   }
61 
~AssemblerTestInfrastructure()62   virtual ~AssemblerTestInfrastructure() {
63     // We leave temporaries in case this failed so we can debug issues.
64     CommonRuntimeTest::TearDownAndroidData(android_data_, false);
65     tmpnam_ = "";
66   }
67 
68   // This is intended to be run as a test.
CheckTools()69   bool CheckTools() {
70     std::string asm_tool = FindTool(assembler_cmd_name_);
71     if (!FileExists(asm_tool)) {
72       LOG(ERROR) << "Could not find assembler from " << assembler_cmd_name_;
73       LOG(ERROR) << "FindTool returned " << asm_tool;
74       FindToolDump(assembler_cmd_name_);
75       return false;
76     }
77     LOG(INFO) << "Chosen assembler command: " << GetAssemblerCommand();
78 
79     std::string objdump_tool = FindTool(objdump_cmd_name_);
80     if (!FileExists(objdump_tool)) {
81       LOG(ERROR) << "Could not find objdump from " << objdump_cmd_name_;
82       LOG(ERROR) << "FindTool returned " << objdump_tool;
83       FindToolDump(objdump_cmd_name_);
84       return false;
85     }
86     LOG(INFO) << "Chosen objdump command: " << GetObjdumpCommand();
87 
88     // Disassembly is optional.
89     std::string disassembler = GetDisassembleCommand();
90     if (disassembler.length() != 0) {
91       std::string disassembler_tool = FindTool(disassembler_cmd_name_);
92       if (!FileExists(disassembler_tool)) {
93         LOG(ERROR) << "Could not find disassembler from " << disassembler_cmd_name_;
94         LOG(ERROR) << "FindTool returned " << disassembler_tool;
95         FindToolDump(disassembler_cmd_name_);
96         return false;
97       }
98       LOG(INFO) << "Chosen disassemble command: " << GetDisassembleCommand();
99     } else {
100       LOG(INFO) << "No disassembler given.";
101     }
102 
103     return true;
104   }
105 
106   // Driver() assembles and compares the results. If the results are not equal and we have a
107   // disassembler, disassemble both and check whether they have the same mnemonics (in which case
108   // we just warn).
Driver(const std::vector<uint8_t> & data,std::string assembly_text,std::string test_name)109   void Driver(const std::vector<uint8_t>& data, std::string assembly_text, std::string test_name) {
110     EXPECT_NE(assembly_text.length(), 0U) << "Empty assembly";
111 
112     NativeAssemblerResult res;
113     Compile(assembly_text, &res, test_name);
114 
115     EXPECT_TRUE(res.ok) << res.error_msg;
116     if (!res.ok) {
117       // No way of continuing.
118       return;
119     }
120 
121     if (data == *res.code) {
122       Clean(&res);
123     } else {
124       if (DisassembleBinaries(data, *res.code, test_name)) {
125         if (data.size() > res.code->size()) {
126           // Fail this test with a fancy colored warning being printed.
127           EXPECT_TRUE(false) << "Assembly code is not identical, but disassembly of machine code "
128               "is equal: this implies sub-optimal encoding! Our code size=" << data.size() <<
129               ", gcc size=" << res.code->size();
130         } else {
131           // Otherwise just print an info message and clean up.
132           LOG(INFO) << "GCC chose a different encoding than ours, but the overall length is the "
133               "same.";
134           Clean(&res);
135         }
136       } else {
137         // This will output the assembly.
138         EXPECT_EQ(*res.code, data) << "Outputs (and disassembly) not identical.";
139       }
140     }
141   }
142 
143  protected:
144   // Return the host assembler command for this test.
GetAssemblerCommand()145   virtual std::string GetAssemblerCommand() {
146     // Already resolved it once?
147     if (resolved_assembler_cmd_.length() != 0) {
148       return resolved_assembler_cmd_;
149     }
150 
151     std::string line = FindTool(assembler_cmd_name_);
152     if (line.length() == 0) {
153       return line;
154     }
155 
156     resolved_assembler_cmd_ = line + assembler_parameters_;
157 
158     return resolved_assembler_cmd_;
159   }
160 
161   // Return the host objdump command for this test.
GetObjdumpCommand()162   virtual std::string GetObjdumpCommand() {
163     // Already resolved it once?
164     if (resolved_objdump_cmd_.length() != 0) {
165       return resolved_objdump_cmd_;
166     }
167 
168     std::string line = FindTool(objdump_cmd_name_);
169     if (line.length() == 0) {
170       return line;
171     }
172 
173     resolved_objdump_cmd_ = line + objdump_parameters_;
174 
175     return resolved_objdump_cmd_;
176   }
177 
178   // Return the host disassembler command for this test.
GetDisassembleCommand()179   virtual std::string GetDisassembleCommand() {
180     // Already resolved it once?
181     if (resolved_disassemble_cmd_.length() != 0) {
182       return resolved_disassemble_cmd_;
183     }
184 
185     std::string line = FindTool(disassembler_cmd_name_);
186     if (line.length() == 0) {
187       return line;
188     }
189 
190     resolved_disassemble_cmd_ = line + disassembler_parameters_;
191 
192     return resolved_disassemble_cmd_;
193   }
194 
195  private:
196   // Structure to store intermediates and results.
197   struct NativeAssemblerResult {
198     bool ok;
199     std::string error_msg;
200     std::string base_name;
201     std::unique_ptr<std::vector<uint8_t>> code;
202     uintptr_t length;
203   };
204 
205   // Compile the assembly file from_file to a binary file to_file. Returns true on success.
Assemble(const char * from_file,const char * to_file,std::string * error_msg)206   bool Assemble(const char* from_file, const char* to_file, std::string* error_msg) {
207     bool have_assembler = FileExists(FindTool(assembler_cmd_name_));
208     EXPECT_TRUE(have_assembler) << "Cannot find assembler:" << GetAssemblerCommand();
209     if (!have_assembler) {
210       return false;
211     }
212 
213     std::vector<std::string> args;
214 
215     // Encaspulate the whole command line in a single string passed to
216     // the shell, so that GetAssemblerCommand() may contain arguments
217     // in addition to the program name.
218     args.push_back(GetAssemblerCommand());
219     args.push_back("-o");
220     args.push_back(to_file);
221     args.push_back(from_file);
222     std::string cmd = Join(args, ' ');
223 
224     args.clear();
225     args.push_back("/bin/sh");
226     args.push_back("-c");
227     args.push_back(cmd);
228 
229     bool success = Exec(args, error_msg);
230     if (!success) {
231       LOG(ERROR) << "Assembler command line:";
232       for (std::string arg : args) {
233         LOG(ERROR) << arg;
234       }
235     }
236     return success;
237   }
238 
239   // Runs objdump -h on the binary file and extracts the first line with .text.
240   // Returns "" on failure.
Objdump(std::string file)241   std::string Objdump(std::string file) {
242     bool have_objdump = FileExists(FindTool(objdump_cmd_name_));
243     EXPECT_TRUE(have_objdump) << "Cannot find objdump: " << GetObjdumpCommand();
244     if (!have_objdump) {
245       return "";
246     }
247 
248     std::string error_msg;
249     std::vector<std::string> args;
250 
251     // Encaspulate the whole command line in a single string passed to
252     // the shell, so that GetObjdumpCommand() may contain arguments
253     // in addition to the program name.
254     args.push_back(GetObjdumpCommand());
255     args.push_back(file);
256     args.push_back(">");
257     args.push_back(file+".dump");
258     std::string cmd = Join(args, ' ');
259 
260     args.clear();
261     args.push_back("/bin/sh");
262     args.push_back("-c");
263     args.push_back(cmd);
264 
265     if (!Exec(args, &error_msg)) {
266       EXPECT_TRUE(false) << error_msg;
267     }
268 
269     std::ifstream dump(file+".dump");
270 
271     std::string line;
272     bool found = false;
273     while (std::getline(dump, line)) {
274       if (line.find(".text") != line.npos) {
275         found = true;
276         break;
277       }
278     }
279 
280     dump.close();
281 
282     if (found) {
283       return line;
284     } else {
285       return "";
286     }
287   }
288 
289   // Disassemble both binaries and compare the text.
DisassembleBinaries(const std::vector<uint8_t> & data,const std::vector<uint8_t> & as,std::string test_name)290   bool DisassembleBinaries(const std::vector<uint8_t>& data, const std::vector<uint8_t>& as,
291                            std::string test_name) {
292     std::string disassembler = GetDisassembleCommand();
293     if (disassembler.length() == 0) {
294       LOG(WARNING) << "No dissassembler command.";
295       return false;
296     }
297 
298     std::string data_name = WriteToFile(data, test_name + ".ass");
299     std::string error_msg;
300     if (!DisassembleBinary(data_name, &error_msg)) {
301       LOG(INFO) << "Error disassembling: " << error_msg;
302       std::remove(data_name.c_str());
303       return false;
304     }
305 
306     std::string as_name = WriteToFile(as, test_name + ".gcc");
307     if (!DisassembleBinary(as_name, &error_msg)) {
308       LOG(INFO) << "Error disassembling: " << error_msg;
309       std::remove(data_name.c_str());
310       std::remove((data_name + ".dis").c_str());
311       std::remove(as_name.c_str());
312       return false;
313     }
314 
315     bool result = CompareFiles(data_name + ".dis", as_name + ".dis");
316 
317     if (!kKeepDisassembledFiles) {
318       std::remove(data_name.c_str());
319       std::remove(as_name.c_str());
320       std::remove((data_name + ".dis").c_str());
321       std::remove((as_name + ".dis").c_str());
322     }
323 
324     return result;
325   }
326 
DisassembleBinary(std::string file,std::string * error_msg)327   bool DisassembleBinary(std::string file, std::string* error_msg) {
328     std::vector<std::string> args;
329 
330     // Encaspulate the whole command line in a single string passed to
331     // the shell, so that GetDisassembleCommand() may contain arguments
332     // in addition to the program name.
333     args.push_back(GetDisassembleCommand());
334     args.push_back(file);
335     args.push_back("| sed -n \'/<.data>/,$p\' | sed -e \'s/.*://\'");
336     args.push_back(">");
337     args.push_back(file+".dis");
338     std::string cmd = Join(args, ' ');
339 
340     args.clear();
341     args.push_back("/bin/sh");
342     args.push_back("-c");
343     args.push_back(cmd);
344 
345     return Exec(args, error_msg);
346   }
347 
WriteToFile(const std::vector<uint8_t> & buffer,std::string test_name)348   std::string WriteToFile(const std::vector<uint8_t>& buffer, std::string test_name) {
349     std::string file_name = GetTmpnam() + std::string("---") + test_name;
350     const char* data = reinterpret_cast<const char*>(buffer.data());
351     std::ofstream s_out(file_name + ".o");
352     s_out.write(data, buffer.size());
353     s_out.close();
354     return file_name + ".o";
355   }
356 
CompareFiles(std::string f1,std::string f2)357   bool CompareFiles(std::string f1, std::string f2) {
358     std::ifstream f1_in(f1);
359     std::ifstream f2_in(f2);
360 
361     bool result = std::equal(std::istreambuf_iterator<char>(f1_in),
362                              std::istreambuf_iterator<char>(),
363                              std::istreambuf_iterator<char>(f2_in));
364 
365     f1_in.close();
366     f2_in.close();
367 
368     return result;
369   }
370 
371   // Compile the given assembly code and extract the binary, if possible. Put result into res.
Compile(std::string assembly_code,NativeAssemblerResult * res,std::string test_name)372   bool Compile(std::string assembly_code, NativeAssemblerResult* res, std::string test_name) {
373     res->ok = false;
374     res->code.reset(nullptr);
375 
376     res->base_name = GetTmpnam() + std::string("---") + test_name;
377 
378     // TODO: Lots of error checking.
379 
380     std::ofstream s_out(res->base_name + ".S");
381     if (asm_header_ != nullptr) {
382       s_out << asm_header_;
383     }
384     s_out << assembly_code;
385     s_out.close();
386 
387     if (!Assemble((res->base_name + ".S").c_str(), (res->base_name + ".o").c_str(),
388                   &res->error_msg)) {
389       res->error_msg = "Could not compile.";
390       return false;
391     }
392 
393     std::string odump = Objdump(res->base_name + ".o");
394     if (odump.length() == 0) {
395       res->error_msg = "Objdump failed.";
396       return false;
397     }
398 
399     std::istringstream iss(odump);
400     std::istream_iterator<std::string> start(iss);
401     std::istream_iterator<std::string> end;
402     std::vector<std::string> tokens(start, end);
403 
404     if (tokens.size() < OBJDUMP_SECTION_LINE_MIN_TOKENS) {
405       res->error_msg = "Objdump output not recognized: too few tokens.";
406       return false;
407     }
408 
409     if (tokens[1] != ".text") {
410       res->error_msg = "Objdump output not recognized: .text not second token.";
411       return false;
412     }
413 
414     std::string lengthToken = "0x" + tokens[2];
415     std::istringstream(lengthToken) >> std::hex >> res->length;
416 
417     std::string offsetToken = "0x" + tokens[5];
418     uintptr_t offset;
419     std::istringstream(offsetToken) >> std::hex >> offset;
420 
421     std::ifstream obj(res->base_name + ".o");
422     obj.seekg(offset);
423     res->code.reset(new std::vector<uint8_t>(res->length));
424     obj.read(reinterpret_cast<char*>(&(*res->code)[0]), res->length);
425     obj.close();
426 
427     res->ok = true;
428     return true;
429   }
430 
431   // Remove temporary files.
Clean(const NativeAssemblerResult * res)432   void Clean(const NativeAssemblerResult* res) {
433     std::remove((res->base_name + ".S").c_str());
434     std::remove((res->base_name + ".o").c_str());
435     std::remove((res->base_name + ".o.dump").c_str());
436   }
437 
438   // Check whether file exists. Is used for commands, so strips off any parameters: anything after
439   // the first space. We skip to the last slash for this, so it should work with directories with
440   // spaces.
FileExists(std::string file)441   static bool FileExists(std::string file) {
442     if (file.length() == 0) {
443       return false;
444     }
445 
446     // Need to strip any options.
447     size_t last_slash = file.find_last_of('/');
448     if (last_slash == std::string::npos) {
449       // No slash, start looking at the start.
450       last_slash = 0;
451     }
452     size_t space_index = file.find(' ', last_slash);
453 
454     if (space_index == std::string::npos) {
455       std::ifstream infile(file.c_str());
456       return infile.good();
457     } else {
458       std::string copy = file.substr(0, space_index - 1);
459 
460       struct stat buf;
461       return stat(copy.c_str(), &buf) == 0;
462     }
463   }
464 
GetGCCRootPath()465   static std::string GetGCCRootPath() {
466     return "prebuilts/gcc/linux-x86";
467   }
468 
GetRootPath()469   static std::string GetRootPath() {
470     // 1) Check ANDROID_BUILD_TOP
471     char* build_top = getenv("ANDROID_BUILD_TOP");
472     if (build_top != nullptr) {
473       return std::string(build_top) + "/";
474     }
475 
476     // 2) Do cwd
477     char temp[1024];
478     return getcwd(temp, 1024) ? std::string(temp) + "/" : std::string("");
479   }
480 
FindTool(std::string tool_name)481   std::string FindTool(std::string tool_name) {
482     // Find the current tool. Wild-card pattern is "arch-string*tool-name".
483     std::string gcc_path = GetRootPath() + GetGCCRootPath();
484     std::vector<std::string> args;
485     args.push_back("find");
486     args.push_back(gcc_path);
487     args.push_back("-name");
488     args.push_back(architecture_string_ + "*" + tool_name);
489     args.push_back("|");
490     args.push_back("sort");
491     args.push_back("|");
492     args.push_back("tail");
493     args.push_back("-n");
494     args.push_back("1");
495     std::string tmp_file = GetTmpnam();
496     args.push_back(">");
497     args.push_back(tmp_file);
498     std::string sh_args = Join(args, ' ');
499 
500     args.clear();
501     args.push_back("/bin/sh");
502     args.push_back("-c");
503     args.push_back(sh_args);
504 
505     std::string error_msg;
506     if (!Exec(args, &error_msg)) {
507       EXPECT_TRUE(false) << error_msg;
508       UNREACHABLE();
509     }
510 
511     std::ifstream in(tmp_file.c_str());
512     std::string line;
513     if (!std::getline(in, line)) {
514       in.close();
515       std::remove(tmp_file.c_str());
516       return "";
517     }
518     in.close();
519     std::remove(tmp_file.c_str());
520     return line;
521   }
522 
523   // Helper for below. If name_predicate is empty, search for all files, otherwise use it for the
524   // "-name" option.
FindToolDumpPrintout(std::string name_predicate,std::string tmp_file)525   static void FindToolDumpPrintout(std::string name_predicate, std::string tmp_file) {
526     std::string gcc_path = GetRootPath() + GetGCCRootPath();
527     std::vector<std::string> args;
528     args.push_back("find");
529     args.push_back(gcc_path);
530     if (!name_predicate.empty()) {
531       args.push_back("-name");
532       args.push_back(name_predicate);
533     }
534     args.push_back("|");
535     args.push_back("sort");
536     args.push_back(">");
537     args.push_back(tmp_file);
538     std::string sh_args = Join(args, ' ');
539 
540     args.clear();
541     args.push_back("/bin/sh");
542     args.push_back("-c");
543     args.push_back(sh_args);
544 
545     std::string error_msg;
546     if (!Exec(args, &error_msg)) {
547       EXPECT_TRUE(false) << error_msg;
548       UNREACHABLE();
549     }
550 
551     LOG(ERROR) << "FindToolDump: gcc_path=" << gcc_path
552                << " cmd=" << sh_args;
553     std::ifstream in(tmp_file.c_str());
554     if (in) {
555       std::string line;
556       while (std::getline(in, line)) {
557         LOG(ERROR) << line;
558       }
559     }
560     in.close();
561     std::remove(tmp_file.c_str());
562   }
563 
564   // For debug purposes.
FindToolDump(std::string tool_name)565   void FindToolDump(std::string tool_name) {
566     // Check with the tool name.
567     FindToolDumpPrintout(architecture_string_ + "*" + tool_name, GetTmpnam());
568     FindToolDumpPrintout("", GetTmpnam());
569   }
570 
571   // Use a consistent tmpnam, so store it.
GetTmpnam()572   std::string GetTmpnam() {
573     if (tmpnam_.length() == 0) {
574       ScratchFile tmp;
575       tmpnam_ = tmp.GetFilename() + "asm";
576     }
577     return tmpnam_;
578   }
579 
580   static constexpr size_t OBJDUMP_SECTION_LINE_MIN_TOKENS = 6;
581 
582   std::string architecture_string_;
583   const char* asm_header_;
584 
585   std::string assembler_cmd_name_;
586   std::string assembler_parameters_;
587 
588   std::string objdump_cmd_name_;
589   std::string objdump_parameters_;
590 
591   std::string disassembler_cmd_name_;
592   std::string disassembler_parameters_;
593 
594   std::string resolved_assembler_cmd_;
595   std::string resolved_objdump_cmd_;
596   std::string resolved_disassemble_cmd_;
597 
598   std::string android_data_;
599 
600   DISALLOW_COPY_AND_ASSIGN(AssemblerTestInfrastructure);
601 };
602 
603 }  // namespace art
604 
605 #endif  // ART_COMPILER_UTILS_ASSEMBLER_TEST_BASE_H_
606