1 /* A sensible interface to MIPS cross compilation tools.
2    Copyright 2001, 2003, 2004 Brian R. Gaeke.
3 
4 This file is part of VMIPS.
5 
6 VMIPS is free software; you can redistribute it and/or modify it
7 under the terms of the GNU General Public License as published by the
8 Free Software Foundation; either version 2 of the License, or (at your
9 option) any later version.
10 
11 VMIPS is distributed in the hope that it will be useful, but
12 WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
13 or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14 for more details.
15 
16 You should have received a copy of the GNU General Public License along
17 with VMIPS; if not, write to the Free Software Foundation, Inc.,
18 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.  */
19 
20 /* For later: ways to get page size:
21 long sysconf(_SC_PAGE_SIZE);
22 or
23 long sysconf(_SC_PAGESIZE);
24 or
25 int getpagesize(void);
26 */
27 
28 #include "endiantest.h"
29 #include "fileutils.h"
30 #include "stub-dis.h"
31 #include "error.h"
32 #include "options.h"
33 #include <cerrno>
34 #include <cstdarg>
35 #include <climits>
36 #include <cstdlib>
37 #include <cstring>
38 #include <cstdio>
39 #include <iostream>
40 #include <sys/stat.h>
41 #include <sys/wait.h>
42 #include <unistd.h>
43 #include <vector>
44 
45 const unsigned int MAXARGV = 500;
46 const unsigned int TMPFNAMESIZE = 26;
47 
48 int verbose = 0;
49 int dryrun = 0;
50 
51 char pkgdatadir[PATH_MAX];
52 char ccname[PATH_MAX];
53 char ldname[PATH_MAX];
54 char objcopyname[PATH_MAX];
55 char objdumpname[PATH_MAX];
56 char endianflag[4];
57 
program_available(const char * execname)58 bool program_available (const char *execname) {
59   struct stat st;
60   if (stat (execname, &st) < 0)
61     return false;
62   if (!(st.st_mode & S_IXUSR))
63     return false;
64   return true;
65 }
66 
config_error(const char * programname,const char * execname)67 int config_error (const char *programname, const char *execname) {
68 	fprintf (stderr, "vmipstool: Couldn't find MIPS tool '%s' in '%s' "
69 		"(installation error?)\n", programname, execname);
70 	return 1;
71 }
72 
setup_paths(bool bigendian,const char * mipstoolprefix)73 void setup_paths (bool bigendian, const char *mipstoolprefix) {
74 	strcpy (pkgdatadir, PKGDATADIR);
75     // Allow user (or a shell script) to override the path from which the
76     // linker script is read. This allows vmipstool to be executed from the
77     // source tree without vmips being installed.
78     char *user_supplied_pkgdatadir;
79     user_supplied_pkgdatadir = getenv ("VMIPS_PKGDATADIR");
80     if (user_supplied_pkgdatadir) {
81       strncpy (pkgdatadir, user_supplied_pkgdatadir, PATH_MAX);
82       if (strlen (user_supplied_pkgdatadir) >= PATH_MAX)
83         pkgdatadir[PATH_MAX - 1] = '\0';
84     }
85 
86 	strcpy (ccname, mipstoolprefix);
87 	strcat (ccname, "gcc");
88 	strcpy (ldname, mipstoolprefix);
89 	strcat (ldname, "ld");
90 	strcpy (objcopyname, mipstoolprefix);
91 	strcat (objcopyname, "objcopy");
92 	strcpy (objdumpname, mipstoolprefix);
93 	strcat (objdumpname, "objdump");
94 
95     // Decide which flag should be used to force the endianness of an
96     // object file.
97     if (bigendian) {
98 		strcpy (endianflag, "-EB");
99     } else {
100 		strcpy (endianflag, "-EL");
101     }
102 }
103 
104 /* ROMs are padded to a multiple of this many bytes. I don't think this
105    is strictly necessary and will probably go away eventually. */
106 long pagesz = 4096;
107 
108 /********************************* Various utils ******************************/
109 
usage(void)110 void usage(void) {
111 	puts("usage:");
112 	puts(" vmipstool [ VT-FLAGS ] --compile [ FLAGS ] FILE.c -o FILE.o");
113 	puts(" vmipstool [ VT-FLAGS ] --preprocess [ FLAGS ] FILE");
114 	puts(" vmipstool [ VT-FLAGS ] --assemble [ FLAGS ] FILE.s -o FILE.o");
115 	puts
116 		(" vmipstool [ VT-FLAGS ] --link [ FLAGS ] FILE1.o ... FILEn.o -o PROG");
117 	puts(" vmipstool [ VT-FLAGS ] --make-rom PROG PROG.rom");
118 	puts(" vmipstool [ VT-FLAGS ] --disassemble-rom PROG.rom");
119 	puts(" vmipstool [ VT-FLAGS ] --disassemble PROG (or FILE.o)");
120 	puts(" vmipstool [ VT-FLAGS ] --swap-words INPUT OUTPUT");
121 	puts("");
122 	puts("VT-FLAGS may include:");
123 	puts(" --help         Display this help message and exit.");
124 	puts(" --version      Display the version of vmipstool and exit.");
125 	puts(" --verbose      Echo commands as they are run.");
126 	puts
127 		(" --dry-run      Don't actually run anything; use with --verbose.");
128 	puts(" --ld-script=T  Use T as ld script (instead of default script);");
129 	puts("                use with --link.");
130 	puts("");
131 	puts("Report bugs to <vmips@dgate.org>.");
132 }
133 
maybe_echo(char ** newargv)134 void maybe_echo(char **newargv) {
135 	int i;
136 	if (verbose) {
137 		for (i = 0; newargv[i] != NULL; i++)
138 			fprintf(stderr, "%s ", newargv[i]);
139 		fprintf(stderr, "\n");
140 	}
141 }
142 
maybe_run(char ** newargv)143 int maybe_run(char **newargv) {
144 	pid_t pid;
145 	int status, error = 0;
146 
147 	if (!dryrun) {
148 		if ((pid = fork()) == 0) {
149 			execv(newargv[0], newargv);
150 			perror (newargv[0]);
151 			exit (255);
152 		} else {
153 			waitpid(pid, &status, 0);
154 			if (WIFEXITED(status))
155 				error = (WEXITSTATUS(status) != 0);
156 			else
157 				error = 1;
158 		}
159 	}
160 	return error;
161 }
162 
echo_and_run_l(char * c,...)163 int echo_and_run_l(char *c, ...) {
164 	va_list ap;
165 	char *newargv[MAXARGV];
166 	int newargc = 0;
167 
168 	va_start(ap, c);
169 	newargv[newargc++] = c;
170 	while (c != NULL) {
171 		c = va_arg(ap, char *);
172 		newargv[newargc++] = c;
173 	}
174 	va_end(ap);
175 
176 	maybe_echo(newargv);
177 	return maybe_run(newargv);
178 }
179 
echo_and_run_lv(char * c,...)180 int echo_and_run_lv(char *c, ...) {
181 	va_list ap;
182 	char *newargv[MAXARGV];
183 	char **originalargv;
184 	int newargc = 0;
185 	int i;
186 
187 	va_start(ap, c);
188 	newargv[newargc++] = c;
189 	while (c != NULL) {
190 		c = va_arg(ap, char *);
191 		newargv[newargc++] = c;
192 	}
193 	newargc--;
194 	originalargv = va_arg(ap, char **);
195 	for (i = 0; originalargv[i] != NULL; i++)
196 		newargv[newargc++] = originalargv[i];
197 	newargv[newargc++] = NULL;
198 	va_end(ap);
199 
200 	maybe_echo(newargv);
201 	return maybe_run(newargv);
202 }
203 
204 /* Search colon-separated PATH for file named TARGET. Copy found file's
205    full pathname into RESULT and return true if found. Returns false and
206    leaves result untouched if not found. */
search_path(char * result,char * path,const char * target)207 bool search_path(char *result, char *path, const char *target) {
208 	const char *delims = ":";
209 	char *dir = strtok (path, delims);
210 	while (dir != NULL) {
211 		char trypath[PATH_MAX];
212 		sprintf (trypath, "%s/%s", dir, target);
213 		if (can_read_file (trypath)) {
214 			strcpy (result, trypath);
215 			return true;
216 		} else {
217 			dir = strtok (NULL, delims);
218 		}
219 	}
220 	return false;
221 }
222 
223 /********************************* Linking ******************************/
224 
225 char ldscript_full_path[PATH_MAX];
226 bool ldscript_error = false;
227 
can_read_default_ldscript()228 int can_read_default_ldscript () {
229 	/* Build the search path */
230 	char *ldscript_search_path;
231 
232 	/* so that it can work without being installed */
233 	char rest_of_path[] =
234 		":.:..:./sample_code:../sample_code:../../sample_code";
235 	const char ldscript_name[] = "ld.script";
236 
237 	ldscript_search_path =
238 		new char[strlen(rest_of_path) + strlen(pkgdatadir) + 2];
239 	strcpy(ldscript_search_path, pkgdatadir);
240 	strcat(ldscript_search_path, rest_of_path);
241 
242 	/* Look for the ld.script */
243 	return search_path(ldscript_full_path, ldscript_search_path,
244 					   ldscript_name);
245 }
246 
do_link(int argc,char ** argv)247 int do_link (int argc, char **argv) {
248 	if (ldscript_error) {
249 		fprintf(stderr, "vmipstool: can't access linker script\n");
250 		return 1;
251 	}
252 	if (!program_available (ldname))
253 		return config_error ("ld", ldname);
254 	return echo_and_run_lv (ldname, endianflag, "-T", ldscript_full_path, NULL,
255                             argv, NULL);
256 }
257 
258 /********************************* ROM-making ******************************/
259 
copy_with_padded_blocks(char * in,char * out,long size)260 int copy_with_padded_blocks (char *in, char *out, long size) {
261 	char *buff = new char[size];
262 	FILE *f = fopen(in, "rb");
263 	if (!f) {
264 		perror(in);
265 		return -1;
266 	}
267 	FILE *g = fopen(out, "wb");
268 	if (!g) {
269 		perror(out);
270 		return -1;
271 	}
272 	errno = 0;
273 	int readcount;
274 	while ((readcount = fread(buff, 1, size, f)) != 0) {
275 		if (readcount < size) {
276 			if (errno) {
277 				perror(in);
278 				fclose(f);
279 				fclose(g);
280 				return -1;
281 			} else {
282 				for (int i = readcount; i < size; i++)
283 					buff[i] = '\0';
284 			}
285 		}
286 		int writecount = fwrite(buff, 1, size, g);
287 		if (writecount < size) {
288 			perror(out);
289 			fclose(f);
290 			fclose(g);
291 			return -1;
292 		}
293 	}
294 	fclose(f);
295 	fclose(g);
296 	delete [] buff;
297 	return 0;
298 }
299 
make_rom(int argc,char ** argv)300 int make_rom (int argc, char **argv) {
301 	char tmp[TMPFNAMESIZE];
302 	char *inputfile;
303 	char *outputfile;
304 	if (argc != 2) {
305 		fprintf(stderr, "vmipstool: --make-rom takes 2 arguments\n");
306 		usage();
307 		return 1;
308 	}
309 	sprintf (tmp, "/tmp/vmipstool-%d-1", (int) getpid ());
310 	inputfile = argv[0];
311 	outputfile = argv[1];
312 	if (!program_available (objcopyname))
313 		return config_error ("objcopy", objcopyname);
314 	if (echo_and_run_l (objcopyname, "-O", "binary", inputfile, tmp, NULL) != 0) {
315 		fprintf(stderr, "vmipstool: Error creating binary image of program.  Aborting.\n");
316 		return 1;
317 	}
318 	if (verbose)
319 		printf ("dd if=%s of=%s bs=%ld conv=sync > /dev/null 2>&1\n",
320 				tmp, outputfile, pagesz);
321 	if (copy_with_padded_blocks(tmp, outputfile, pagesz) != 0) {
322 		fprintf(stderr,
323 				"vmipstool: Error copying program image to ROM file.  Aborting.\n");
324 		return 1;
325 	}
326 	if (verbose)
327 		printf("rm %s\n", tmp);
328 	unlink(tmp);
329 	return 0;
330 }
331 
332 /********************************* Byte-swapping ******************************/
333 
swap_words(int argc,char ** argv)334 int swap_words (int argc, char **argv) {
335 	char *inputfname = argv[0];
336 	char *outputfname = argv[1];
337 	FILE *inputfp = fopen(inputfname, "rb");
338 	if (!inputfp) {
339 		perror(inputfname);
340 		return -1;
341 	}
342 	FILE *outputfp = fopen(outputfname, "wb");
343 	if (!outputfp) {
344 		perror(outputfname);
345 		return -1;
346 	}
347 	char buff[4], buff2[4];
348 	int readcount, writecount;
349 	while ((readcount = fread(buff, 1, 4, inputfp)) > 0) {
350 		if (readcount < 4) {
351 			fprintf(stderr,
352 					"%s: warning: file does not end on a word boundary\n",
353 					inputfname);
354 			for (int i = readcount; i < 3; i++)
355 				buff[i] = 0;
356 		}
357 		buff2[0] = buff[3];
358 		buff2[1] = buff[2];
359 		buff2[2] = buff[1];
360 		buff2[3] = buff[0];
361 		if ((writecount = fwrite(buff2, 1, 4, outputfp)) != 4) {
362 			perror(outputfname);
363 			break;
364 		}
365 	}
366 	fclose(inputfp);
367 	fclose(outputfp);
368 	return 0;
369 }
370 
371 /******************************** Everything else *****************************/
372 
compile(int argc,char ** argv)373 int compile (int argc, char **argv) {
374 	if (!program_available (ccname)) return config_error ("gcc", ccname);
375 	return echo_and_run_lv (ccname, endianflag, "-mno-abicalls",
376 				"-fno-pic", NULL, argv, NULL);
377 }
378 
assemble(int argc,char ** argv)379 int assemble (int argc, char **argv) {
380 	if (!program_available (ccname)) return config_error ("gcc", ccname);
381 	return echo_and_run_lv (ccname, "-c", "-x", "assembler-with-cpp",
382 				endianflag, "-fno-pic", NULL, argv, NULL);
383 }
384 
preprocess(int argc,char ** argv)385 int preprocess (int argc, char **argv) {
386 	if (!program_available (ccname)) return config_error ("gcc", ccname);
387 	return echo_and_run_lv(ccname, "-E", NULL, argv, NULL);
388 }
389 
disassemble(int argc,char ** argv)390 int disassemble (int argc, char **argv) {
391 	char *inputfile = argv[0];
392 	if (!program_available (objdumpname))
393 		return config_error ("objdump", objdumpname);
394 	return echo_and_run_l (objdumpname, "--disassemble", endianflag,
395                            inputfile, NULL);
396 }
397 
disassemble_rom(int argc,char ** argv)398 int disassemble_rom (int argc, char **argv) {
399 	char *inputfile = argv[0];
400 	if (!program_available (objdumpname))
401 		return config_error ("objdump", objdumpname);
402 	return echo_and_run_l (objdumpname, "--disassemble-all", "--target=binary",
403                            endianflag, "-m", "mips", inputfile, NULL);
404 }
405 
disassemble_word(int argc,char ** argv)406 int disassemble_word (int argc, char **argv) {
407   EndianSelfTester est;
408   Disassembler d (est.host_is_big_endian(), stdout);
409   if (argc != 2) {
410     fprintf (stderr, "Error: must supply PC and INSTR\n");
411     return 1;
412   }
413   uint32 pc = strtoul(argv[0], NULL, 0);
414   uint32 instr = strtoul(argv[1], NULL, 0);
415   d.disassemble (pc, instr);
416   return 0;
417 }
418 
version(int argc,char ** argv)419 int version (int argc, char **argv) {
420 	printf("vmipstool (VMIPS) %s\n", VERSION);
421 	return 0;
422 }
423 
help(int argc,char ** argv)424 int help (int argc, char **argv) {
425 	usage();
426 	return 0;
427 }
428 
429 /******************************** Options handling ****************************/
430 
431 int (*handler) (int argc, char **argv);
432 char **handler_argv = 0;
433 int handler_argc = -1;
434 
435 class VmipstoolOptions : public Options {
436 public:
VmipstoolOptions()437   VmipstoolOptions () : Options () {
438   }
~VmipstoolOptions()439   virtual ~VmipstoolOptions () { }
440 
usage(char * argv0)441   virtual void usage (char *argv0)
442   {
443     printf ("usage:\n"
444             " %s [ VT-FLAGS ] MODE MODE-ARGS\n"
445             "Each MODE takes its own MODE-ARGS:\n"
446             "  MODE:              MODE-ARGS:\n"
447             "  --compile          [ FLAGS ] FILE.c -o FILE.o\n"
448             "  --preprocess       [ FLAGS ] FILE\n"
449             "  --assemble         [ FLAGS ] FILE.s -o FILE.o\n"
450             "  --link             [ FLAGS ] FILE1.o ... FILEn.o -o PROG\n"
451             "  --make-rom         PROG PROG.rom\n"
452             "  --disassemble-rom  PROG.rom\n"
453             "  --disassemble-word PC INSTR\n"
454             "  --disassemble      PROG (or FILE.o)\n"
455             "  --swap-words       INPUT OUTPUT\n"
456             "\n"
457             "VT-FLAGS may include:\n"
458             " --help         Display this help message and exit.\n"
459             " --version      Display the version of vmipstool and exit.\n"
460             " --verbose      Echo commands as they are run.\n"
461             " --dry-run      Don't actually run anything; use with --verbose.\n"
462             " --ld-script=T  Use T as ld script (instead of default script);\n"
463             "                use with --link.\n"
464             "\n" "Report bugs to <vmips@dgate.org>.\n", argv0);
465   }
466 
process_options(int argc,char ** argv)467   virtual void process_options (int argc, char **argv)
468   {
469     /* Get options from defaults. */
470     process_defaults ();
471 
472     /* Get default name of user's config file */
473     char user_config_filename[PATH_MAX] = "~/.vmipsrc";
474 
475     /* Process command line */
476     bool read_system_config_file = true;
477     std::vector<std::string> command_line_options;
478 
479     int i;
480     for (i = 1; (!handler) && (i < argc); ++i)
481       {
482         if (strcmp (argv[i], "--version") == 0)
483           {
484             print_package_version ("vmipstool", VERSION);
485             exit (0);
486           }
487         else if (strcmp (argv[i], "--help") == 0)
488           {
489             usage (argv[0]);
490             exit (0);
491           }
492         else if (strcmp (argv[i], "-o") == 0)
493           {
494             if (argc <= i + 1)
495               error_exit ("The -o flag requires an argument. Try %s --help",
496                            argv[0]);
497             command_line_options.push_back (argv[i + 1]);
498             ++i;
499           }
500         else if (strcmp (argv[i], "-F") == 0)
501           {
502             if (argc <= i + 1)
503               error_exit ("The -F flag requires an argument. Try %s --help",
504                            argv[0]);
505             strcpy (user_config_filename, argv[i + 1]);
506             ++i;
507           }
508         else if (strcmp (argv[i], "-n") == 0)
509           {
510             read_system_config_file = false;
511           }
512         else if (strcmp (argv[i], "--verbose") == 0)
513           {
514             verbose = 1;
515           }
516         else if (strcmp (argv[i], "--dry-run") == 0)
517           {
518             dryrun = 1;
519           }
520         else if (strncmp (argv[i], "--ld-script=", 12) == 0)
521           {
522             strcpy (ldscript_full_path, &argv[i][12]);
523             ldscript_error = !can_read_file (ldscript_full_path);
524           }
525         else if (strcmp (argv[i], "--compile") == 0)
526           {
527             handler = compile;
528           }
529         else if (strcmp (argv[i], "--assemble") == 0)
530           {
531             handler = assemble;
532           }
533         else if (strcmp (argv[i], "--preprocess") == 0)
534           {
535             handler = preprocess;
536           }
537         else if (strcmp (argv[i], "--link") == 0)
538           {
539             handler = do_link;
540           }
541         else if (strcmp (argv[i], "--make-rom") == 0)
542           {
543             handler = make_rom;
544           }
545         else if (strcmp (argv[i], "--disassemble-rom") == 0)
546           {
547             handler = disassemble_rom;
548           }
549         else if (strcmp (argv[i], "--disassemble-word") == 0)
550           {
551             handler = disassemble_word;
552           }
553         else if (strcmp (argv[i], "--disassemble") == 0)
554           {
555             handler = disassemble;
556           }
557         else if (strcmp (argv[i], "--swap-words") == 0)
558           {
559             handler = swap_words;
560           }
561         else                    /* Unrecognized arg. */
562           {
563             std::cerr << "Unrecognized arg: " << argv[i] << "\n";
564             usage (argv[0]);
565             exit (1);
566           }
567       }
568     if (!handler)
569       {
570         std::cerr << "Error: Missing mode arg.\n";
571         usage (argv[0]);        /* Arg list was incomplete somehow. */
572         exit (1);
573       }
574     handler_argc = argc - i;
575     handler_argv = argv + i;
576 
577     /* Get options from system configuration file */
578     if (read_system_config_file)
579       process_options_from_file (SYSTEM_CONFIG_FILE);
580 
581     /* Get options from user configuration file */
582     tilde_expand (user_config_filename);
583     process_options_from_file (user_config_filename);
584 
585     /* Process -o options saved from command line, above. */
586     for (std::vector<std::string>::iterator i =
587          command_line_options.begin (), e = command_line_options.end ();
588          i != e; ++i)
589       process_one_option (i->c_str ());
590   }
591 };
592 
593 int
main(int argc,char ** argv)594 main (int argc, char **argv)
595 {
596   /* Find the default ld script. */
597   ldscript_error = !can_read_default_ldscript ();
598 
599   /* Get the command line options. */
600   VmipstoolOptions *opt = new VmipstoolOptions;
601   opt->process_options (argc, argv);
602   setup_paths (opt->option ("bigendian")->flag,
603                opt->option ("mipstoolprefix")->str);
604 
605   /* Run the appropriate handler. */
606   if (!handler)
607     fatal_error ("handler was not set");
608   if (handler_argc == -1)
609     fatal_error ("handler_argc was not set");
610   if (!handler_argv)
611     fatal_error ("handler_argv was not set");
612   return handler (handler_argc, handler_argv);
613 }
614