elftosb.cpp revision 1.1.2.3
1/* 2 * File: elftosb.cpp 3 * 4 * Copyright (c) Freescale Semiconductor, Inc. All rights reserved. 5 * See included license file for license details. 6 */ 7 8#include "stdafx.h" 9#include <iostream> 10#include <fstream> 11#include <sstream> 12#include <stdlib.h> 13#include <stdexcept> 14#include "ConversionController.h" 15#include "options.h" 16#include "Version.h" 17#include "EncoreBootImage.h" 18#include "smart_ptr.h" 19#include "Logging.h" 20#include "EncoreBootImageGenerator.h" 21#include "SearchPath.h" 22#include "format_string.h" 23 24//! An array of strings. 25typedef std::vector<std::string> string_vector_t; 26 27//! The tool's name. 28const char k_toolName[] = "elftosb"; 29 30//! Current version number for the tool. 31const char k_version[] = "2.6.1"; 32 33//! Copyright string. 34const char k_copyright[] = "Copyright (c) 2004-2010 Freescale Semiconductor, Inc.\nAll rights reserved."; 35 36static const char * k_optionsDefinition[] = { 37 "?|help", 38 "v|version", 39 "f:chip-family <family>", 40 "c:command <file>", 41 "o:output <file>", 42 "P:product <version>", 43 "C:component <version>", 44 "k:key <file>", 45 "z|zero-key", 46 "D:define <const>", 47 "O:option <option>", 48 "d|debug", 49 "q|quiet", 50 "V|verbose", 51 "p:search-path <path>", 52 NULL 53}; 54 55//! Help string. 56const char k_usageText[] = "\nOptions:\n\ 57 -?/--help Show this help\n\ 58 -v/--version Display tool version\n\ 59 -f/--chip-family <family> Select the chip family (default is 37xx)\n\ 60 -c/--command <file> Use this command file\n\ 61 -o/--output <file> Write output to this file\n\ 62 -p/--search-path <path> Add a search path used to find input files\n\ 63 -P/--product <version Set product version\n\ 64 -C/--component <version> Set component version\n\ 65 -k/--key <file> Add OTP key, enable encryption\n\ 66 -z/--zero-key Add default key of all zeroes\n\ 67 -D/--define <const>=<int> Define or override a constant value\n\ 68 -O/--option <name>=<value> Set or override a processing option\n\ 69 -d/--debug Enable debug output\n\ 70 -q/--quiet Output only warnings and errors\n\ 71 -V/--verbose Print extra detailed log information\n\n"; 72 73// prototypes 74int main(int argc, char* argv[], char* envp[]); 75 76/*! 77 * \brief Class that encapsulates the elftosb tool. 78 * 79 * A single global logger instance is created during object construction. It is 80 * never freed because we need it up to the last possible minute, when an 81 * exception could be thrown. 82 */ 83class elftosbTool 84{ 85protected: 86 //! Supported chip families. 87 enum chip_family_t 88 { 89 k37xxFamily, //!< 37xx series. 90 kMX28Family, //!< Catskills series. 91 }; 92 93 /*! 94 * \brief A structure describing an entry in the table of chip family names. 95 */ 96 struct FamilyNameTableEntry 97 { 98 const char * const name; 99 chip_family_t family; 100 }; 101 102 //! \brief Table that maps from family name strings to chip family constants. 103 static const FamilyNameTableEntry kFamilyNameTable[]; 104 105 int m_argc; //!< Number of command line arguments. 106 char ** m_argv; //!< String value for each command line argument. 107 StdoutLogger * m_logger; //!< Singleton logger instance. 108 string_vector_t m_keyFilePaths; //!< Paths to OTP key files. 109 string_vector_t m_positionalArgs; //!< Arguments coming after explicit options. 110 bool m_isVerbose; //!< Whether the verbose flag was turned on. 111 bool m_useDefaultKey; //!< Include a default (zero) crypto key. 112 const char * m_commandFilePath; //!< Path to the elftosb command file. 113 const char * m_outputFilePath; //!< Path to the output .sb file. 114 const char * m_searchPath; //!< Optional search path for input files. 115 elftosb::version_t m_productVersion; //!< Product version specified on command line. 116 elftosb::version_t m_componentVersion; //!< Component version specified on command line. 117 bool m_productVersionSpecified; //!< True if the product version was specified on the command line. 118 bool m_componentVersionSpecified; //!< True if the component version was specified on the command line. 119 chip_family_t m_family; //!< Chip family that the output file is formatted for. 120 elftosb::ConversionController m_controller; //!< Our conversion controller instance. 121 122public: 123 /*! 124 * Constructor. 125 * 126 * Creates the singleton logger instance. 127 */ 128 elftosbTool(int argc, char * argv[]) 129 : m_argc(argc), 130 m_argv(argv), 131 m_logger(0), 132 m_keyFilePaths(), 133 m_positionalArgs(), 134 m_isVerbose(false), 135 m_useDefaultKey(false), 136 m_commandFilePath(NULL), 137 m_outputFilePath(NULL), 138 m_searchPath(NULL), 139 m_productVersion(), 140 m_componentVersion(), 141 m_productVersionSpecified(false), 142 m_componentVersionSpecified(false), 143 m_family(k37xxFamily), 144 m_controller() 145 { 146 // create logger instance 147 m_logger = new StdoutLogger(); 148 m_logger->setFilterLevel(Logger::INFO); 149 Log::setLogger(m_logger); 150 } 151 152 /*! 153 * Destructor. 154 */ 155 ~elftosbTool() 156 { 157 } 158 159 /*! 160 * \brief Searches the family name table. 161 * 162 * \retval true The \a name was found in the table, and \a family is valid. 163 * \retval false No matching family name was found. The \a family argument is not modified. 164 */ 165 bool lookupFamilyName(const char * name, chip_family_t * family) 166 { 167 // Create a local read-write copy of the argument string. 168 std::string familyName(name); 169 170 // Convert the argument string to lower case for case-insensitive comparison. 171 for (int n=0; n < familyName.length(); n++) 172 { 173 familyName[n] = tolower(familyName[n]); 174 } 175 176 // Exit the loop if we hit the NULL terminator entry. 177 const FamilyNameTableEntry * entry = &kFamilyNameTable[0]; 178 for (; entry->name; entry++) 179 { 180 // Compare lowercased name with the table entry. 181 if (familyName == entry->name) 182 { 183 *family = entry->family; 184 return true; 185 } 186 } 187 188 // Failed to find a matching name. 189 return false; 190 } 191 192 /*! 193 * Reads the command line options passed into the constructor. 194 * 195 * This method can return a return code to its caller, which will cause the 196 * tool to exit immediately with that return code value. Normally, though, it 197 * will return -1 to signal that the tool should continue to execute and 198 * all options were processed successfully. 199 * 200 * The Options class is used to parse command line options. See 201 * #k_optionsDefinition for the list of options and #k_usageText for the 202 * descriptive help for each option. 203 * 204 * \retval -1 The options were processed successfully. Let the tool run normally. 205 * \return A zero or positive result is a return code value that should be 206 * returned from the tool as it exits immediately. 207 */ 208 int processOptions() 209 { 210 Options options(*m_argv, k_optionsDefinition); 211 OptArgvIter iter(--m_argc, ++m_argv); 212 213 // process command line options 214 int optchar; 215 const char * optarg; 216 while (optchar = options(iter, optarg)) 217 { 218 switch (optchar) 219 { 220 case '?': 221 printUsage(options); 222 return 0; 223 224 case 'v': 225 printf("%s %s\n%s\n", k_toolName, k_version, k_copyright); 226 return 0; 227 228 case 'f': 229 if (!lookupFamilyName(optarg, &m_family)) 230 { 231 Log::log(Logger::ERROR, "error: unknown chip family '%s'\n", optarg); 232 printUsage(options); 233 return 0; 234 } 235 break; 236 237 case 'c': 238 m_commandFilePath = optarg; 239 break; 240 241 case 'o': 242 m_outputFilePath = optarg; 243 break; 244 245 case 'P': 246 m_productVersion.set(optarg); 247 m_productVersionSpecified = true; 248 break; 249 250 case 'C': 251 m_componentVersion.set(optarg); 252 m_componentVersionSpecified = true; 253 break; 254 255 case 'k': 256 m_keyFilePaths.push_back(optarg); 257 break; 258 259 case 'z': 260 m_useDefaultKey = true; 261 break; 262 263 case 'D': 264 overrideVariable(optarg); 265 break; 266 267 case 'O': 268 overrideOption(optarg); 269 break; 270 271 case 'd': 272 Log::getLogger()->setFilterLevel(Logger::DEBUG); 273 break; 274 275 case 'q': 276 Log::getLogger()->setFilterLevel(Logger::WARNING); 277 break; 278 279 case 'V': 280 m_isVerbose = true; 281 break; 282 283 case 'p': 284 { 285 std::string newSearchPath(optarg); 286 PathSearcher::getGlobalSearcher().addSearchPath(newSearchPath); 287 break; 288 } 289 290 default: 291 Log::log(Logger::ERROR, "error: unrecognized option\n\n"); 292 printUsage(options); 293 return 0; 294 } 295 } 296 297 // handle positional args 298 if (iter.index() < m_argc) 299 { 300 Log::SetOutputLevel leveler(Logger::DEBUG); 301 Log::log("positional args:\n"); 302 int i; 303 for (i = iter.index(); i < m_argc; ++i) 304 { 305 Log::log("%d: %s\n", i - iter.index(), m_argv[i]); 306 m_positionalArgs.push_back(m_argv[i]); 307 } 308 } 309 310 // all is well 311 return -1; 312 } 313 314 /*! 315 * Prints help for the tool. 316 */ 317 void printUsage(Options & options) 318 { 319 options.usage(std::cout, "files..."); 320 printf("%s", k_usageText); 321 } 322 323 /*! 324 * \brief Core of the tool. 325 * 326 * Calls processOptions() to handle command line options before performing the 327 * real work the tool does. 328 */ 329 int run() 330 { 331 try 332 { 333 // read command line options 334 int result; 335 if ((result = processOptions()) != -1) 336 { 337 return result; 338 } 339 340 // set verbose logging 341 setVerboseLogging(); 342 343 // check argument values 344 checkArguments(); 345 346 // set up the controller 347 m_controller.setCommandFilePath(m_commandFilePath); 348 349 // add external paths to controller 350 string_vector_t::iterator it = m_positionalArgs.begin(); 351 for (; it != m_positionalArgs.end(); ++it) 352 { 353 m_controller.addExternalFilePath(*it); 354 } 355 356 // run conversion 357 convert(); 358 } 359 catch (std::exception & e) 360 { 361 Log::log(Logger::ERROR, "error: %s\n", e.what()); 362 return 1; 363 } 364 catch (...) 365 { 366 Log::log(Logger::ERROR, "error: unexpected exception\n"); 367 return 1; 368 } 369 370 return 0; 371 } 372 373 /*! 374 * \brief Validate arguments that can be checked. 375 * \exception std::runtime_error Thrown if an argument value fails to pass validation. 376 */ 377 void checkArguments() 378 { 379 if (m_commandFilePath == NULL) 380 { 381 throw std::runtime_error("no command file was specified"); 382 } 383 if (m_outputFilePath == NULL) 384 { 385 throw std::runtime_error("no output file was specified"); 386 } 387 } 388 389 /*! 390 * \brief Turns on verbose logging. 391 */ 392 void setVerboseLogging() 393 { 394 if (m_isVerbose) 395 { 396 // verbose only affects the INFO and DEBUG filter levels 397 // if the user has selected quiet mode, it overrides verbose 398 switch (Log::getLogger()->getFilterLevel()) 399 { 400 case Logger::INFO: 401 Log::getLogger()->setFilterLevel(Logger::INFO2); 402 break; 403 case Logger::DEBUG: 404 Log::getLogger()->setFilterLevel(Logger::DEBUG2); 405 break; 406 } 407 } 408 } 409 410 /*! 411 * \brief Returns the integer value for a string. 412 * 413 * Metric multiplier prefixes are supported. 414 */ 415 uint32_t parseIntValue(const char * value) 416 { 417 // Accept 'true'/'yes' and 'false'/'no' as integer values. 418 if ((strcmp(value, "true") == 0) || (strcmp(value, "yes") == 0)) 419 { 420 return 1; 421 } 422 else if ((strcmp(value, "false") == 0) || (strcmp(value, "no") == 0)) 423 { 424 return 0; 425 } 426 427 uint32_t intValue = strtoul(value, NULL, 0); 428 unsigned multiplier; 429 switch (value[strlen(value) - 1]) 430 { 431 case 'G': 432 multiplier = 1024 * 1024 * 1024; 433 break; 434 case 'M': 435 multiplier = 1024 * 1024; 436 break; 437 case 'K': 438 multiplier = 1024; 439 break; 440 default: 441 multiplier = 1; 442 } 443 intValue *= multiplier; 444 return intValue; 445 } 446 447 /*! 448 * \brief Parses the -D option to override a constant value. 449 */ 450 void overrideVariable(const char * optarg) 451 { 452 // split optarg into two strings 453 std::string constName(optarg); 454 int i; 455 for (i=0; i < strlen(optarg); ++i) 456 { 457 if (optarg[i] == '=') 458 { 459 constName.resize(i++); 460 break; 461 } 462 } 463 464 uint32_t constValue = parseIntValue(&optarg[i]); 465 466 elftosb::EvalContext & context = m_controller.getEvalContext(); 467 context.setVariable(constName, constValue); 468 context.lockVariable(constName); 469 } 470 471 /*! 472 * \brief 473 */ 474 void overrideOption(const char * optarg) 475 { 476 // split optarg into two strings 477 std::string optionName(optarg); 478 int i; 479 for (i=0; i < strlen(optarg); ++i) 480 { 481 if (optarg[i] == '=') 482 { 483 optionName.resize(i++); 484 break; 485 } 486 } 487 488 // handle quotes for option value 489 const char * valuePtr = &optarg[i]; 490 bool isString = false; 491 int len; 492 if (valuePtr[0] == '"') 493 { 494 // remember that the value is a string and get rid of the opening quote 495 isString = true; 496 valuePtr++; 497 498 // remove trailing quote if present 499 len = strlen(valuePtr); 500 if (valuePtr[len] == '"') 501 { 502 len--; 503 } 504 } 505 506 elftosb::Value * value; 507 if (isString) 508 { 509 std::string stringValue(valuePtr); 510 stringValue.resize(len); // remove trailing quote 511 value = new elftosb::StringValue(stringValue); 512 } 513 else 514 { 515 value = new elftosb::IntegerValue(parseIntValue(valuePtr)); 516 } 517 518 // Set and lock the option in the controller 519 m_controller.setOption(optionName, value); 520 m_controller.lockOption(optionName); 521 } 522 523 /*! 524 * \brief Do the conversion. 525 * \exception std::runtime_error This exception is thrown if the conversion controller does 526 * not produce a boot image, or if the output file cannot be opened. Other errors 527 * internal to the conversion controller may also produce this exception. 528 */ 529 void convert() 530 { 531 // create a generator for the chosen chip family 532 smart_ptr<elftosb::BootImageGenerator> generator; 533 switch (m_family) 534 { 535 case k37xxFamily: 536 generator = new elftosb::EncoreBootImageGenerator; 537 elftosb::g_enableHABSupport = false; 538 break; 539 540 case kMX28Family: 541 generator = new elftosb::EncoreBootImageGenerator; 542 elftosb::g_enableHABSupport = true; 543 break; 544 } 545 546 // process input and get a boot image 547 m_controller.run(); 548 smart_ptr<elftosb::BootImage> image = m_controller.generateOutput(generator); 549 if (!image) 550 { 551 throw std::runtime_error("failed to produce output!"); 552 } 553 554 // set version numbers if they were provided on the command line 555 if (m_productVersionSpecified) 556 { 557 image->setProductVersion(m_productVersion); 558 } 559 if (m_componentVersionSpecified) 560 { 561 image->setComponentVersion(m_componentVersion); 562 } 563 564 // special handling for each family 565 switch (m_family) 566 { 567 case k37xxFamily: 568 case kMX28Family: 569 { 570 // add OTP keys 571 elftosb::EncoreBootImage * encoreImage = dynamic_cast<elftosb::EncoreBootImage*>(image.get()); 572 if (encoreImage) 573 { 574 // add keys 575 addCryptoKeys(encoreImage); 576 577 // print debug image 578 encoreImage->debugPrint(); 579 } 580 break; 581 } 582 } 583 584 // write output 585 std::ofstream outputStream(m_outputFilePath, std::ios_base::binary | std::ios_base::out | std::ios_base::trunc); 586 if (outputStream.is_open()) 587 { 588 image->writeToStream(outputStream); 589 } 590 else 591 { 592 throw std::runtime_error(format_string("could not open output file %s", m_outputFilePath)); 593 } 594 } 595 596 /*! 597 * \brief 598 */ 599 void addCryptoKeys(elftosb::EncoreBootImage * encoreImage) 600 { 601 string_vector_t::iterator it = m_keyFilePaths.begin(); 602 for (; it != m_keyFilePaths.end(); ++it) 603 { 604 std::string & keyPath = *it; 605 606 std::string actualPath; 607 bool found = PathSearcher::getGlobalSearcher().search(keyPath, PathSearcher::kFindFile, true, actualPath); 608 if (!found) 609 { 610 throw std::runtime_error(format_string("unable to find key file %s\n", keyPath.c_str())); 611 } 612 613 std::ifstream keyStream(actualPath.c_str(), std::ios_base::in); 614 if (!keyStream.is_open()) 615 { 616 throw std::runtime_error(format_string("unable to read key file %s\n", keyPath.c_str())); 617 } 618 keyStream.seekg(0); 619 620 try 621 { 622 // read as many keys as possible from the stream 623 while (true) 624 { 625 AESKey<128> key(keyStream); 626 encoreImage->addKey(key); 627 628 // dump key bytes 629 dumpKey(key); 630 } 631 } 632 catch (...) 633 { 634 // ignore the exception -- there are just no more keys in the stream 635 } 636 } 637 638 // add the default key of all zero bytes if requested 639 if (m_useDefaultKey) 640 { 641 AESKey<128> defaultKey; 642 encoreImage->addKey(defaultKey); 643 } 644 } 645 646 /*! 647 * \brief Write the value of each byte of the \a key to the log. 648 */ 649 void dumpKey(const AESKey<128> & key) 650 { 651 // dump key bytes 652 Log::log(Logger::DEBUG, "key bytes: "); 653 AESKey<128>::key_t the_key; 654 key.getKey(&the_key); 655 int q; 656 for (q=0; q<16; q++) 657 { 658 Log::log(Logger::DEBUG, "%02x ", the_key[q]); 659 } 660 Log::log(Logger::DEBUG, "\n"); 661 } 662 663}; 664 665const elftosbTool::FamilyNameTableEntry elftosbTool::kFamilyNameTable[] = 666 { 667 { "37xx", k37xxFamily }, 668 { "377x", k37xxFamily }, 669 { "378x", k37xxFamily }, 670 { "mx23", k37xxFamily }, 671 { "imx23", k37xxFamily }, 672 { "i.mx23", k37xxFamily }, 673 { "mx28", kMX28Family }, 674 { "imx28", kMX28Family }, 675 { "i.mx28", kMX28Family }, 676 677 // Null terminator entry. 678 { NULL, k37xxFamily } 679 }; 680 681/*! 682 * Main application entry point. Creates an sbtool instance and lets it take over. 683 */ 684int main(int argc, char* argv[], char* envp[]) 685{ 686 try 687 { 688 return elftosbTool(argc, argv).run(); 689 } 690 catch (...) 691 { 692 Log::log(Logger::ERROR, "error: unexpected exception\n"); 693 return 1; 694 } 695 696 return 0; 697} 698 699 700 701