OpenSolaris_b135/tools/aw/aw.c

Compare this file to the similar file:
Show the results in this format:

/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */

/*
 * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

/*
 * Wrapper for the GNU assembler to make it accept the Sun assembler
 * arguments where possible.
 *
 * There are several limitations; the Sun assembler takes multiple
 * source files, we only take one.
 *
 * -b, -s, -xF, -T plain not supported.
 * -S isn't supported either, because while GNU as does generate
 * listings with -a, there's no obvious mapping between sub-options.
 * -K pic, -K PIC not supported either, though it's not clear what
 * these actually do ..
 * -Qy (not supported) adds a string to the .comment section
 * describing the assembler version, while
 * -Qn (supported) suppresses the string (also the default).
 *
 * We also add '-#' support to see invocation lines..
 * We also add '-xarch=amd64' in case we need to feed the assembler
 * something different (or in case we need to invoke a different binary
 * altogether!)
 */

#include <sys/types.h>
#include <sys/wait.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <sys/param.h>

static const char *progname;
static int verbose;

struct aelist {
	int ael_argc;
	struct ae {
		struct ae *ae_next;
		char *ae_arg;
	} *ael_head, *ael_tail;
};

static struct aelist *
newael(void)
{
	return (calloc(sizeof (struct aelist), 1));
}

static void
newae(struct aelist *ael, const char *arg)
{
	struct ae *ae;

	ae = calloc(sizeof (*ae), 1);
	ae->ae_arg = strdup(arg);
	if (ael->ael_tail == NULL)
		ael->ael_head = ae;
	else
		ael->ael_tail->ae_next = ae;
	ael->ael_tail = ae;
	ael->ael_argc++;
}

static void
fixae_arg(struct ae *ae, const char *newarg)
{
	free(ae->ae_arg);
	ae->ae_arg = strdup(newarg);
}

static char **
aeltoargv(struct aelist *ael)
{
	struct ae *ae;
	char **argv;
	int argc;

	argv = calloc(sizeof (*argv), ael->ael_argc + 1);

	for (argc = 0, ae = ael->ael_head; ae; ae = ae->ae_next, argc++) {
		argv[argc] = ae->ae_arg;
		if (ae == ael->ael_tail)
			break;
	}

	return (argv);
}

static int
error(const char *arg)
{
	(void) fprintf(stderr,
	    "%s: as->gas mapping failed at or near arg '%s'\n", progname, arg);
	return (2);
}

static int
usage(const char *arg)
{
	if (arg != NULL)
		(void) fprintf(stderr, "error: %s\n", arg);
	(void) fprintf(stderr, "Usage: %s [-V] [-#]\n"
	    "\t[-xarch=architecture]\n"
	    "\t[-o objfile] [-L]\n"
	    "\t[-P [[-Ipath] [-Dname] [-Dname=def] [-Uname]]...]\n"
	    "\t[-m] [-n] file.s ...\n", progname);
	return (3);
}

static void
copyuntil(FILE *in, FILE *out, int termchar)
{
	int c;

	while ((c = fgetc(in)) != EOF) {
		if (out && fputc(c, out) == EOF)
			exit(1);
		if (c == termchar)
			break;
	}
}

/*
 * Variant of copyuntil(), used for copying the path used
 * for .file directives. This version removes the workspace
 * from the head of the path, or failing that, attempts to remove
 * /usr/include. This is a workaround for the way gas handles
 * these directives. The objects produced by gas contain STT_FILE
 * symbols for every .file directive. These FILE symbols contain our
 * workspace paths, leading to wsdiff incorrectly flagging them as
 * having changed. By clipping off the workspace from these paths,
 * we eliminate these false positives.
 */
