1 /* 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 <cstdio> 21 #include <cstdlib> 22 #include <fstream> 23 #include <iterator> 24 #include <sys/stat.h> 25 26 #include "common_runtime_test.h" // For ScratchFile 27 #include "utils.h" 28 29 namespace 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. 33 static 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. 37 static 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. 40 class AssemblerTestInfrastructure { 41 public: AssemblerTestInfrastructure(std::string architecture,std::string as,std::string as_params,std::string objdump,std::string objdump_params,std::string disasm,std::string disasm_params,const char * asm_header)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 ~AssemblerTestInfrastructure()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. CheckTools()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). Driver(const std::vector<uint8_t> & data,std::string assembly_text,std::string test_name)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. GetAssemblerCommand()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. GetObjdumpCommand()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. GetDisassembleCommand()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. Assemble(const char * from_file,const char * to_file,std::string * error_msg)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) { 219 LOG(INFO) << "Assembler command line:"; 220 for (std::string arg : args) { 221 LOG(INFO) << arg; 222 } 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. Objdump(std::string file)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. DisassembleBinaries(const std::vector<uint8_t> & data,const std::vector<uint8_t> & as,std::string test_name)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 DisassembleBinary(std::string file,std::string * error_msg)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 WriteToFile(const std::vector<uint8_t> & buffer,std::string test_name)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 CompareFiles(std::string f1,std::string f2)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. Compile(std::string assembly_code,NativeAssemblerResult * res,std::string test_name)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. Clean(const NativeAssemblerResult * res)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. FileExists(std::string file)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 GetGCCRootPath()453 static std::string GetGCCRootPath() { 454 return "prebuilts/gcc/linux-x86"; 455 } 456 GetRootPath()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 FindTool(std::string tool_name)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. GetTmpnam()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_ 546