blob: ac24ee95eb42cadba6d961106320bf3d3d8b85e0 [file] [log] [blame]
Andreas Gampe03b9ee42015-04-24 21:41:45 -07001/*
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
Andreas Gampe03b9ee42015-04-24 21:41:45 -070020#include <cstdio>
21#include <cstdlib>
22#include <fstream>
23#include <iterator>
24#include <sys/stat.h>
25
Vladimir Marko80afd022015-05-19 18:08:00 +010026#include "common_runtime_test.h" // For ScratchFile
27#include "utils.h"
28
Andreas Gampe03b9ee42015-04-24 21:41:45 -070029namespace 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.
33static 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.
37static 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.
40class AssemblerTestInfrastructure {
41 public:
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
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.
69 bool CheckTools() {
Andreas Gampef3e07062015-10-06 09:05:10 -070070 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_);
Andreas Gampe03b9ee42015-04-24 21:41:45 -070075 return false;
76 }
77 LOG(INFO) << "Chosen assembler command: " << GetAssemblerCommand();
78
Andreas Gampef3e07062015-10-06 09:05:10 -070079 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_);
Andreas Gampe03b9ee42015-04-24 21:41:45 -070084 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) {
Andreas Gampef3e07062015-10-06 09:05:10 -070091 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_);
Andreas Gampe03b9ee42015-04-24 21:41:45 -070096 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).
Andreas Gampe2e965ac2016-11-03 17:24:15 -0700109 void Driver(const std::vector<uint8_t>& data,
110 const std::string& assembly_text,
111 const std::string& test_name) {
Andreas Gampe03b9ee42015-04-24 21:41:45 -0700112 EXPECT_NE(assembly_text.length(), 0U) << "Empty assembly";
113
114 NativeAssemblerResult res;
115 Compile(assembly_text, &res, test_name);
116
117 EXPECT_TRUE(res.ok) << res.error_msg;
118 if (!res.ok) {
119 // No way of continuing.
120 return;
121 }
122
123 if (data == *res.code) {
124 Clean(&res);
125 } else {
126 if (DisassembleBinaries(data, *res.code, test_name)) {
127 if (data.size() > res.code->size()) {
128 // Fail this test with a fancy colored warning being printed.
129 EXPECT_TRUE(false) << "Assembly code is not identical, but disassembly of machine code "
130 "is equal: this implies sub-optimal encoding! Our code size=" << data.size() <<
131 ", gcc size=" << res.code->size();
132 } else {
133 // Otherwise just print an info message and clean up.
134 LOG(INFO) << "GCC chose a different encoding than ours, but the overall length is the "
135 "same.";
136 Clean(&res);
137 }
138 } else {
139 // This will output the assembly.
140 EXPECT_EQ(*res.code, data) << "Outputs (and disassembly) not identical.";
141 }
142 }
143 }
144
145 protected:
146 // Return the host assembler command for this test.
147 virtual std::string GetAssemblerCommand() {
148 // Already resolved it once?
149 if (resolved_assembler_cmd_.length() != 0) {
150 return resolved_assembler_cmd_;
151 }
152
153 std::string line = FindTool(assembler_cmd_name_);
154 if (line.length() == 0) {
155 return line;
156 }
157
158 resolved_assembler_cmd_ = line + assembler_parameters_;
159
160 return resolved_assembler_cmd_;
161 }
162
163 // Return the host objdump command for this test.
164 virtual std::string GetObjdumpCommand() {
165 // Already resolved it once?
166 if (resolved_objdump_cmd_.length() != 0) {
167 return resolved_objdump_cmd_;
168 }
169
170 std::string line = FindTool(objdump_cmd_name_);
171 if (line.length() == 0) {
172 return line;
173 }
174
175 resolved_objdump_cmd_ = line + objdump_parameters_;
176
177 return resolved_objdump_cmd_;
178 }
179
180 // Return the host disassembler command for this test.
181 virtual std::string GetDisassembleCommand() {
182 // Already resolved it once?
183 if (resolved_disassemble_cmd_.length() != 0) {
184 return resolved_disassemble_cmd_;
185 }
186
187 std::string line = FindTool(disassembler_cmd_name_);
188 if (line.length() == 0) {
189 return line;
190 }
191
192 resolved_disassemble_cmd_ = line + disassembler_parameters_;
193
194 return resolved_disassemble_cmd_;
195 }
196
197 private:
198 // Structure to store intermediates and results.
199 struct NativeAssemblerResult {
200 bool ok;
201 std::string error_msg;
202 std::string base_name;
203 std::unique_ptr<std::vector<uint8_t>> code;
204 uintptr_t length;
205 };
206
207 // Compile the assembly file from_file to a binary file to_file. Returns true on success.
208 bool Assemble(const char* from_file, const char* to_file, std::string* error_msg) {
209 bool have_assembler = FileExists(FindTool(assembler_cmd_name_));
210 EXPECT_TRUE(have_assembler) << "Cannot find assembler:" << GetAssemblerCommand();
211 if (!have_assembler) {
212 return false;
213 }
214
215 std::vector<std::string> args;
216
217 // Encaspulate the whole command line in a single string passed to
218 // the shell, so that GetAssemblerCommand() may contain arguments
219 // in addition to the program name.
220 args.push_back(GetAssemblerCommand());
221 args.push_back("-o");
222 args.push_back(to_file);
223 args.push_back(from_file);
224 std::string cmd = Join(args, ' ');
225
226 args.clear();
227 args.push_back("/bin/sh");
228 args.push_back("-c");
229 args.push_back(cmd);
230
231 bool success = Exec(args, error_msg);
232 if (!success) {
Nicolas Geoffrayd56376c2015-05-21 12:32:34 +0000233 LOG(ERROR) << "Assembler command line:";
Andreas Gampe2e965ac2016-11-03 17:24:15 -0700234 for (const std::string& arg : args) {
Nicolas Geoffrayd56376c2015-05-21 12:32:34 +0000235 LOG(ERROR) << arg;
Andreas Gampe03b9ee42015-04-24 21:41:45 -0700236 }
237 }
238 return success;
239 }
240
241 // Runs objdump -h on the binary file and extracts the first line with .text.
242 // Returns "" on failure.
Andreas Gampe2e965ac2016-11-03 17:24:15 -0700243 std::string Objdump(const std::string& file) {
Andreas Gampe03b9ee42015-04-24 21:41:45 -0700244 bool have_objdump = FileExists(FindTool(objdump_cmd_name_));
245 EXPECT_TRUE(have_objdump) << "Cannot find objdump: " << GetObjdumpCommand();
246 if (!have_objdump) {
247 return "";
248 }
249
250 std::string error_msg;
251 std::vector<std::string> args;
252
253 // Encaspulate the whole command line in a single string passed to
254 // the shell, so that GetObjdumpCommand() may contain arguments
255 // in addition to the program name.
256 args.push_back(GetObjdumpCommand());
257 args.push_back(file);
258 args.push_back(">");
259 args.push_back(file+".dump");
260 std::string cmd = Join(args, ' ');
261
262 args.clear();
263 args.push_back("/bin/sh");
264 args.push_back("-c");
265 args.push_back(cmd);
266
267 if (!Exec(args, &error_msg)) {
268 EXPECT_TRUE(false) << error_msg;
269 }
270
271 std::ifstream dump(file+".dump");
272
273 std::string line;
274 bool found = false;
275 while (std::getline(dump, line)) {
276 if (line.find(".text") != line.npos) {
277 found = true;
278 break;
279 }
280 }
281
282 dump.close();
283
284 if (found) {
285 return line;
286 } else {
287 return "";
288 }
289 }
290
291 // Disassemble both binaries and compare the text.
Andreas Gampe2e965ac2016-11-03 17:24:15 -0700292 bool DisassembleBinaries(const std::vector<uint8_t>& data,
293 const std::vector<uint8_t>& as,
294 const std::string& test_name) {
Andreas Gampe03b9ee42015-04-24 21:41:45 -0700295 std::string disassembler = GetDisassembleCommand();
296 if (disassembler.length() == 0) {
297 LOG(WARNING) << "No dissassembler command.";
298 return false;
299 }
300
301 std::string data_name = WriteToFile(data, test_name + ".ass");
302 std::string error_msg;
303 if (!DisassembleBinary(data_name, &error_msg)) {
304 LOG(INFO) << "Error disassembling: " << error_msg;
305 std::remove(data_name.c_str());
306 return false;
307 }
308
309 std::string as_name = WriteToFile(as, test_name + ".gcc");
310 if (!DisassembleBinary(as_name, &error_msg)) {
311 LOG(INFO) << "Error disassembling: " << error_msg;
312 std::remove(data_name.c_str());
313 std::remove((data_name + ".dis").c_str());
314 std::remove(as_name.c_str());
315 return false;
316 }
317
318 bool result = CompareFiles(data_name + ".dis", as_name + ".dis");
319
320 if (!kKeepDisassembledFiles) {
321 std::remove(data_name.c_str());
322 std::remove(as_name.c_str());
323 std::remove((data_name + ".dis").c_str());
324 std::remove((as_name + ".dis").c_str());
325 }
326
327 return result;
328 }
329
Andreas Gampe2e965ac2016-11-03 17:24:15 -0700330 bool DisassembleBinary(const std::string& file, std::string* error_msg) {
Andreas Gampe03b9ee42015-04-24 21:41:45 -0700331 std::vector<std::string> args;
332
333 // Encaspulate the whole command line in a single string passed to
334 // the shell, so that GetDisassembleCommand() may contain arguments
335 // in addition to the program name.
336 args.push_back(GetDisassembleCommand());
337 args.push_back(file);
338 args.push_back("| sed -n \'/<.data>/,$p\' | sed -e \'s/.*://\'");
339 args.push_back(">");
340 args.push_back(file+".dis");
341 std::string cmd = Join(args, ' ');
342
343 args.clear();
344 args.push_back("/bin/sh");
345 args.push_back("-c");
346 args.push_back(cmd);
347
348 return Exec(args, error_msg);
349 }
350
Andreas Gampe2e965ac2016-11-03 17:24:15 -0700351 std::string WriteToFile(const std::vector<uint8_t>& buffer, const std::string& test_name) {
Andreas Gampe03b9ee42015-04-24 21:41:45 -0700352 std::string file_name = GetTmpnam() + std::string("---") + test_name;
353 const char* data = reinterpret_cast<const char*>(buffer.data());
354 std::ofstream s_out(file_name + ".o");
355 s_out.write(data, buffer.size());
356 s_out.close();
357 return file_name + ".o";
358 }
359
Andreas Gampe2e965ac2016-11-03 17:24:15 -0700360 bool CompareFiles(const std::string& f1, const std::string& f2) {
Andreas Gampe03b9ee42015-04-24 21:41:45 -0700361 std::ifstream f1_in(f1);
362 std::ifstream f2_in(f2);
363
364 bool result = std::equal(std::istreambuf_iterator<char>(f1_in),
365 std::istreambuf_iterator<char>(),
366 std::istreambuf_iterator<char>(f2_in));
367
368 f1_in.close();
369 f2_in.close();
370
371 return result;
372 }
373
374 // Compile the given assembly code and extract the binary, if possible. Put result into res.
Andreas Gampe2e965ac2016-11-03 17:24:15 -0700375 bool Compile(const std::string& assembly_code,
376 NativeAssemblerResult* res,
377 const std::string& test_name) {
Andreas Gampe03b9ee42015-04-24 21:41:45 -0700378 res->ok = false;
379 res->code.reset(nullptr);
380
381 res->base_name = GetTmpnam() + std::string("---") + test_name;
382
383 // TODO: Lots of error checking.
384
385 std::ofstream s_out(res->base_name + ".S");
386 if (asm_header_ != nullptr) {
387 s_out << asm_header_;
388 }
389 s_out << assembly_code;
390 s_out.close();
391
392 if (!Assemble((res->base_name + ".S").c_str(), (res->base_name + ".o").c_str(),
393 &res->error_msg)) {
394 res->error_msg = "Could not compile.";
395 return false;
396 }
397
398 std::string odump = Objdump(res->base_name + ".o");
399 if (odump.length() == 0) {
400 res->error_msg = "Objdump failed.";
401 return false;
402 }
403
404 std::istringstream iss(odump);
405 std::istream_iterator<std::string> start(iss);
406 std::istream_iterator<std::string> end;
407 std::vector<std::string> tokens(start, end);
408
409 if (tokens.size() < OBJDUMP_SECTION_LINE_MIN_TOKENS) {
410 res->error_msg = "Objdump output not recognized: too few tokens.";
411 return false;
412 }
413
414 if (tokens[1] != ".text") {
415 res->error_msg = "Objdump output not recognized: .text not second token.";
416 return false;
417 }
418
419 std::string lengthToken = "0x" + tokens[2];
420 std::istringstream(lengthToken) >> std::hex >> res->length;
421
422 std::string offsetToken = "0x" + tokens[5];
423 uintptr_t offset;
424 std::istringstream(offsetToken) >> std::hex >> offset;
425
426 std::ifstream obj(res->base_name + ".o");
427 obj.seekg(offset);
428 res->code.reset(new std::vector<uint8_t>(res->length));
429 obj.read(reinterpret_cast<char*>(&(*res->code)[0]), res->length);
430 obj.close();
431
432 res->ok = true;
433 return true;
434 }
435
436 // Remove temporary files.
437 void Clean(const NativeAssemblerResult* res) {
438 std::remove((res->base_name + ".S").c_str());
439 std::remove((res->base_name + ".o").c_str());
440 std::remove((res->base_name + ".o.dump").c_str());
441 }
442
443 // Check whether file exists. Is used for commands, so strips off any parameters: anything after
444 // the first space. We skip to the last slash for this, so it should work with directories with
445 // spaces.
Andreas Gampe2e965ac2016-11-03 17:24:15 -0700446 static bool FileExists(const std::string& file) {
Andreas Gampe03b9ee42015-04-24 21:41:45 -0700447 if (file.length() == 0) {
448 return false;
449 }
450
451 // Need to strip any options.
452 size_t last_slash = file.find_last_of('/');
453 if (last_slash == std::string::npos) {
454 // No slash, start looking at the start.
455 last_slash = 0;
456 }
457 size_t space_index = file.find(' ', last_slash);
458
459 if (space_index == std::string::npos) {
460 std::ifstream infile(file.c_str());
461 return infile.good();
462 } else {
463 std::string copy = file.substr(0, space_index - 1);
464
465 struct stat buf;
466 return stat(copy.c_str(), &buf) == 0;
467 }
468 }
469
470 static std::string GetGCCRootPath() {
471 return "prebuilts/gcc/linux-x86";
472 }
473
474 static std::string GetRootPath() {
475 // 1) Check ANDROID_BUILD_TOP
476 char* build_top = getenv("ANDROID_BUILD_TOP");
477 if (build_top != nullptr) {
478 return std::string(build_top) + "/";
479 }
480
481 // 2) Do cwd
482 char temp[1024];
483 return getcwd(temp, 1024) ? std::string(temp) + "/" : std::string("");
484 }
485
Andreas Gampe2e965ac2016-11-03 17:24:15 -0700486 std::string FindTool(const std::string& tool_name) {
Andreas Gampe03b9ee42015-04-24 21:41:45 -0700487 // Find the current tool. Wild-card pattern is "arch-string*tool-name".
488 std::string gcc_path = GetRootPath() + GetGCCRootPath();
489 std::vector<std::string> args;
490 args.push_back("find");
491 args.push_back(gcc_path);
492 args.push_back("-name");
493 args.push_back(architecture_string_ + "*" + tool_name);
494 args.push_back("|");
495 args.push_back("sort");
496 args.push_back("|");
497 args.push_back("tail");
498 args.push_back("-n");
499 args.push_back("1");
500 std::string tmp_file = GetTmpnam();
501 args.push_back(">");
502 args.push_back(tmp_file);
503 std::string sh_args = Join(args, ' ');
504
505 args.clear();
506 args.push_back("/bin/sh");
507 args.push_back("-c");
508 args.push_back(sh_args);
509
510 std::string error_msg;
511 if (!Exec(args, &error_msg)) {
512 EXPECT_TRUE(false) << error_msg;
Andreas Gampef3e07062015-10-06 09:05:10 -0700513 UNREACHABLE();
Andreas Gampe03b9ee42015-04-24 21:41:45 -0700514 }
515
516 std::ifstream in(tmp_file.c_str());
517 std::string line;
518 if (!std::getline(in, line)) {
519 in.close();
520 std::remove(tmp_file.c_str());
521 return "";
522 }
523 in.close();
524 std::remove(tmp_file.c_str());
525 return line;
526 }
527
Andreas Gampe75324012015-10-06 18:59:08 -0700528 // Helper for below. If name_predicate is empty, search for all files, otherwise use it for the
529 // "-name" option.
Andreas Gampe2e965ac2016-11-03 17:24:15 -0700530 static void FindToolDumpPrintout(const std::string& name_predicate,
531 const std::string& tmp_file) {
Andreas Gampef3e07062015-10-06 09:05:10 -0700532 std::string gcc_path = GetRootPath() + GetGCCRootPath();
533 std::vector<std::string> args;
534 args.push_back("find");
535 args.push_back(gcc_path);
Andreas Gampe75324012015-10-06 18:59:08 -0700536 if (!name_predicate.empty()) {
537 args.push_back("-name");
538 args.push_back(name_predicate);
539 }
Andreas Gampef3e07062015-10-06 09:05:10 -0700540 args.push_back("|");
541 args.push_back("sort");
Andreas Gampef3e07062015-10-06 09:05:10 -0700542 args.push_back(">");
543 args.push_back(tmp_file);
544 std::string sh_args = Join(args, ' ');
545
546 args.clear();
547 args.push_back("/bin/sh");
548 args.push_back("-c");
549 args.push_back(sh_args);
550
551 std::string error_msg;
552 if (!Exec(args, &error_msg)) {
553 EXPECT_TRUE(false) << error_msg;
554 UNREACHABLE();
555 }
556
Andreas Gampe75324012015-10-06 18:59:08 -0700557 LOG(ERROR) << "FindToolDump: gcc_path=" << gcc_path
558 << " cmd=" << sh_args;
Andreas Gampef3e07062015-10-06 09:05:10 -0700559 std::ifstream in(tmp_file.c_str());
560 if (in) {
Andreas Gampe75324012015-10-06 18:59:08 -0700561 std::string line;
562 while (std::getline(in, line)) {
563 LOG(ERROR) << line;
564 }
Andreas Gampef3e07062015-10-06 09:05:10 -0700565 }
Andreas Gampe75324012015-10-06 18:59:08 -0700566 in.close();
567 std::remove(tmp_file.c_str());
568 }
569
570 // For debug purposes.
Andreas Gampe2e965ac2016-11-03 17:24:15 -0700571 void FindToolDump(const std::string& tool_name) {
Andreas Gampe75324012015-10-06 18:59:08 -0700572 // Check with the tool name.
573 FindToolDumpPrintout(architecture_string_ + "*" + tool_name, GetTmpnam());
574 FindToolDumpPrintout("", GetTmpnam());
Andreas Gampef3e07062015-10-06 09:05:10 -0700575 }
576
Andreas Gampe03b9ee42015-04-24 21:41:45 -0700577 // Use a consistent tmpnam, so store it.
578 std::string GetTmpnam() {
579 if (tmpnam_.length() == 0) {
580 ScratchFile tmp;
581 tmpnam_ = tmp.GetFilename() + "asm";
582 }
583 return tmpnam_;
584 }
585
586 static constexpr size_t OBJDUMP_SECTION_LINE_MIN_TOKENS = 6;
587
588 std::string architecture_string_;
589 const char* asm_header_;
590
591 std::string assembler_cmd_name_;
592 std::string assembler_parameters_;
593
594 std::string objdump_cmd_name_;
595 std::string objdump_parameters_;
596
597 std::string disassembler_cmd_name_;
598 std::string disassembler_parameters_;
599
600 std::string resolved_assembler_cmd_;
601 std::string resolved_objdump_cmd_;
602 std::string resolved_disassemble_cmd_;
603
604 std::string android_data_;
605
606 DISALLOW_COPY_AND_ASSIGN(AssemblerTestInfrastructure);
607};
608
609} // namespace art
610
611#endif // ART_COMPILER_UTILS_ASSEMBLER_TEST_BASE_H_