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