static void
copyuntil_path(FILE *in, FILE *out, int termchar,
    const char *wspace, size_t wspace_len)
{
#define	PROTO_INC "/proto/root_i386/usr/include/"
#define	SYS_INC "/usr/include/"

	static const size_t proto_inc_len = sizeof (PROTO_INC) - 1;
	static const size_t sys_inc_len = sizeof (SYS_INC) - 1;

	/*
	 * Dynamically sized buffer for reading paths. Retained
	 * and reused between calls.
	 */
	static char	*buf = NULL;
	static size_t	bufsize = 0;

	size_t	bufcnt = 0;
	char	*bufptr;
	int	c;

	/* Read the path into the buffer */
	while ((c = fgetc(in)) != EOF) {
		/*
		 * If we need a buffer, or need a larger buffer,
		 * fix that here.
		 */
		if (bufcnt >= bufsize) {
			bufsize = (bufsize == 0) ? MAXPATHLEN : (bufsize * 2);
			buf = realloc(buf, bufsize + 1); /* + room for NULL */
			if (buf == NULL) {
				perror("realloc");
				exit(1);
			}
		}

		buf[bufcnt++] = c;
		if (c == termchar)
			break;
	}
	if (bufcnt == 0)
		return;

	/*
	 * We have a non-empty buffer, and thus the opportunity
	 * to do some surgery on it before passing it to the output.
	 */
	buf[bufcnt] = '\0';
	bufptr = buf;

	/*
	 * If our workspace is at the start, remove it.
	 * If not, then look for the system /usr/include instead.
	 */
	if ((wspace_len > 0) && (wspace_len < bufcnt) &&
	    (strncmp(bufptr, wspace, wspace_len) == 0)) {
		bufptr += wspace_len;
		bufcnt -= wspace_len;

		/*
		 * Further opportunity: Also clip the prefix
		 * that leads to /usr/include in the proto.
		 */
		if ((proto_inc_len < bufcnt) &&
		    (strncmp(bufptr, PROTO_INC, proto_inc_len) == 0)) {
			bufptr += proto_inc_len;
			bufcnt -= proto_inc_len;
		}
	} else if ((sys_inc_len < bufcnt) &&
	    (strncmp(bufptr, SYS_INC, sys_inc_len) == 0)) {
		bufptr += sys_inc_len;
		bufcnt -= sys_inc_len;
	}

	/* Output whatever is left */
	if (out && (fwrite(bufptr, 1, bufcnt, out) != bufcnt)) {
		perror("fwrite");
		exit(1);
	}

#undef PROTO_INC
#undef SYS_INC
}

/*
 * The idea here is to take directives like this emitted
 * by cpp:
 *
 *	# num
 *
 * and convert them to directives like this that are
 * understood by the GNU assembler:
 *
 *	.line num
 *
 * and similarly:
 *
 *	# num "string" optional stuff
 *
 * is converted to
 *
 *	.line num
 *	.file "string"
 *
 * While this could be done with a sequence of sed
 * commands, this is simpler and faster..
 */
static pid_t
filter(int pipein, int pipeout)
{
	pid_t pid;
	FILE *in, *out;
	char *wspace;
	size_t wspace_len;

	if (verbose)
		(void) fprintf(stderr, "{#line filter} ");

	switch (pid = fork()) {
	case 0:
		if (dup2(pipein, 0) == -1 ||
		    dup2(pipeout, 1) == -1) {
			perror("dup2");
			exit(1);
		}
		closefrom(3);
		break;
	case -1:
		perror("fork");
	default:
		return (pid);
	}

	in = fdopen(0, "r");
	out = fdopen(1, "w");

	/*
	 * Key off the CODEMGR_WS environment variable to detect
	 * if we're in an activated workspace, and to get the
	 * path to the workspace.
	 */
	wspace = getenv("CODEMGR_WS");
	if (wspace != NULL)
		wspace_len = strlen(wspace);

	while (!feof(in)) {
		int c, num;

		switch (c = fgetc(in)) {
		case '#':
			switch (fscanf(in, " %d", &num)) {
			case 0:
				/*
				 * discard comment lines completely
				 * discard ident strings completely too.
				 * (GNU as politely ignores them..)
				 */
				copyuntil(in, NULL, '\n');
				break;
			default:
				(void) fprintf(stderr, "fscanf botch?");
				/*FALLTHROUGH*/
			case EOF:
				exit(1);
				/*NOTREACHED*/
			case 1:
				/*
				 * This line has a number at the beginning;
				 * if it has a string after the number, then
				 * it's a filename.
				 *
				 * If this is an activated workspace, use
				 * copyuntil_path() to do path rewriting
				 * that will prevent workspace paths from
				 * being burned into the resulting object.
				 * If not in an activated workspace, then
				 * copy the existing path straight through
				 * without interpretation.
				 */
				if (fgetc(in) == ' ' && fgetc(in) == '"') {
					(void) fprintf(out, "\t.file \"");
					if (wspace != NULL)
						copyuntil_path(in, out, '"',
						    wspace, wspace_len);
					else
						copyuntil(in, out, '"');
					(void) fputc('\n', out);
				}
				(void) fprintf(out, "\t.line %d\n", num - 1);
				/*
				 * discard the rest of the line
				 */
				copyuntil(in, NULL, '\n');
				break;
			}
			break;
		case '\n':
			/*
			 * preserve newlines
			 */
			(void) fputc(c, out);
			break;
		case EOF:
			/*
			 * don't write EOF!
			 */
			break;
		default:
			/*
			 * lines that don't begin with '#' are copied
			 */
			(void) fputc(c, out);
			copyuntil(in, out, '\n');
			break;
		}

		if (ferror(out))
			exit(1);
	}

	exit(0);
	/*NOTREACHED*/
}

