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