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