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