static pid_t
invoke(char **argv, int pipein, int pipeout)
{
	pid_t pid;

	if (verbose) {
		char **dargv = argv;

		while (*dargv)
			(void) fprintf(stderr, "%s ", *dargv++);
	}

	switch (pid = fork()) {
	case 0:
		if (pipein >= 0 && dup2(pipein, 0) == -1) {
			perror("dup2");
			exit(1);
		}
		if (pipeout >= 0 && dup2(pipeout, 1) == -1) {
			perror("dup2");
			exit(1);
		}
		closefrom(3);
		(void) execvp(argv[0], argv);
		perror("execvp");
		(void) fprintf(stderr, "%s: couldn't run %s\n",
		    progname, argv[0]);
		break;
	case -1:
		perror("fork");
	default:
		return (pid);
	}
	exit(2);
	/*NOTREACHED*/
}

static int
pipeline(char **ppargv, char **asargv)
{
	int pipedes[4];
	int active = 0;
	int rval = 0;
	pid_t pid_pp, pid_f, pid_as;

	if (pipe(pipedes) == -1 || pipe(pipedes + 2) == -1) {
		perror("pipe");
		return (4);
	}

	if ((pid_pp = invoke(ppargv, -1, pipedes[0])) > 0)
		active++;

	if (verbose)
		(void) fprintf(stderr, "| ");

	if ((pid_f = filter(pipedes[1], pipedes[2])) > 0)
		active++;

	if (verbose)
		(void) fprintf(stderr, "| ");

	if ((pid_as = invoke(asargv, pipedes[3], -1)) > 0)
		active++;

	if (verbose) {
		(void) fprintf(stderr, "\n");
		(void) fflush(stderr);
	}

	closefrom(3);

	if (active != 3)
		return (5);

	while (active != 0) {
		pid_t pid;
		int stat;

		if ((pid = wait(&stat)) == -1) {
			rval++;
			break;
		}

		if (!WIFEXITED(stat))
			continue;

		if (pid == pid_pp || pid == pid_f || pid == pid_as) {
			active--;
			if (WEXITSTATUS(stat) != 0)
				rval++;
		}
	}

	return (rval);
}

