mirror of
https://github.com/netwide-assembler/nasm.git
synced 2024-11-27 08:10:07 +08:00
90b1ccff86
Currently, NASM always issues as an unknown symbol any symbol declared EXTERN. This is highly undesirable when using common header files, as it might cause the linker to pull in a bunch of unnecessary modules, depending on how smart the linker is. Add a new REQUIRED directive which behaves like the old EXTERN, for the use cases which might still need this behavior. Signed-off-by: H. Peter Anvin (Intel) <hpa@zytor.com>
505 lines
14 KiB
C
505 lines
14 KiB
C
/* ----------------------------------------------------------------------- *
|
|
*
|
|
* Copyright 1996-2019 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.
|
|
*
|
|
* ----------------------------------------------------------------------- */
|
|
|
|
/*
|
|
* Parse and handle assembler directives
|
|
*/
|
|
|
|
#include "compiler.h"
|
|
|
|
#include "nctype.h"
|
|
|
|
#include "nasm.h"
|
|
#include "nasmlib.h"
|
|
#include "ilog2.h"
|
|
#include "error.h"
|
|
#include "float.h"
|
|
#include "stdscan.h"
|
|
#include "preproc.h"
|
|
#include "eval.h"
|
|
#include "assemble.h"
|
|
#include "outform.h"
|
|
#include "listing.h"
|
|
#include "labels.h"
|
|
#include "iflag.h"
|
|
|
|
struct cpunames {
|
|
const char *name;
|
|
unsigned int level;
|
|
/* Eventually a table of features */
|
|
};
|
|
|
|
static iflag_t get_cpu(const char *value)
|
|
{
|
|
iflag_t r;
|
|
const struct cpunames *cpu;
|
|
static const struct cpunames cpunames[] = {
|
|
{ "8086", IF_8086 },
|
|
{ "186", IF_186 },
|
|
{ "286", IF_286 },
|
|
{ "386", IF_386 },
|
|
{ "486", IF_486 },
|
|
{ "586", IF_PENT },
|
|
{ "pentium", IF_PENT },
|
|
{ "pentiummmx", IF_PENT },
|
|
{ "686", IF_P6 },
|
|
{ "p6", IF_P6 },
|
|
{ "ppro", IF_P6 },
|
|
{ "pentiumpro", IF_P6 },
|
|
{ "p2", IF_P6 }, /* +MMX */
|
|
{ "pentiumii", IF_P6 },
|
|
{ "p3", IF_KATMAI },
|
|
{ "katmai", IF_KATMAI },
|
|
{ "p4", IF_WILLAMETTE },
|
|
{ "willamette", IF_WILLAMETTE },
|
|
{ "prescott", IF_PRESCOTT },
|
|
{ "x64", IF_X86_64 },
|
|
{ "x86-64", IF_X86_64 },
|
|
{ "ia64", IF_IA64 },
|
|
{ "ia-64", IF_IA64 },
|
|
{ "itanium", IF_IA64 },
|
|
{ "itanic", IF_IA64 },
|
|
{ "merced", IF_IA64 },
|
|
{ "nehalem", IF_NEHALEM },
|
|
{ "westmere", IF_WESTMERE },
|
|
{ "sandybridge", IF_SANDYBRIDGE },
|
|
{ "ivybridge", IF_FUTURE },
|
|
{ "any", IF_ANY },
|
|
{ "all", IF_ANY },
|
|
{ "default", IF_PLEVEL },
|
|
{ NULL, IF_PLEVEL } /* Error and final default entry */
|
|
};
|
|
|
|
iflag_clear_all(&r);
|
|
|
|
for (cpu = cpunames; cpu->name; cpu++) {
|
|
if (!nasm_stricmp(value, cpu->name))
|
|
break;
|
|
}
|
|
|
|
if (!cpu->name)
|
|
nasm_nonfatal("unknown 'cpu' type '%s'", value);
|
|
|
|
iflag_set_cpu(&r, cpu->level);
|
|
return r;
|
|
}
|
|
|
|
static int get_bits(const char *value)
|
|
{
|
|
int i = atoi(value);
|
|
|
|
switch (i) {
|
|
case 16:
|
|
break; /* Always safe */
|
|
case 32:
|
|
if (!iflag_cpu_level_ok(&cpu, IF_386)) {
|
|
nasm_nonfatal("cannot specify 32-bit segment on processor below a 386");
|
|
i = 16;
|
|
}
|
|
break;
|
|
case 64:
|
|
if (!iflag_cpu_level_ok(&cpu, IF_X86_64)) {
|
|
nasm_nonfatal("cannot specify 64-bit segment on processor below an x86-64");
|
|
i = 16;
|
|
}
|
|
break;
|
|
default:
|
|
nasm_nonfatal("`%s' is not a valid segment size; must be 16, 32 or 64",
|
|
value);
|
|
i = 16;
|
|
break;
|
|
}
|
|
return i;
|
|
}
|
|
|
|
static enum directive parse_directive_line(char **directive, char **value)
|
|
{
|
|
char *p, *q, *buf;
|
|
|
|
buf = nasm_skip_spaces(*directive);
|
|
|
|
/*
|
|
* It should be enclosed in [ ].
|
|
* XXX: we don't check there is nothing else on the remainder of the
|
|
* line, except a possible comment.
|
|
*/
|
|
if (*buf != '[')
|
|
return D_none;
|
|
q = strchr(buf, ']');
|
|
if (!q)
|
|
return D_corrupt;
|
|
|
|
/*
|
|
* Strip off the comments. XXX: this doesn't account for quoted
|
|
* strings inside a directive. We should really strip the
|
|
* comments in generic code, not here. While we're at it, it
|
|
* would be better to pass the backend a series of tokens instead
|
|
* of a raw string, and actually process quoted strings for it,
|
|
* like of like argv is handled in C.
|
|
*/
|
|
p = strchr(buf, ';');
|
|
if (p) {
|
|
if (p < q) /* ouch! somewhere inside */
|
|
return D_corrupt;
|
|
*p = '\0';
|
|
}
|
|
|
|
/* no brace, no trailing spaces */
|
|
*q = '\0';
|
|
nasm_zap_spaces_rev(--q);
|
|
|
|
/* directive */
|
|
p = nasm_skip_spaces(++buf);
|
|
q = nasm_skip_word(p);
|
|
if (!q)
|
|
return D_corrupt; /* sigh... no value there */
|
|
*q = '\0';
|
|
*directive = p;
|
|
|
|
/* and value finally */
|
|
p = nasm_skip_spaces(++q);
|
|
*value = p;
|
|
|
|
return directive_find(*directive);
|
|
}
|
|
|
|
/*
|
|
* Process a line from the assembler and try to handle it if it
|
|
* is a directive. Return true if the line was handled (including
|
|
* if it was an error), false otherwise.
|
|
*/
|
|
bool process_directives(char *directive)
|
|
{
|
|
enum directive d;
|
|
char *value, *p, *q, *special;
|
|
struct tokenval tokval;
|
|
bool bad_param = false;
|
|
enum label_type type;
|
|
|
|
d = parse_directive_line(&directive, &value);
|
|
|
|
switch (d) {
|
|
case D_none:
|
|
return D_none; /* Not a directive */
|
|
|
|
case D_corrupt:
|
|
nasm_nonfatal("invalid directive line");
|
|
break;
|
|
|
|
default: /* It's a backend-specific directive */
|
|
switch (ofmt->directive(d, value)) {
|
|
case DIRR_UNKNOWN:
|
|
goto unknown;
|
|
case DIRR_OK:
|
|
case DIRR_ERROR:
|
|
break;
|
|
case DIRR_BADPARAM:
|
|
bad_param = true;
|
|
break;
|
|
default:
|
|
panic();
|
|
}
|
|
break;
|
|
|
|
case D_unknown:
|
|
unknown:
|
|
nasm_nonfatal("unrecognized directive [%s]", directive);
|
|
break;
|
|
|
|
case D_SEGMENT: /* [SEGMENT n] */
|
|
case D_SECTION:
|
|
{
|
|
int sb = globalbits;
|
|
int32_t seg = ofmt->section(value, &sb);
|
|
|
|
if (seg == NO_SEG) {
|
|
nasm_nonfatal("segment name `%s' not recognized", value);
|
|
} else {
|
|
globalbits = sb;
|
|
switch_segment(seg);
|
|
}
|
|
break;
|
|
}
|
|
|
|
case D_SECTALIGN: /* [SECTALIGN n] */
|
|
{
|
|
expr *e;
|
|
|
|
if (*value) {
|
|
stdscan_reset();
|
|
stdscan_set(value);
|
|
tokval.t_type = TOKEN_INVALID;
|
|
e = evaluate(stdscan, NULL, &tokval, NULL, true, NULL);
|
|
if (e) {
|
|
uint64_t align = e->value;
|
|
|
|
if (!is_power2(e->value)) {
|
|
nasm_nonfatal("segment alignment `%s' is not power of two",
|
|
value);
|
|
} else if (align > UINT64_C(0x7fffffff)) {
|
|
/*
|
|
* FIXME: Please make some sane message here
|
|
* ofmt should have some 'check' method which
|
|
* would report segment alignment bounds.
|
|
*/
|
|
nasm_nonfatal("absurdly large segment alignment `%s' (2^%d)",
|
|
value, ilog2_64(align));
|
|
}
|
|
|
|
/* callee should be able to handle all details */
|
|
if (location.segment != NO_SEG)
|
|
ofmt->sectalign(location.segment, align);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
case D_BITS: /* [BITS bits] */
|
|
globalbits = get_bits(value);
|
|
break;
|
|
|
|
case D_GLOBAL: /* [GLOBAL|STATIC|EXTERN|COMMON symbol:special] */
|
|
type = LBL_GLOBAL;
|
|
goto symdef;
|
|
case D_STATIC:
|
|
type = LBL_STATIC;
|
|
goto symdef;
|
|
case D_EXTERN:
|
|
type = LBL_EXTERN;
|
|
goto symdef;
|
|
case D_REQUIRED:
|
|
type = LBL_REQUIRED;
|
|
goto symdef;
|
|
case D_COMMON:
|
|
type = LBL_COMMON;
|
|
goto symdef;
|
|
|
|
symdef:
|
|
{
|
|
bool validid = true;
|
|
int64_t size = 0;
|
|
char *sizestr;
|
|
bool rn_error;
|
|
|
|
if (*value == '$')
|
|
value++; /* skip initial $ if present */
|
|
|
|
q = value;
|
|
if (!nasm_isidstart(*q)) {
|
|
validid = false;
|
|
} else {
|
|
q++;
|
|
while (*q && *q != ':' && !nasm_isspace(*q)) {
|
|
if (!nasm_isidchar(*q))
|
|
validid = false;
|
|
q++;
|
|
}
|
|
}
|
|
if (!validid) {
|
|
nasm_nonfatal("identifier expected after %s, got `%s'",
|
|
directive, value);
|
|
break;
|
|
}
|
|
|
|
if (nasm_isspace(*q)) {
|
|
*q++ = '\0';
|
|
sizestr = q = nasm_skip_spaces(q);
|
|
q = strchr(q, ':');
|
|
} else {
|
|
sizestr = NULL;
|
|
}
|
|
|
|
if (q && *q == ':') {
|
|
*q++ = '\0';
|
|
special = q;
|
|
} else {
|
|
special = NULL;
|
|
}
|
|
|
|
if (type == LBL_COMMON) {
|
|
if (sizestr)
|
|
size = readnum(sizestr, &rn_error);
|
|
if (!sizestr || rn_error)
|
|
nasm_nonfatal("%s size specified in common declaration",
|
|
sizestr ? "invalid" : "no");
|
|
} else if (sizestr) {
|
|
nasm_nonfatal("invalid syntax in %s declaration", directive);
|
|
}
|
|
|
|
if (!declare_label(value, type, special))
|
|
break;
|
|
|
|
if (type == LBL_COMMON || type == LBL_EXTERN || type == LBL_REQUIRED)
|
|
define_label(value, 0, size, false);
|
|
|
|
break;
|
|
}
|
|
|
|
case D_ABSOLUTE: /* [ABSOLUTE address] */
|
|
{
|
|
expr *e;
|
|
|
|
stdscan_reset();
|
|
stdscan_set(value);
|
|
tokval.t_type = TOKEN_INVALID;
|
|
e = evaluate(stdscan, NULL, &tokval, NULL, true, NULL);
|
|
if (e) {
|
|
if (!is_reloc(e)) {
|
|
nasm_nonfatal("cannot use non-relocatable expression as "
|
|
"ABSOLUTE address");
|
|
} else {
|
|
absolute.segment = reloc_seg(e);
|
|
absolute.offset = reloc_value(e);
|
|
}
|
|
} else if (pass_first()) {
|
|
absolute.offset = 0x100; /* don't go near zero in case of / */
|
|
} else {
|
|
nasm_nonfatal("invalid ABSOLUTE address");
|
|
}
|
|
in_absolute = true;
|
|
location.segment = NO_SEG;
|
|
location.offset = absolute.offset;
|
|
break;
|
|
}
|
|
|
|
case D_DEBUG: /* [DEBUG] */
|
|
{
|
|
bool badid, overlong;
|
|
char debugid[128];
|
|
|
|
p = value;
|
|
q = debugid;
|
|
badid = overlong = false;
|
|
if (!nasm_isidstart(*p)) {
|
|
badid = true;
|
|
} else {
|
|
while (*p && !nasm_isspace(*p)) {
|
|
if (q >= debugid + sizeof debugid - 1) {
|
|
overlong = true;
|
|
break;
|
|
}
|
|
if (!nasm_isidchar(*p))
|
|
badid = true;
|
|
*q++ = *p++;
|
|
}
|
|
*q = 0;
|
|
}
|
|
if (badid) {
|
|
nasm_nonfatal("identifier expected after DEBUG");
|
|
break;
|
|
}
|
|
if (overlong) {
|
|
nasm_nonfatal("DEBUG identifier too long");
|
|
break;
|
|
}
|
|
p = nasm_skip_spaces(p);
|
|
if (pass_final())
|
|
dfmt->debug_directive(debugid, p);
|
|
break;
|
|
}
|
|
|
|
case D_WARNING: /* [WARNING {push|pop|{+|-|*}warn-name}] */
|
|
value = nasm_skip_spaces(value);
|
|
if ((*value | 0x20) == 'p') {
|
|
if (!nasm_stricmp(value, "push"))
|
|
push_warnings();
|
|
else if (!nasm_stricmp(value, "pop"))
|
|
pop_warnings();
|
|
}
|
|
set_warning_status(value);
|
|
break;
|
|
|
|
case D_CPU: /* [CPU] */
|
|
cpu = get_cpu(value);
|
|
break;
|
|
|
|
case D_LIST: /* [LIST {+|-}] */
|
|
value = nasm_skip_spaces(value);
|
|
if (*value == '+') {
|
|
user_nolist = false;
|
|
} else {
|
|
if (*value == '-') {
|
|
user_nolist = true;
|
|
} else {
|
|
bad_param = true;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case D_DEFAULT: /* [DEFAULT] */
|
|
stdscan_reset();
|
|
stdscan_set(value);
|
|
tokval.t_type = TOKEN_INVALID;
|
|
if (stdscan(NULL, &tokval) != TOKEN_INVALID) {
|
|
switch (tokval.t_integer) {
|
|
case S_REL:
|
|
globalrel = 1;
|
|
break;
|
|
case S_ABS:
|
|
globalrel = 0;
|
|
break;
|
|
case P_BND:
|
|
globalbnd = 1;
|
|
break;
|
|
case P_NOBND:
|
|
globalbnd = 0;
|
|
break;
|
|
default:
|
|
bad_param = true;
|
|
break;
|
|
}
|
|
} else {
|
|
bad_param = true;
|
|
}
|
|
break;
|
|
|
|
case D_FLOAT:
|
|
if (float_option(value)) {
|
|
nasm_nonfatal("unknown 'float' directive: %s", value);
|
|
}
|
|
break;
|
|
|
|
case D_PRAGMA:
|
|
process_pragma(value);
|
|
break;
|
|
}
|
|
|
|
|
|
/* A common error message */
|
|
if (bad_param) {
|
|
nasm_nonfatal("invalid parameter to [%s] directive", directive);
|
|
}
|
|
|
|
return d != D_none;
|
|
}
|