nasm/asm/nasm.c
H. Peter Anvin 03490692b0 BR 3392832: fix dependency list generation
The dependency list needs to be updated for all passes, not just the
last one. The dependency list is already uniquized, so it doesn't
cause problems with multiple entires.

The reasons it needs to be done for all passes is first of all that an
%include could be pass-dependent, and secondly that we only record a
dependency for an %include or %require for the first occurrence of
that file, when pathnames are resolved.

Reported-by <michael@mehlich@com>
Signed-off-by: H. Peter Anvin <hpa@zytor.com>
2023-10-11 14:35:49 -07:00

2353 lines
68 KiB
C

/* ----------------------------------------------------------------------- *
*
* Copyright 1996-2022 The NASM Authors - All Rights Reserved
* See the file AUTHORS included with the NASM distribution for
* the specific copyright holders.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following
* conditions are met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following
* disclaimer in the documentation and/or other materials provided
* with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
* CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* ----------------------------------------------------------------------- */
/*
* The Netwide Assembler main program module
*/
#include "compiler.h"
#include "nasm.h"
#include "nasmlib.h"
#include "nctype.h"
#include "error.h"
#include "saa.h"
#include "raa.h"
#include "floats.h"
#include "stdscan.h"
#include "insns.h"
#include "preproc.h"
#include "parser.h"
#include "eval.h"
#include "assemble.h"
#include "labels.h"
#include "outform.h"
#include "listing.h"
#include "iflag.h"
#include "quote.h"
#include "ver.h"
/*
* This is the maximum number of optimization passes to do. If we ever
* find a case where the optimizer doesn't naturally converge, we might
* have to drop this value so the assembler doesn't appear to just hang.
*/
#define MAX_OPTIMIZE (INT_MAX >> 1)
struct forwrefinfo { /* info held on forward refs. */
int lineno;
int operand;
};
const char *_progname;
static void parse_cmdline(int, char **, int);
static void assemble_file(const char *, struct strlist *);
static bool skip_this_pass(errflags severity);
static void usage(void);
static void help(FILE *);
struct error_format {
const char *beforeline; /* Before line number, if present */
const char *afterline; /* After line number, if present */
const char *beforemsg; /* Before actual message */
};
static const struct error_format errfmt_gnu = { ":", "", ": " };
static const struct error_format errfmt_msvc = { "(", ")", " : " };
static const struct error_format *errfmt = &errfmt_gnu;
static struct strlist *warn_list;
static struct nasm_errhold *errhold_stack;
unsigned int debug_nasm; /* Debugging messages? */
static bool using_debug_info, opt_verbose_info;
static const char *debug_format;
#ifndef ABORT_ON_PANIC
# define ABORT_ON_PANIC 0
#endif
static bool abort_on_panic = ABORT_ON_PANIC;
static bool keep_all;
bool tasm_compatible_mode = false;
enum pass_type _pass_type;
const char * const _pass_types[] =
{
"init", "preproc-only", "first", "optimize", "stabilize", "final"
};
int64_t _passn;
int globalrel = 0;
int globalbnd = 0;
struct compile_time official_compile_time;
const char *inname;
const char *outname;
static const char *listname;
static const char *errname;
static int64_t globallineno; /* for forward-reference tracking */
const struct ofmt *ofmt = &OF_DEFAULT;
const struct ofmt_alias *ofmt_alias = NULL;
const struct dfmt *dfmt;
FILE *error_file; /* Where to write error messages */
FILE *ofile = NULL;
struct optimization optimizing =
{ MAX_OPTIMIZE, OPTIM_ALL_ENABLED }; /* number of optimization passes to take */
static int cmd_sb = 16; /* by default */
iflag_t cpu, cmd_cpu;
struct location location;
bool in_absolute; /* Flag we are in ABSOLUTE seg */
struct location absolute; /* Segment/offset inside ABSOLUTE */
static struct RAA *offsets;
static struct SAA *forwrefs; /* keep track of forward references */
static const struct forwrefinfo *forwref;
static struct strlist *include_path;
static enum preproc_opt ppopt;
#define OP_NORMAL (1U << 0)
#define OP_PREPROCESS (1U << 1)
#define OP_DEPEND (1U << 2)
static unsigned int operating_mode;
/* Dependency flags */
static bool depend_emit_phony = false;
static bool depend_missing_ok = false;
static const char *depend_target = NULL;
static const char *depend_file = NULL;
struct strlist *depend_list;
static bool want_usage;
static bool terminate_after_phase;
bool user_nolist = false;
static char *quote_for_pmake(const char *str);
static char *quote_for_wmake(const char *str);
static char *(*quote_for_make)(const char *) = quote_for_pmake;
/*
* Execution limits that can be set via a command-line option or %pragma
*/
/*
* This is really unlimited; it would take far longer than the
* current age of the universe for this limit to be reached even on
* much faster CPUs than currently exist.
*/
#define LIMIT_MAX_VAL (INT64_MAX >> 1)
int64_t nasm_limit[LIMIT_MAX+1];
struct limit_info {
const char *name;
const char *help;
int64_t default_val;
};
/* The order here must match enum nasm_limit in nasm.h */
static const struct limit_info limit_info[LIMIT_MAX+1] = {
{ "passes", "total number of passes", LIMIT_MAX_VAL },
{ "stalled-passes", "number of passes without forward progress", 1000 },
{ "macro-levels", "levels of macro expansion", 10000 },
{ "macro-tokens", "tokens processed during single-lime macro expansion", 10000000 },
{ "mmacros", "multi-line macros before final return", 100000 },
{ "rep", "%rep count", 1000000 },
{ "eval", "expression evaluation descent", 8192 },
{ "lines", "total source lines processed", 2000000000 }
};
static void set_default_limits(void)
{
int i;
size_t rl;
int64_t new_limit;
for (i = 0; i <= LIMIT_MAX; i++)
nasm_limit[i] = limit_info[i].default_val;
/*
* Try to set a sensible default value for the eval depth based
* on the limit of the stack size, if knowable...
*/
rl = nasm_get_stack_size_limit();
new_limit = rl / (128 * sizeof(void *)); /* Sensible heuristic */
if (new_limit < nasm_limit[LIMIT_EVAL])
nasm_limit[LIMIT_EVAL] = new_limit;
}
enum directive_result
nasm_set_limit(const char *limit, const char *valstr)
{
int i;
int64_t val;
bool rn_error;
int errlevel;
if (!limit)
limit = "";
if (!valstr)
valstr = "";
for (i = 0; i <= LIMIT_MAX; i++) {
if (!nasm_stricmp(limit, limit_info[i].name))
break;
}
if (i > LIMIT_MAX) {
if (not_started())
errlevel = ERR_WARNING|WARN_OTHER|ERR_USAGE;
else
errlevel = ERR_WARNING|WARN_PRAGMA_UNKNOWN;
nasm_error(errlevel, "unknown limit: `%s'", limit);
return DIRR_ERROR;
}
if (!nasm_stricmp(valstr, "unlimited")) {
val = LIMIT_MAX_VAL;
} else {
val = readnum(valstr, &rn_error);
if (rn_error || val < 0) {
if (not_started())
errlevel = ERR_WARNING|WARN_OTHER|ERR_USAGE;
else
errlevel = ERR_WARNING|WARN_PRAGMA_BAD;
nasm_error(errlevel, "invalid limit value: `%s'", valstr);
return DIRR_ERROR;
}
if (val > LIMIT_MAX_VAL)
val = LIMIT_MAX_VAL;
}
nasm_limit[i] = val;
return DIRR_OK;
}
int64_t switch_segment(int32_t segment)
{
location.segment = segment;
if (segment == NO_SEG) {
location.offset = absolute.offset;
in_absolute = true;
} else {
location.offset = raa_read(offsets, segment);
in_absolute = false;
}
return location.offset;
}
static void set_curr_offs(int64_t l_off)
{
if (in_absolute)
absolute.offset = l_off;
else
offsets = raa_write(offsets, location.segment, l_off);
}
static void increment_offset(int64_t delta)
{
if (unlikely(delta == 0))
return;
location.offset += delta;
set_curr_offs(location.offset);
}
/*
* Define system-defined macros that are not part of
* macros/standard.mac.
*/
static void define_macros(void)
{
const struct compile_time * const oct = &official_compile_time;
char temp[128];
if (oct->have_local) {
strftime(temp, sizeof temp, "__?DATE?__=\"%Y-%m-%d\"", &oct->local);
pp_pre_define(temp);
strftime(temp, sizeof temp, "__?DATE_NUM?__=%Y%m%d", &oct->local);
pp_pre_define(temp);
strftime(temp, sizeof temp, "__?TIME?__=\"%H:%M:%S\"", &oct->local);
pp_pre_define(temp);
strftime(temp, sizeof temp, "__?TIME_NUM?__=%H%M%S", &oct->local);
pp_pre_define(temp);
}
if (oct->have_gm) {
strftime(temp, sizeof temp, "__?UTC_DATE?__=\"%Y-%m-%d\"", &oct->gm);
pp_pre_define(temp);
strftime(temp, sizeof temp, "__?UTC_DATE_NUM?__=%Y%m%d", &oct->gm);
pp_pre_define(temp);
strftime(temp, sizeof temp, "__?UTC_TIME?__=\"%H:%M:%S\"", &oct->gm);
pp_pre_define(temp);
strftime(temp, sizeof temp, "__?UTC_TIME_NUM?__=%H%M%S", &oct->gm);
pp_pre_define(temp);
}
if (oct->have_posix) {
snprintf(temp, sizeof temp, "__?POSIX_TIME?__=%"PRId64, oct->posix);
pp_pre_define(temp);
}
/*
* In case if output format is defined by alias
* we have to put shortname of the alias itself here
* otherwise ABI backward compatibility gets broken.
*/
snprintf(temp, sizeof(temp), "__?OUTPUT_FORMAT?__=%s",
ofmt_alias ? ofmt_alias->shortname : ofmt->shortname);
pp_pre_define(temp);
/*
* Output-format specific macros.
*/
if (ofmt->stdmac)
pp_extra_stdmac(ofmt->stdmac);
/*
* Debug format, if any
*/
if (dfmt != &null_debug_form) {
snprintf(temp, sizeof(temp), "__?DEBUG_FORMAT?__=%s", dfmt->shortname);
pp_pre_define(temp);
}
}
/*
* Initialize the preprocessor, set up the include path, and define
* the system-included macros. This is called between passes 1 and 2
* of parsing the command options; ofmt and dfmt are defined at this
* point.
*
* Command-line specified preprocessor directives (-p, -d, -u,
* --pragma, --before) are processed after this function.
*/
static void preproc_init(struct strlist *ipath)
{
pp_init(ppopt);
define_macros();
pp_include_path(ipath);
}
static void emit_dependencies(struct strlist *list)
{
FILE *deps;
int linepos, len;
bool wmake = (quote_for_make == quote_for_wmake);
const char *wrapstr, *nulltarget;
const struct strlist_entry *l;
if (!list)
return;
wrapstr = wmake ? " &\n " : " \\\n ";
nulltarget = wmake ? "\t%null\n" : "";
if (depend_file && strcmp(depend_file, "-")) {
deps = nasm_open_write(depend_file, NF_TEXT);
if (!deps) {
nasm_nonfatal("unable to write dependency file `%s'", depend_file);
return;
}
} else {
deps = stdout;
}
linepos = fprintf(deps, "%s :", depend_target);
strlist_for_each(l, list) {
char *file = quote_for_make(l->str);
len = strlen(file);
if (linepos + len > 62 && linepos > 1) {
fputs(wrapstr, deps);
linepos = 1;
}
fprintf(deps, " %s", file);
linepos += len+1;
nasm_free(file);
}
fputs("\n\n", deps);
strlist_for_each(l, list) {
if (depend_emit_phony) {
char *file = quote_for_make(l->str);
fprintf(deps, "%s :\n%s\n", file, nulltarget);
nasm_free(file);
}
}
strlist_free(&list);
if (deps != stdout)
fclose(deps);
}
/* Convert a struct tm to a POSIX-style time constant */
static int64_t make_posix_time(const struct tm *tm)
{
int64_t t;
int64_t y = tm->tm_year;
/* See IEEE 1003.1:2004, section 4.14 */
t = (y-70)*365 + (y-69)/4 - (y-1)/100 + (y+299)/400;
t += tm->tm_yday;
t *= 24;
t += tm->tm_hour;
t *= 60;
t += tm->tm_min;
t *= 60;
t += tm->tm_sec;
return t;
}
/*
* Quote a filename string if and only if it is necessary.
* It is considered necessary if any one of these is true:
* 1. The filename contains control characters;
* 2. The filename starts or ends with a space or quote mark;
* 3. The filename contains more than one space in a row;
* 4. The filename is empty.
*
* The filename is returned in a newly allocated buffer.
*/
static char *nasm_quote_filename(const char *fn)
{
const unsigned char *p =
(const unsigned char *)fn;
size_t len;
if (!p || !*p)
return nasm_strdup("\"\"");
if (*p <= ' ' || nasm_isquote(*p)) {
goto quote;
} else {
unsigned char cutoff = ' ';
while (*p) {
if (*p < cutoff)
goto quote;
cutoff = ' ' + (*p == ' ');
p++;
}
if (p[-1] <= ' ' || nasm_isquote(p[-1]))
goto quote;
}
/* Quoting not necessary */
return nasm_strdup(fn);
quote:
len = strlen(fn);
return nasm_quote(fn, &len);
}
static void timestamp(void)
{
struct compile_time * const oct = &official_compile_time;
const struct tm *tp, *best_gm;
time(&oct->t);
best_gm = NULL;
tp = localtime(&oct->t);
if (tp) {
oct->local = *tp;
best_gm = &oct->local;
oct->have_local = true;
}
tp = gmtime(&oct->t);
if (tp) {
oct->gm = *tp;
best_gm = &oct->gm;
oct->have_gm = true;
if (!oct->have_local)
oct->local = oct->gm;
} else {
oct->gm = oct->local;
}
if (best_gm) {
oct->posix = make_posix_time(best_gm);
oct->have_posix = true;
}
}
int main(int argc, char **argv)
{
/* Do these as early as possible */
error_file = stderr;
_progname = argv[0];
if (!_progname || !_progname[0])
_progname = "nasm";
timestamp();
set_cpu(NULL);
cmd_cpu = cpu;
set_default_limits();
include_path = strlist_alloc(true);
_pass_type = PASS_INIT;
_passn = 0;
want_usage = terminate_after_phase = false;
nasm_ctype_init();
src_init();
/*
* We must call init_labels() before the command line parsing,
* because we may be setting prefixes/suffixes from the command
* line.
*/
init_labels();
offsets = raa_init();
forwrefs = saa_init((int32_t)sizeof(struct forwrefinfo));
operating_mode = OP_NORMAL;
parse_cmdline(argc, argv, 1);
if (terminate_after_phase) {
if (want_usage)
usage();
return 1;
}
/* At this point we have ofmt and the name of the desired debug format */
if (!using_debug_info) {
/* No debug info, redirect to the null backend (empty stubs) */
dfmt = &null_debug_form;
} else if (!debug_format) {
/* Default debug format for this backend */
dfmt = ofmt->default_dfmt;
} else {
dfmt = dfmt_find(ofmt, debug_format);
if (!dfmt) {
nasm_fatalf(ERR_USAGE, "unrecognized debug format `%s' for output format `%s'",
debug_format, ofmt->shortname);
}
}
/* Have we enabled TASM mode? */
if (tasm_compatible_mode) {
ppopt |= PP_TASM;
nasm_ctype_tasm_mode();
}
preproc_init(include_path);
parse_cmdline(argc, argv, 2);
if (terminate_after_phase) {
if (want_usage)
usage();
return 1;
}
/* Save away the default state of warnings */
init_warnings();
/* Dependency filename if we are also doing other things */
if (!depend_file && (operating_mode & ~OP_DEPEND)) {
if (outname)
depend_file = nasm_strcat(outname, ".d");
else
depend_file = filename_set_extension(inname, ".d");
}
/*
* If no output file name provided and this
* is preprocess mode, we're perfectly
* fine to output into stdout.
*/
if (!outname && !(operating_mode & OP_PREPROCESS)) {
outname = filename_set_extension(inname, ofmt->extension);
if (!strcmp(outname, inname)) {
outname = "nasm.out";
nasm_warn(WARN_OTHER, "default output file same as input, using `%s' for output\n", outname);
}
}
depend_list = (operating_mode & OP_DEPEND) ? strlist_alloc(true) : NULL;
if (!depend_target)
depend_target = quote_for_make(outname);
if (!(operating_mode & (OP_PREPROCESS|OP_NORMAL))) {
char *line;
if (depend_missing_ok)
pp_include_path(NULL); /* "assume generated" */
pp_reset(inname, PP_DEPS, depend_list);
ofile = NULL;
while ((line = pp_getline()))
nasm_free(line);
pp_cleanup_pass();
reset_warnings();
} else if (operating_mode & OP_PREPROCESS) {
char *line;
const char *file_name = NULL;
char *quoted_file_name = nasm_quote_filename(file_name);
int32_t linnum = 0;
int32_t lineinc = 0;
FILE *out;
if (outname) {
ofile = nasm_open_write(outname, NF_TEXT);
if (!ofile)
nasm_fatal("unable to open output file `%s'", outname);
out = ofile;
} else {
ofile = NULL;
out = stdout;
}
location.known = false;
_pass_type = PASS_PREPROC;
pp_reset(inname, PP_PREPROC, depend_list);
while ((line = pp_getline())) {
/*
* We generate %line directives if needed for later programs
*/
struct src_location where = src_where();
if (file_name != where.filename) {
file_name = where.filename;
linnum = -1; /* Force a new %line statement */
lineinc = file_name ? 1 : 0;
nasm_free(quoted_file_name);
quoted_file_name = nasm_quote_filename(file_name);
} else if (lineinc) {
if (linnum + lineinc == where.lineno) {
/* Add one blank line to account for increment */
fputc('\n', out);
linnum += lineinc;
} else if (linnum - lineinc == where.lineno) {
/*
* Standing still, probably a macro. Set increment
* to zero.
*/
lineinc = 0;
}
} else {
/* lineinc == 0 */
if (linnum + 1 == where.lineno)
lineinc = 1;
}
/* Skip blank lines if we will need a %line anyway */
if (linnum == -1 && !line[0])
continue;
if (linnum != where.lineno) {
fprintf(out, "%%line %"PRId32"%+"PRId32" %s\n",
where.lineno, lineinc, quoted_file_name);
}
linnum = where.lineno + lineinc;
fputs(line, out);
fputc('\n', out);
}
nasm_free(quoted_file_name);
pp_cleanup_pass();
reset_warnings();
if (ofile)
fclose(ofile);
if (ofile && terminate_after_phase && !keep_all)
remove(outname);
ofile = NULL;
}
if (operating_mode & OP_NORMAL) {
ofile = nasm_open_write(outname, (ofmt->flags & OFMT_TEXT) ? NF_TEXT : NF_BINARY);
if (!ofile)
nasm_fatal("unable to open output file `%s'", outname);
ofmt->init();
dfmt->init();
assemble_file(inname, depend_list);
if (!terminate_after_phase) {
ofmt->cleanup();
cleanup_labels();
fflush(ofile);
if (ferror(ofile))
nasm_nonfatal("write error on output file `%s'", outname);
}
if (ofile) {
fclose(ofile);
if (terminate_after_phase && !keep_all)
remove(outname);
ofile = NULL;
}
}
pp_cleanup_session();
if (depend_list && !terminate_after_phase)
emit_dependencies(depend_list);
if (want_usage)
usage();
raa_free(offsets);
saa_free(forwrefs);
eval_cleanup();
stdscan_cleanup();
src_free();
strlist_free(&include_path);
return terminate_after_phase;
}
/*
* Get a parameter for a command line option.
* First arg must be in the form of e.g. -f...
*/
static char *get_param(char *p, char *q, bool *advance)
{
*advance = false;
if (p[2]) /* the parameter's in the option */
return nasm_skip_spaces(p + 2);
if (q && q[0]) {
*advance = true;
return q;
}
nasm_nonfatalf(ERR_USAGE, "option `-%c' requires an argument", p[1]);
return NULL;
}
/*
* Copy a filename
*/
static void copy_filename(const char **dst, const char *src, const char *what)
{
if (*dst)
nasm_fatal("more than one %s file specified: %s\n", what, src);
*dst = nasm_strdup(src);
}
/*
* Convert a string to a POSIX make-safe form
*/
static char *quote_for_pmake(const char *str)
{
const char *p;
char *os, *q;
size_t n = 1; /* Terminating zero */
size_t nbs = 0;
if (!str)
return NULL;
for (p = str; *p; p++) {
switch (*p) {
case ' ':
case '\t':
/* Convert N backslashes + ws -> 2N+1 backslashes + ws */
n += nbs + 2;
nbs = 0;
break;
case '$':
case '#':
nbs = 0;
n += 2;
break;
case '\\':
nbs++;
n++;
break;
default:
nbs = 0;
n++;
break;
}
}
/* Convert N backslashes at the end of filename to 2N backslashes */
n += nbs;
os = q = nasm_malloc(n);
nbs = 0;
for (p = str; *p; p++) {
switch (*p) {
case ' ':
case '\t':
q = mempset(q, '\\', nbs);
*q++ = '\\';
*q++ = *p;
nbs = 0;
break;
case '$':
*q++ = *p;
*q++ = *p;
nbs = 0;
break;
case '#':
*q++ = '\\';
*q++ = *p;
nbs = 0;
break;
case '\\':
*q++ = *p;
nbs++;
break;
default:
*q++ = *p;
nbs = 0;
break;
}
}
q = mempset(q, '\\', nbs);
*q = '\0';
return os;
}
/*
* Convert a string to a Watcom make-safe form
*/
static char *quote_for_wmake(const char *str)
{
const char *p;
char *os, *q;
bool quote = false;
size_t n = 1; /* Terminating zero */
if (!str)
return NULL;
for (p = str; *p; p++) {
switch (*p) {
case ' ':
case '\t':
case '&':
quote = true;
n++;
break;
case '\"':
quote = true;
n += 2;
break;
case '$':
case '#':
n += 2;
break;
default:
n++;
break;
}
}
if (quote)
n += 2;
os = q = nasm_malloc(n);
if (quote)
*q++ = '\"';
for (p = str; *p; p++) {
switch (*p) {
case '$':
case '#':
*q++ = '$';
*q++ = *p;
break;
case '\"':
*q++ = *p;
*q++ = *p;
break;
default:
*q++ = *p;
break;
}
}
if (quote)
*q++ = '\"';
*q = '\0';
return os;
}
enum text_options {
OPT_BOGUS,
OPT_VERSION,
OPT_HELP,
OPT_ABORT_ON_PANIC,
OPT_MANGLE,
OPT_INCLUDE,
OPT_PRAGMA,
OPT_BEFORE,
OPT_LIMIT,
OPT_KEEP_ALL,
OPT_NO_LINE,
OPT_DEBUG,
OPT_REPRODUCIBLE
};
enum need_arg {
ARG_NO,
ARG_YES,
ARG_MAYBE
};
struct textargs {
const char *label;
enum text_options opt;
enum need_arg need_arg;
int pvt;
};
static const struct textargs textopts[] = {
{"v", OPT_VERSION, ARG_NO, 0},
{"version", OPT_VERSION, ARG_NO, 0},
{"help", OPT_HELP, ARG_NO, 0},
{"abort-on-panic", OPT_ABORT_ON_PANIC, ARG_NO, 0},
{"prefix", OPT_MANGLE, ARG_YES, LM_GPREFIX},
{"postfix", OPT_MANGLE, ARG_YES, LM_GSUFFIX},
{"gprefix", OPT_MANGLE, ARG_YES, LM_GPREFIX},
{"gpostfix", OPT_MANGLE, ARG_YES, LM_GSUFFIX},
{"lprefix", OPT_MANGLE, ARG_YES, LM_LPREFIX},
{"lpostfix", OPT_MANGLE, ARG_YES, LM_LSUFFIX},
{"include", OPT_INCLUDE, ARG_YES, 0},
{"pragma", OPT_PRAGMA, ARG_YES, 0},
{"before", OPT_BEFORE, ARG_YES, 0},
{"limit-", OPT_LIMIT, ARG_YES, 0},
{"keep-all", OPT_KEEP_ALL, ARG_NO, 0},
{"no-line", OPT_NO_LINE, ARG_NO, 0},
{"debug", OPT_DEBUG, ARG_MAYBE, 0},
{"reproducible", OPT_REPRODUCIBLE, ARG_NO, 0},
{NULL, OPT_BOGUS, ARG_NO, 0}
};
static void show_version(void)
{
printf("NASM version %s compiled on %s%s\n",
nasm_version, nasm_date, nasm_compile_options);
exit(0);
}
static bool stopoptions = false;
static bool process_arg(char *p, char *q, int pass)
{
char *param;
bool advance = false;
if (!p || !p[0])
return false;
if (p[0] == '-' && !stopoptions) {
if (strchr("oOfpPdDiIlLFXuUZwW", p[1])) {
/* These parameters take values */
if (!(param = get_param(p, q, &advance)))
return advance;
}
switch (p[1]) {
case 's':
if (pass == 1)
error_file = stdout;
break;
case 'o': /* output file */
if (pass == 2)
copy_filename(&outname, param, "output");
break;
case 'f': /* output format */
if (pass == 1) {
ofmt = ofmt_find(param, &ofmt_alias);
if (!ofmt) {
nasm_fatalf(ERR_USAGE, "unrecognised output format `%s' - use -hf for a list", param);
}
}
break;
case 'O': /* Optimization level */
if (pass == 1) {
int opt;
if (!*param) {
/* Naked -O == -Ox */
optimizing.level = MAX_OPTIMIZE;
} else {
while (*param) {
switch (*param) {
case '0': case '1': case '2': case '3': case '4':
case '5': case '6': case '7': case '8': case '9':
opt = strtoul(param, &param, 10);
/* -O0 -> optimizing.level == -1, 0.98 behaviour */
/* -O1 -> optimizing.level == 0, 0.98.09 behaviour */
if (opt < 2)
optimizing.level = opt - 1;
else
optimizing.level = opt;
break;
case 'v':
case '+':
param++;
opt_verbose_info = true;
break;
case 'x':
param++;
optimizing.level = MAX_OPTIMIZE;
break;
default:
nasm_fatal("unknown optimization option -O%c\n",
*param);
break;
}
}
if (optimizing.level > MAX_OPTIMIZE)
optimizing.level = MAX_OPTIMIZE;
}
}
break;
case 'p': /* pre-include */
case 'P':
if (pass == 2)
pp_pre_include(param);
break;
case 'd': /* pre-define */
case 'D':
if (pass == 2)
pp_pre_define(param);
break;
case 'u': /* un-define */
case 'U':
if (pass == 2)
pp_pre_undefine(param);
break;
case 'i': /* include search path */
case 'I':
if (pass == 1)
strlist_add(include_path, param);
break;
case 'l': /* listing file */
if (pass == 2)
copy_filename(&listname, param, "listing");
break;
case 'L': /* listing options */
if (pass == 2) {
while (*param)
list_options |= list_option_mask(*param++);
}
break;
case 'Z': /* error messages file */
if (pass == 1)
copy_filename(&errname, param, "error");
break;
case 'F': /* specify debug format */
if (pass == 1) {
using_debug_info = true;
debug_format = param;
}
break;
case 'X': /* specify error reporting format */
if (pass == 1) {
if (!nasm_stricmp("vc", param) || !nasm_stricmp("msvc", param) || !nasm_stricmp("ms", param))
errfmt = &errfmt_msvc;
else if (!nasm_stricmp("gnu", param) || !nasm_stricmp("gcc", param))
errfmt = &errfmt_gnu;
else
nasm_fatalf(ERR_USAGE, "unrecognized error reporting format `%s'", param);
}
break;
case 'g':
if (pass == 1) {
using_debug_info = true;
if (p[2])
debug_format = nasm_skip_spaces(p + 2);
}
break;
case 'h':
help(stdout);
exit(0); /* never need usage message here */
break;
case 'y':
/* legacy option */
dfmt_list(stdout);
exit(0);
break;
case 't':
if (pass == 1)
tasm_compatible_mode = true;
break;
case 'v':
show_version();
break;
case 'e': /* preprocess only */
case 'E':
if (pass == 1)
operating_mode = OP_PREPROCESS;
break;
case 'a': /* assemble only - don't preprocess */
if (pass == 1)
ppopt |= PP_TRIVIAL;
break;
case 'w':
case 'W':
if (pass == 2)
set_warning_status(param);
break;
case 'M':
if (pass == 1) {
switch (p[2]) {
case 'W':
quote_for_make = quote_for_wmake;
break;
case 'D':
case 'F':
case 'T':
case 'Q':
advance = true;
break;
default:
break;
}
} else {
switch (p[2]) {
case 0:
operating_mode = OP_DEPEND;
break;
case 'G':
operating_mode = OP_DEPEND;
depend_missing_ok = true;
break;
case 'P':
depend_emit_phony = true;
break;
case 'D':
operating_mode |= OP_DEPEND;
if (q && (q[0] != '-' || q[1] == '\0')) {
depend_file = q;
advance = true;
}
break;
case 'F':
depend_file = q;
advance = true;
break;
case 'T':
depend_target = q;
advance = true;
break;
case 'Q':
depend_target = quote_for_make(q);
advance = true;
break;
case 'W':
/* handled in pass 1 */
break;
default:
nasm_nonfatalf(ERR_USAGE, "unknown dependency option `-M%c'", p[2]);
break;
}
}
if (advance && (!q || !q[0])) {
nasm_nonfatalf(ERR_USAGE, "option `-M%c' requires a parameter", p[2]);
break;
}
break;
case '-':
{
const struct textargs *tx;
size_t olen, plen;
char *eqsave;
enum text_options opt;
p += 2;
if (!*p) { /* -- => stop processing options */
stopoptions = true;
break;
}
olen = 0; /* Placate gcc at lower optimization levels */
plen = strlen(p);
for (tx = textopts; tx->label; tx++) {
olen = strlen(tx->label);
if (olen > plen)
continue;
if (nasm_memicmp(p, tx->label, olen))
continue;
if (tx->label[olen-1] == '-')
break; /* Incomplete option */
if (!p[olen] || p[olen] == '=')
break; /* Complete option */
}
if (!tx->label) {
nasm_nonfatalf(ERR_USAGE, "unrecognized option `--%s'", p);
}
opt = tx->opt;
eqsave = param = strchr(p+olen, '=');
if (param)
*param++ = '\0';
switch (tx->need_arg) {
case ARG_YES: /* Argument required, and may be standalone */
if (!param) {
param = q;
advance = true;
}
/* Note: a null string is a valid parameter */
if (!param) {
nasm_nonfatalf(ERR_USAGE, "option `--%s' requires an argument", p);
opt = OPT_BOGUS;
}
break;
case ARG_NO: /* Argument prohibited */
if (param) {
nasm_nonfatalf(ERR_USAGE, "option `--%s' does not take an argument", p);
opt = OPT_BOGUS;
}
break;
case ARG_MAYBE: /* Argument permitted, but must be attached with = */
break;
}
switch (opt) {
case OPT_BOGUS:
break; /* We have already errored out */
case OPT_VERSION:
show_version();
break;
case OPT_ABORT_ON_PANIC:
abort_on_panic = true;
break;
case OPT_MANGLE:
if (pass == 2)
set_label_mangle(tx->pvt, param);
break;
case OPT_INCLUDE:
if (pass == 2)
pp_pre_include(q);
break;
case OPT_PRAGMA:
if (pass == 2)
pp_pre_command("%pragma", param);
break;
case OPT_BEFORE:
if (pass == 2)
pp_pre_command(NULL, param);
break;
case OPT_LIMIT:
if (pass == 1)
nasm_set_limit(p+olen, param);
break;
case OPT_KEEP_ALL:
keep_all = true;
break;
case OPT_NO_LINE:
ppopt |= PP_NOLINE;
break;
case OPT_DEBUG:
debug_nasm = param ? strtoul(param, NULL, 10) : debug_nasm+1;
break;
case OPT_REPRODUCIBLE:
reproducible = true;
break;
case OPT_HELP:
help(stdout);
exit(0);
default:
panic();
}
if (eqsave)
*eqsave = '='; /* Restore = argument separator */
break;
}
default:
nasm_nonfatalf(ERR_USAGE, "unrecognised option `-%c'", p[1]);
break;
}
} else if (pass == 2) {
/* In theory we could allow multiple input files... */
copy_filename(&inname, p, "input");
}
return advance;
}
#define ARG_BUF_DELTA 128
static void process_respfile(FILE * rfile, int pass)
{
char *buffer, *p, *q, *prevarg;
int bufsize, prevargsize;
bufsize = prevargsize = ARG_BUF_DELTA;
buffer = nasm_malloc(ARG_BUF_DELTA);
prevarg = nasm_malloc(ARG_BUF_DELTA);
prevarg[0] = '\0';
while (1) { /* Loop to handle all lines in file */
p = buffer;
while (1) { /* Loop to handle long lines */
q = fgets(p, bufsize - (p - buffer), rfile);
if (!q)
break;
p += strlen(p);
if (p > buffer && p[-1] == '\n')
break;
if (p - buffer > bufsize - 10) {
int offset;
offset = p - buffer;
bufsize += ARG_BUF_DELTA;
buffer = nasm_realloc(buffer, bufsize);
p = buffer + offset;
}
}
if (!q && p == buffer) {
if (prevarg[0])
process_arg(prevarg, NULL, pass);
nasm_free(buffer);
nasm_free(prevarg);
return;
}
/*
* Play safe: remove CRs, LFs and any spurious ^Zs, if any of
* them are present at the end of the line.
*/
*(p = &buffer[strcspn(buffer, "\r\n\032")]) = '\0';
while (p > buffer && nasm_isspace(p[-1]))
*--p = '\0';
p = nasm_skip_spaces(buffer);
if (process_arg(prevarg, p, pass))
*p = '\0';
if ((int) strlen(p) > prevargsize - 10) {
prevargsize += ARG_BUF_DELTA;
prevarg = nasm_realloc(prevarg, prevargsize);
}
strncpy(prevarg, p, prevargsize);
}
}
/* Function to process args from a string of args, rather than the
* argv array. Used by the environment variable and response file
* processing.
*/
static void process_args(char *args, int pass)
{
char *p, *q, *arg, *prevarg;
char separator = ' ';
p = args;
if (*p && *p != '-')
separator = *p++;
arg = NULL;
while (*p) {
q = p;
while (*p && *p != separator)
p++;
while (*p == separator)
*p++ = '\0';
prevarg = arg;
arg = q;
if (process_arg(prevarg, arg, pass))
arg = NULL;
}
if (arg)
process_arg(arg, NULL, pass);
}
static void process_response_file(const char *file, int pass)
{
char str[2048];
FILE *f = nasm_open_read(file, NF_TEXT);
if (!f) {
perror(file);
exit(-1);
}
while (fgets(str, sizeof str, f)) {
process_args(str, pass);
}
fclose(f);
}
static void parse_cmdline(int argc, char **argv, int pass)
{
FILE *rfile;
char *envreal, *envcopy = NULL, *p;
/*
* Initialize all the warnings to their default state, including
* warning index 0 used for "always on".
*/
memcpy(warning_state, warning_default, sizeof warning_state);
/*
* First, process the NASMENV environment variable.
*/
envreal = getenv("NASMENV");
if (envreal) {
envcopy = nasm_strdup(envreal);
process_args(envcopy, pass);
nasm_free(envcopy);
}
/*
* Now process the actual command line.
*/
while (--argc) {
bool advance;
argv++;
if (argv[0][0] == '@') {
/*
* We have a response file, so process this as a set of
* arguments like the environment variable. This allows us
* to have multiple arguments on a single line, which is
* different to the -@resp file processing below for regular
* NASM.
*/
process_response_file(argv[0]+1, pass);
argc--;
argv++;
}
if (!stopoptions && argv[0][0] == '-' && argv[0][1] == '@') {
p = get_param(argv[0], argc > 1 ? argv[1] : NULL, &advance);
if (p) {
rfile = nasm_open_read(p, NF_TEXT);
if (rfile) {
process_respfile(rfile, pass);
fclose(rfile);
} else {
nasm_nonfatalf(ERR_USAGE, "unable to open response file `%s'", p);
}
}
} else
advance = process_arg(argv[0], argc > 1 ? argv[1] : NULL, pass);
argv += advance, argc -= advance;
}
/*
* Look for basic command line typos. This definitely doesn't
* catch all errors, but it might help cases of fumbled fingers.
*/
if (pass != 2)
return;
if (!inname)
nasm_fatalf(ERR_USAGE, "no input file specified");
else if ((errname && !strcmp(inname, errname)) ||
(outname && !strcmp(inname, outname)) ||
(listname && !strcmp(inname, listname)) ||
(depend_file && !strcmp(inname, depend_file)))
nasm_fatalf(ERR_USAGE, "will not overwrite input file");
if (errname) {
error_file = nasm_open_write(errname, NF_TEXT);
if (!error_file) {
error_file = stderr; /* Revert to default! */
nasm_fatalf(ERR_USAGE, "cannot open file `%s' for error messages", errname);
}
}
}
static void forward_refs(insn *instruction)
{
int i;
struct forwrefinfo *fwinf;
instruction->forw_ref = false;
if (!optimizing.level)
return; /* For -O0 don't bother */
if (!forwref)
return;
if (forwref->lineno != globallineno)
return;
instruction->forw_ref = true;
do {
instruction->oprs[forwref->operand].opflags |= OPFLAG_FORWARD;
forwref = saa_rstruct(forwrefs);
} while (forwref && forwref->lineno == globallineno);
if (!pass_first())
return;
for (i = 0; i < instruction->operands; i++) {
if (instruction->oprs[i].opflags & OPFLAG_FORWARD) {
fwinf = saa_wstruct(forwrefs);
fwinf->lineno = globallineno;
fwinf->operand = i;
}
}
}
static void process_insn(insn *instruction)
{
int32_t n;
int64_t l;
if (!instruction->times)
return; /* Nothing to do... */
nasm_assert(instruction->times > 0);
/*
* NOTE: insn_size() can change instruction->times
* (usually to 1) when called.
*/
if (!pass_final()) {
int64_t start = location.offset;
for (n = 1; n <= instruction->times; n++) {
l = insn_size(location.segment, location.offset,
globalbits, instruction);
/* l == -1 -> invalid instruction */
if (l != -1)
increment_offset(l);
}
if (list_option('p')) {
struct out_data dummy;
memset(&dummy, 0, sizeof dummy);
dummy.type = OUT_RAWDATA; /* Handled specially with .data NULL */
dummy.offset = start;
dummy.size = location.offset - start;
lfmt->output(&dummy);
}
} else {
l = assemble(location.segment, location.offset,
globalbits, instruction);
/* We can't get an invalid instruction here */
increment_offset(l);
if (instruction->times > 1) {
lfmt->uplevel(LIST_TIMES, instruction->times);
for (n = 2; n <= instruction->times; n++) {
l = assemble(location.segment, location.offset,
globalbits, instruction);
increment_offset(l);
}
lfmt->downlevel(LIST_TIMES);
}
}
}
static void assemble_file(const char *fname, struct strlist *depend_list)
{
char *line;
insn output_ins;
uint64_t prev_offset_changed;
int64_t stall_count = 0; /* Make sure we make forward progress... */
switch (cmd_sb) {
case 16:
break;
case 32:
if (!iflag_cpu_level_ok(&cmd_cpu, IF_386))
nasm_fatal("command line: 32-bit segment size requires a higher cpu");
break;
case 64:
if (!iflag_cpu_level_ok(&cmd_cpu, IF_X86_64))
nasm_fatal("command line: 64-bit segment size requires a higher cpu");
break;
default:
panic();
break;
}
prev_offset_changed = INT64_MAX;
if (listname && !keep_all) {
/* Remove the list file in case we die before the output pass */
remove(listname);
}
while (!terminate_after_phase && !pass_final()) {
_passn++;
switch (pass_type()) {
case PASS_INIT:
_pass_type = PASS_FIRST;
break;
case PASS_OPT:
if (global_offset_changed)
break; /* One more optimization pass */
/* fall through */
default:
_pass_type++;
break;
}
global_offset_changed = 0;
/*
* Create a warning buffer list unless we are in
* pass 2 (everything will be emitted immediately in pass 2.)
*/
if (warn_list) {
if (warn_list->nstr || pass_final())
strlist_free(&warn_list);
}
if (!pass_final() && !warn_list)
warn_list = strlist_alloc(false);
globalbits = cmd_sb; /* set 'bits' to command line default */
cpu = cmd_cpu;
if (listname) {
if (pass_final() || list_on_every_pass()) {
lfmt->init(listname);
} else if (list_active()) {
/*
* Looks like we used the list engine on a previous pass,
* but now it is turned off, presumably via %pragma -p
*/
lfmt->cleanup();
if (!keep_all)
remove(listname);
}
}
in_absolute = false;
if (!pass_first()) {
saa_rewind(forwrefs);
forwref = saa_rstruct(forwrefs);
raa_free(offsets);
offsets = raa_init();
}
location.segment = NO_SEG;
location.offset = 0;
if (pass_first())
location.known = true;
ofmt->reset();
switch_segment(ofmt->section(NULL, &globalbits));
pp_reset(fname, PP_NORMAL, depend_list);
globallineno = 0;
while ((line = pp_getline())) {
if (++globallineno > nasm_limit[LIMIT_LINES])
nasm_fatal("overall line count exceeds the maximum %"PRId64"\n",
nasm_limit[LIMIT_LINES]);
/*
* Here we parse our directives; this is not handled by the
* main parser.
*/
if (process_directives(line))
goto end_of_line; /* Just do final cleanup */
/* Not a directive, or even something that starts with [ */
parse_line(line, &output_ins);
forward_refs(&output_ins);
process_insn(&output_ins);
cleanup_insn(&output_ins);
end_of_line:
nasm_free(line);
} /* end while (line = pp_getline... */
pp_cleanup_pass();
/* We better not be having an error hold still... */
nasm_assert(!errhold_stack);
if (global_offset_changed) {
switch (pass_type()) {
case PASS_OPT:
/*
* This is the only pass type that can be executed more
* than once, and therefore has the ability to stall.
*/
if (global_offset_changed < prev_offset_changed) {
prev_offset_changed = global_offset_changed;
stall_count = 0;
} else {
stall_count++;
}
if (stall_count > nasm_limit[LIMIT_STALLED] ||
pass_count() >= nasm_limit[LIMIT_PASSES]) {
/* No convergence, almost certainly dead */
nasm_nonfatalf(ERR_UNDEAD,
"unable to find valid values for all labels "
"after %"PRId64" passes; "
"stalled for %"PRId64", giving up.",
pass_count(), stall_count);
nasm_nonfatalf(ERR_UNDEAD,
"Possible causes: recursive EQUs, macro abuse.");
}
break;
case PASS_STAB:
/*!
*!phase [off] phase error during stabilization
*! warns about symbols having changed values during
*! the second-to-last assembly pass. This is not
*! inherently fatal, but may be a source of bugs.
*/
nasm_warn(WARN_PHASE|ERR_UNDEAD,
"phase error during stabilization "
"pass, hoping for the best");
break;
case PASS_FINAL:
nasm_nonfatalf(ERR_UNDEAD,
"phase error during code generation pass");
break;
default:
/* This is normal, we'll keep going... */
break;
}
}
reset_warnings();
}
if (opt_verbose_info && pass_final()) {
/* -On and -Ov switches */
nasm_info("assembly required 1+%"PRId64"+2 passes\n", pass_count()-3);
}
lfmt->cleanup();
strlist_free(&warn_list);
}
/**
* get warning index; 0 if this is non-suppressible.
*/
static size_t warn_index(errflags severity)
{
size_t index;
if ((severity & ERR_MASK) >= ERR_FATAL)
return 0; /* Fatal errors are never suppressible */
/* Warnings MUST HAVE a warning category specifier! */
nasm_assert((severity & (ERR_MASK|WARN_MASK)) != ERR_WARNING);
index = WARN_IDX(severity);
nasm_assert(index < WARN_IDX_ALL);
return index;
}
static bool skip_this_pass(errflags severity)
{
errflags type = severity & ERR_MASK;
/*
* See if it's a pass-specific error or warning which should be skipped.
* We can never skip fatal errors as by definition they cannot be
* resumed from.
*/
if (type >= ERR_FATAL)
return false;
/*
* ERR_LISTMSG messages are always skipped; the list file
* receives them anyway as this function is not consulted
* for sending to the list file.
*/
if (type == ERR_LISTMSG)
return true;
/*
* This message not applicable unless it is the last pass we are going
* to execute; this can be either the final code-generation pass or
* the single pass executed in preproc-only mode.
*/
return (severity & ERR_PASS2) && !pass_final_or_preproc();
}
/**
* check for suppressed message (usually warnings or notes)
*
* @param severity the severity of the warning or error
* @return true if we should abort error/warning printing
*/
static bool is_suppressed(errflags severity)
{
/* Fatal errors must never be suppressed */
if ((severity & ERR_MASK) >= ERR_FATAL)
return false;
/* This error/warning is pointless if we are dead anyway */
if ((severity & ERR_UNDEAD) && terminate_after_phase)
return true;
if (!(warning_state[warn_index(severity)] & WARN_ST_ENABLED))
return true;
if (!(severity & ERR_PP_LISTMACRO))
return pp_suppress_error(severity);
return false;
}
/**
* Return the true error type (the ERR_MASK part) of the given
* severity, accounting for warnings that may need to be promoted to
* error.
*
* @param severity the severity of the warning or error
* @return true if we should error out
*/
static errflags true_error_type(errflags severity)
{
const uint8_t warn_is_err = WARN_ST_ENABLED|WARN_ST_ERROR;
int type;
type = severity & ERR_MASK;
/* Promote warning to error? */
if (type == ERR_WARNING) {
uint8_t state = warning_state[warn_index(severity)];
if ((state & warn_is_err) == warn_is_err)
type = ERR_NONFATAL;
}
return type;
}
/*
* The various error type prefixes
*/
static const char * const error_pfx_table[ERR_MASK+1] = {
";;; ", "debug: ", "info: ", "warning: ",
"error: ", "fatal: ", "critical: ", "panic: "
};
static const char no_file_name[] = "nasm"; /* What to print if no file name */
/*
* For fatal/critical/panic errors, kill this process.
*/
static fatal_func die_hard(errflags true_type, errflags severity)
{
fflush(NULL);
if (true_type == ERR_PANIC && abort_on_panic)
abort();
if (ofile) {
fclose(ofile);
if (!keep_all)
remove(outname);
ofile = NULL;
}
if (severity & ERR_USAGE)
usage();
/* Terminate immediately */
exit(true_type - ERR_FATAL + 1);
}
/*
* Returns the struct src_location appropriate for use, after some
* potential filename mangling.
*/
static struct src_location error_where(errflags severity)
{
struct src_location where;
if (severity & ERR_NOFILE) {
where.filename = NULL;
where.lineno = 0;
} else {
where = src_where_error();
if (!where.filename) {
where.filename =
inname && inname[0] ? inname :
outname && outname[0] ? outname :
NULL;
where.lineno = 0;
}
}
return where;
}
/*
* error reporting for critical and panic errors: minimize
* the amount of system dependencies for getting a message out,
* and in particular try to avoid memory allocations.
*/
fatal_func nasm_verror_critical(errflags severity, const char *fmt, va_list args)
{
struct src_location where;
errflags true_type = severity & ERR_MASK;
static bool been_here = false;
if (unlikely(been_here))
abort(); /* Recursive error... just die */
been_here = true;
where = error_where(severity);
if (!where.filename)
where.filename = no_file_name;
fputs(error_pfx_table[severity], error_file);
fputs(where.filename, error_file);
if (where.lineno) {
fprintf(error_file, "%s%"PRId32"%s",
errfmt->beforeline, where.lineno, errfmt->afterline);
}
fputs(errfmt->beforemsg, error_file);
vfprintf(error_file, fmt, args);
fputc('\n', error_file);
die_hard(true_type, severity);
}
/**
* Stack of tentative error hold lists.
*/
struct nasm_errtext {
struct nasm_errtext *next;
char *msg; /* Owned by this structure */
struct src_location where; /* Owned by the srcfile system */
errflags severity;
errflags true_type;
};
struct nasm_errhold {
struct nasm_errhold *up;
struct nasm_errtext *head, **tail;
};
static void nasm_free_error(struct nasm_errtext *et)
{
nasm_free(et->msg);
nasm_free(et);
}
static void nasm_issue_error(struct nasm_errtext *et);
struct nasm_errhold *nasm_error_hold_push(void)
{
struct nasm_errhold *eh;
nasm_new(eh);
eh->up = errhold_stack;
eh->tail = &eh->head;
errhold_stack = eh;
return eh;
}
void nasm_error_hold_pop(struct nasm_errhold *eh, bool issue)
{
struct nasm_errtext *et, *etmp;
/* Allow calling with a null argument saying no hold in the first place */
if (!eh)
return;
/* This *must* be the current top of the errhold stack */
nasm_assert(eh == errhold_stack);
if (eh->head) {
if (issue) {
if (eh->up) {
/* Commit the current hold list to the previous level */
*eh->up->tail = eh->head;
eh->up->tail = eh->tail;
} else {
/* Issue errors */
list_for_each_safe(et, etmp, eh->head)
nasm_issue_error(et);
}
} else {
/* Free the list, drop errors */
list_for_each_safe(et, etmp, eh->head)
nasm_free_error(et);
}
}
errhold_stack = eh->up;
nasm_free(eh);
}
/**
* common error reporting
* This is the common back end of the error reporting schemes currently
* implemented. It prints the nature of the warning and then the
* specific error message to error_file and may or may not return. It
* doesn't return if the error severity is a "panic" or "debug" type.
*
* @param severity the severity of the warning or error
* @param fmt the printf style format string
*/
void nasm_verror(errflags severity, const char *fmt, va_list args)
{
struct nasm_errtext *et;
errflags true_type = true_error_type(severity);
if (true_type >= ERR_CRITICAL)
nasm_verror_critical(severity, fmt, args);
if (is_suppressed(severity))
return;
nasm_new(et);
et->severity = severity;
et->true_type = true_type;
et->msg = nasm_vasprintf(fmt, args);
et->where = error_where(severity);
if (errhold_stack && true_type <= ERR_NONFATAL) {
/* It is a tentative error */
*errhold_stack->tail = et;
errhold_stack->tail = &et->next;
} else {
nasm_issue_error(et);
}
/*
* Don't do this before then, if we do, we lose messages in the list
* file, as the list file is only generated in the last pass.
*/
if (skip_this_pass(severity))
return;
if (!(severity & (ERR_HERE|ERR_PP_LISTMACRO)))
pp_error_list_macros(severity);
}
/*
* Actually print, list and take action on an error
*/
static void nasm_issue_error(struct nasm_errtext *et)
{
const char *pfx;
char warnsuf[64]; /* Warning suffix */
char linestr[64]; /* Formatted line number if applicable */
const errflags severity = et->severity;
const errflags true_type = et->true_type;
const struct src_location where = et->where;
if (severity & ERR_NO_SEVERITY)
pfx = "";
else
pfx = error_pfx_table[true_type];
*warnsuf = 0;
if ((severity & (ERR_MASK|ERR_HERE|ERR_PP_LISTMACRO)) == ERR_WARNING) {
/*
* It's a warning without ERR_HERE defined, and we are not already
* unwinding the macros that led us here.
*/
snprintf(warnsuf, sizeof warnsuf, " [-w+%s%s]",
(true_type >= ERR_NONFATAL) ? "error=" : "",
warning_name[warn_index(severity)]);
}
*linestr = 0;
if (where.lineno) {
snprintf(linestr, sizeof linestr, "%s%"PRId32"%s",
errfmt->beforeline, where.lineno, errfmt->afterline);
}
if (!skip_this_pass(severity)) {
const char *file = where.filename ? where.filename : no_file_name;
const char *here = "";
if (severity & ERR_HERE) {
here = where.filename ? " here" : " in an unknown location";
}
if (warn_list && true_type < ERR_NONFATAL) {
/*
* Buffer up warnings until we either get an error
* or we are on the code-generation pass.
*/
strlist_printf(warn_list, "%s%s%s%s%s%s%s",
file, linestr, errfmt->beforemsg,
pfx, et->msg, here, warnsuf);
} else {
/*
* Actually output an error. If we have buffered
* warnings, and this is a non-warning, output them now.
*/
if (true_type >= ERR_NONFATAL && warn_list) {
strlist_write(warn_list, "\n", error_file);
strlist_free(&warn_list);
}
fprintf(error_file, "%s%s%s%s%s%s%s\n",
file, linestr, errfmt->beforemsg,
pfx, et->msg, here, warnsuf);
}
}
/* Are we recursing from error_list_macros? */
if (severity & ERR_PP_LISTMACRO)
goto done;
/*
* Don't suppress this with skip_this_pass(), or we don't get
* pass1 or preprocessor warnings in the list file
*/
if (severity & ERR_HERE) {
if (where.lineno)
lfmt->error(severity, "%s%s at %s:%"PRId32"%s",
pfx, et->msg, where.filename, where.lineno, warnsuf);
else if (where.filename)
lfmt->error(severity, "%s%s in file %s%s",
pfx, et->msg, where.filename, warnsuf);
else
lfmt->error(severity, "%s%s in an unknown location%s",
pfx, et->msg, warnsuf);
} else {
lfmt->error(severity, "%s%s%s", pfx, et->msg, warnsuf);
}
if (skip_this_pass(severity))
goto done;
if (true_type >= ERR_FATAL)
die_hard(true_type, severity);
else if (true_type >= ERR_NONFATAL)
terminate_after_phase = true;
done:
nasm_free_error(et);
}
static void usage(void)
{
fprintf(error_file, "Type %s -h for help.\n", _progname);
}
static void help(FILE *out)
{
int i;
fprintf(out,
"Usage: %s [-@ response_file] [options...] [--] filename\n"
" %s -v (or --v)\n",
_progname, _progname);
fputs(
"\n"
"Options (values in brackets indicate defaults):\n"
"\n"
" -h show this text and exit (also --help)\n"
" -v (or --v) print the NASM version number and exit\n"
" -@ file response file; one command line option per line\n"
"\n"
" -o outfile write output to outfile\n"
" --keep-all output files will not be removed even if an error happens\n"
"\n"
" -Xformat specify error reporting format (gnu or vc)\n"
" -s redirect error messages to stdout\n"
" -Zfile redirect error messages to file\n"
"\n"
" -M generate Makefile dependencies on stdout\n"
" -MG d:o, missing files assumed generated\n"
" -MF file set Makefile dependency file\n"
" -MD file assemble and generate dependencies\n"
" -MT file dependency target name\n"
" -MQ file dependency target name (quoted)\n"
" -MP emit phony targets\n"
"\n"
" -f format select output file format\n"
, out);
ofmt_list(ofmt, out);
fputs(
"\n"
" -g generate debugging information\n"
" -F format select a debugging format (output format dependent)\n"
" -gformat same as -g -F format\n"
, out);
dfmt_list(out);
fputs(
"\n"
" -l listfile write listing to a list file\n"
" -Lflags... add optional information to the list file\n"
" -Lb show builtin macro packages (standard and %use)\n"
" -Ld show byte and repeat counts in decimal, not hex\n"
" -Le show the preprocessed output\n"
" -Lf ignore .nolist (force output)\n"
" -Lm show multi-line macro calls with expanded parameters\n"
" -Lp output a list file every pass, in case of errors\n"
" -Ls show all single-line macro definitions\n"
" -Lw flush the output after every line (very slow!)\n"
" -L+ enable all listing options except -Lw (very verbose!)\n"
"\n"
" -Oflags... optimize opcodes, immediates and branch offsets\n"
" -O0 no optimization\n"
" -O1 minimal optimization\n"
" -Ox multipass optimization (default)\n"
" -Ov display the number of passes executed at the end\n"
" -t assemble in limited SciTech TASM compatible mode\n"
"\n"
" -E (or -e) preprocess only (writes output to stdout by default)\n"
" -a don't preprocess (assemble only)\n"
" -Ipath add a pathname to the include file path\n"
" -Pfile pre-include a file (also --include)\n"
" -Dmacro[=str] pre-define a macro\n"
" -Umacro undefine a macro\n"
" --pragma str pre-executes a specific %%pragma\n"
" --before str add line (usually a preprocessor statement) before the input\n"
" --no-line ignore %line directives in input\n"
"\n"
" --prefix str prepend the given string to the names of all extern,\n"
" common and global symbols (also --gprefix)\n"
" --suffix str append the given string to the names of all extern,\n"
" common and global symbols (also --gprefix)\n"
" --lprefix str prepend the given string to local symbols\n"
" --lpostfix str append the given string to local symbols\n"
"\n"
" --reproducible attempt to produce run-to-run identical output\n"
"\n"
" -w+x enable warning x (also -Wx)\n"
" -w-x disable warning x (also -Wno-x)\n"
" -w[+-]error promote all warnings to errors (also -Werror)\n"
" -w[+-]error=x promote warning x to errors (also -Werror=x)\n"
, out);
fprintf(out, " %-20s %s\n",
warning_name[WARN_IDX_ALL], warning_help[WARN_IDX_ALL]);
for (i = 1; i < WARN_IDX_ALL; i++) {
const char *me = warning_name[i];
const char *prev = warning_name[i-1];
const char *next = warning_name[i+1];
if (prev) {
int prev_len = strlen(prev);
const char *dash = me;
while ((dash = strchr(dash+1, '-'))) {
int prefix_len = dash - me; /* Not including final dash */
if (strncmp(next, me, prefix_len+1)) {
/* Only one or last option with this prefix */
break;
}
if (prefix_len >= prev_len ||
strncmp(prev, me, prefix_len) ||
(prev[prefix_len] != '-' && prev[prefix_len] != '\0')) {
/* This prefix is different from the previous option */
fprintf(out, " %-20.*s all warnings prefixed with \"%.*s\"\n",
prefix_len, me, prefix_len+1, me);
}
}
}
fprintf(out, " %-20s %s%s\n",
warning_name[i], warning_help[i],
(warning_default[i] & WARN_ST_ERROR) ? " [error]" :
(warning_default[i] & WARN_ST_ENABLED) ? " [on]" : " [off]");
}
fputs(
"\n"
" --limit-X val set execution limit X\n"
, out);
for (i = 0; i <= LIMIT_MAX; i++) {
fprintf(out, " %-20s %s [",
limit_info[i].name, limit_info[i].help);
if (nasm_limit[i] < LIMIT_MAX_VAL) {
fprintf(out, "%"PRId64"]\n", nasm_limit[i]);
} else {
fputs("unlimited]\n", out);
}
}
}