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