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