int
main(int argc, char *argv[])
{
	struct aelist *cpp = NULL;
	struct aelist *m4 = NULL;
	struct aelist *as = newael();
	char **asargv;
	char *outfile = NULL;
	char *srcfile = NULL;
	const char *as_dir, *as64_dir, *m4_dir, *m4_lib_dir, *cpp_dir;
	char *as_pgm, *as64_pgm, *m4_pgm, *m4_cmdefs, *cpp_pgm;
	size_t bufsize;
	int as64 = 0;
	int code;

	if ((progname = strrchr(argv[0], '/')) == NULL)
		progname = argv[0];
	else
		progname++;

	/*
	 * Helpful when debugging, or when changing tool versions..
	 */
	if ((as_dir = getenv("AW_AS_DIR")) == NULL)
		as_dir = DEFAULT_AS_DIR;	/* /usr/sfw/bin */
	bufsize = strlen(as_dir) + strlen("/gas") + 1;
	as_pgm = malloc(bufsize);
	(void) snprintf(as_pgm, bufsize, "%s/gas", as_dir);

	if ((as64_dir = getenv("AW_AS64_DIR")) == NULL)
		as64_dir = DEFAULT_AS64_DIR;	/* /usr/sfw/bin */
	bufsize = strlen(as64_dir) + strlen("/gas") + 1;
	as64_pgm = malloc(bufsize);
	(void) snprintf(as64_pgm, bufsize, "%s/gas", as64_dir);

	if ((m4_dir = getenv("AW_M4_DIR")) == NULL)
		m4_dir = DEFAULT_M4_DIR;	/* /usr/ccs/bin */
	bufsize = strlen(m4_dir) + strlen("/m4") + 1;
	m4_pgm = malloc(bufsize);
	(void) snprintf(m4_pgm, bufsize, "%s/m4", m4_dir);

	if ((m4_lib_dir = getenv("AW_M4LIB_DIR")) == NULL)
		m4_lib_dir = DEFAULT_M4LIB_DIR;	/* /usr/ccs/lib */
	bufsize = strlen(m4_lib_dir) + strlen("/cmdefs") + 1;
	m4_cmdefs = malloc(bufsize);
	(void) snprintf(m4_cmdefs, bufsize, "%s/cmdefs", m4_lib_dir);

	if ((cpp_dir = getenv("AW_CPP_DIR")) == NULL)
		cpp_dir = DEFAULT_CPP_DIR;	/* /usr/ccs/lib */
	bufsize = strlen(cpp_dir) + strlen("/cpp") + 1;
	cpp_pgm = malloc(bufsize);
	(void) snprintf(cpp_pgm, bufsize, "%s/cpp", cpp_dir);

	newae(as, as_pgm);
	newae(as, "--warn");
	newae(as, "--fatal-warnings");
	newae(as, "--traditional-format");

	/*
	 * This is a support hack to rewrite code for the compiler
	 * which should probably cause an assembler programmer to recode
	 * - so, generate a warning in this case.
	 *
	 * -K was dropped begining with version 2.18.
	 */
	{
		struct aelist *as_ver = newael();
		struct aelist *ggrep = newael();

		newae(as_ver, as_pgm);
		newae(as_ver, "--version");
		newae(ggrep, "/usr/bin/ggrep");
		newae(ggrep, "-q");
		newae(ggrep, "-E");
		newae(ggrep, "2.1[567]");
		code = pipeline(aeltoargv(as_ver), aeltoargv(ggrep));
		if (code == 0) {
			newae(as, "-K");
		}
	}

	/*
	 * Walk the argument list, translating as we go ..
	 */
	while (--argc > 0) {
		char *arg;
		int arglen;

		arg = *++argv;
		arglen = strlen(arg);

		if (*arg != '-') {
			char *filename;

			/*
			 * filenames ending in '.s' are taken to be
			 * assembler files, and provide the default
			 * basename of the output file.
			 *
			 * other files are passed through to the
			 * preprocessor, if present, or to gas if not.
			 */
			filename = arg;
			if (arglen > 2 &&
			    strcmp(arg + arglen - 2, ".s") == 0) {
				/*
				 * Though 'as' allows multiple assembler
				 * files to be processed in one invocation
				 * of the assembler, ON only processes one
				 * file at a time, which makes things a lot
				 * simpler!
				 */
				if (srcfile == NULL)
					srcfile = arg;
				else
					return (usage(
					    "one assembler file at a time"));

				/*
				 * If we haven't seen a -o option yet,
				 * default the output to the basename
				 * of the input, substituting a .o on the end
				 */
				if (outfile == NULL) {
					char *argcopy;

					argcopy = strdup(arg);
					argcopy[arglen - 1] = 'o';

					if ((outfile = strrchr(
					    argcopy, '/')) == NULL)
						outfile = argcopy;
					else
						outfile++;
				}
			}
			if (cpp)
				newae(cpp, filename);
			else if (m4)
				newae(m4, filename);
			else
				newae(as, filename);
			continue;
		} else
			arglen--;

		switch (arg[1]) {
		case 'K':
			/*
			 * -K pic
			 * -K PIC
			 */
			if (arglen == 1) {
				if ((arg = *++argv) == NULL || *arg == '\0')
					return (usage("malformed -K"));
				argc--;
			} else {
				arg += 2;
			}
			if (strcmp(arg, "PIC") != 0 && strcmp(arg, "pic") != 0)
				return (usage("malformed -K"));
			break;		/* just ignore -Kpic for gcc */
		case 'Q':
			if (strcmp(arg, "-Qn") == 0)
				break;
			/*FALLTHROUGH*/
		case 'b':
		case 's':
		case 'T':
			/*
			 * -b	Extra symbol table for source browser ..
			 *	not relevant to gas, thus should error.
			 * -s	Put stabs in .stabs section not stabs.excl
			 *	not clear if there's an equivalent
			 * -T	4.x migration option
			 */
		default:
			return (error(arg));
		case 'x':
			/*
			 * Accept -xarch special case to invoke alternate
			 * assemblers or assembler flags for different
			 * architectures.
			 */
			if (strcmp(arg, "-xarch=amd64") == 0 ||
			    strcmp(arg, "-xarch=generic64") == 0) {
				as64++;
				fixae_arg(as->ael_head, as64_pgm);
				break;
			}
			/*
			 * XX64: Is this useful to gas?
			 */
			if (strcmp(arg, "-xmodel=kernel") == 0)
				break;

			/*
			 * -xF	Generates performance analysis data
			 *	no equivalent
			 */
			return (error(arg));
		case 'V':
			newae(as, arg);
			break;
		case '#':
			verbose++;
			break;
		case 'L':
			newae(as, "--keep-locals");
			break;
		case 'n':
			newae(as, "--no-warn");
			break;
		case 'o':
			if (arglen != 1)
				return (usage("bad -o flag"));
			if ((arg = *++argv) == NULL || *arg == '\0')
				return (usage("bad -o flag"));
			outfile = arg;
			argc--;
			arglen = strlen(arg + 1);
			break;
		case 'm':
			if (cpp)
				return (usage("-m conflicts with -P"));
			if (m4 == NULL) {
				m4 = newael();
				newae(m4, m4_pgm);
				newae(m4, m4_cmdefs);
			}
			break;
		case 'P':
			if (m4)
				return (usage("-P conflicts with -m"));
			if (cpp == NULL) {
				cpp = newael();
				newae(cpp, cpp_pgm);
				newae(cpp, "-D__GNUC_AS__");
			}
			break;
		case 'D':
		case 'U':
			if (cpp)
				newae(cpp, arg);
			else if (m4)
				newae(m4, arg);
			else
				newae(as, arg);
			break;
		case 'I':
			if (cpp)
				newae(cpp, arg);
			else
				newae(as, arg);
			break;
		case '-':	/* a gas-specific option */
			newae(as, arg);
			break;
		}
	}

#if defined(__i386)
	if (as64)
		newae(as, "--64");
	else
		newae(as, "--32");
#endif

	if (srcfile == NULL)
		return (usage("no source file(s) specified"));
	if (outfile == NULL)
		outfile = "a.out";
	newae(as, "-o");
	newae(as, outfile);

	asargv = aeltoargv(as);
	if (cpp) {
#if defined(__sparc)
		newae(cpp, "-Dsparc");
		newae(cpp, "-D__sparc");
		if (as64)
			newae(cpp, "-D__sparcv9");
		else
			newae(cpp, "-D__sparcv8");
#elif defined(__i386) || defined(__x86)
		if (as64) {
			newae(cpp, "-D__x86_64");
			newae(cpp, "-D__amd64");
		} else {
			newae(cpp, "-Di386");
			newae(cpp, "-D__i386");
		}
#else
#error	"need isa-dependent defines"
#endif
		code = pipeline(aeltoargv(cpp), asargv);
	} else if (m4)
		code = pipeline(aeltoargv(m4), asargv);
	else {
		/*
		 * XXX	should arrange to fork/exec so that we
		 *	can unlink the output file if errors are
		 *	detected..
		 */
		(void) execvp(asargv[0], asargv);
		perror("execvp");
		(void) fprintf(stderr, "%s: couldn't run %s\n",
		    progname, asargv[0]);
		code = 7;
	}
	if (code != 0)
		(void) unlink(outfile);
	return (code);
}