mirror of
https://github.com/netwide-assembler/nasm.git
synced 2024-11-27 08:10:07 +08:00
fc0ff223b2
When a local label was seen, the debug backend would not receive the full label name! In order to both simplify the code and avoid this kind of discrepancy again, make both the output and debug format calls from a common static function. However, none of the current debug format backends want to see NASM special symbols (that start with .. but not ..@) so filter those from the debug backend. Finally, fix an incorrect comment in nasm.h: the debug format is called *after* the output format. Signed-off-by: H. Peter Anvin <hpa@zytor.com> Cc: Jim Kukunas <james.t.kukunas@linux.intel.com>
532 lines
16 KiB
C
532 lines
16 KiB
C
/* ----------------------------------------------------------------------- *
|
|
*
|
|
* Copyright 1996-2016 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.
|
|
*
|
|
* ----------------------------------------------------------------------- */
|
|
|
|
/*
|
|
* labels.c label handling for the Netwide Assembler
|
|
*/
|
|
|
|
#include "compiler.h"
|
|
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
#include <inttypes.h>
|
|
|
|
#include "nasm.h"
|
|
#include "nasmlib.h"
|
|
#include "hashtbl.h"
|
|
#include "labels.h"
|
|
|
|
/*
|
|
* A local label is one that begins with exactly one period. Things
|
|
* that begin with _two_ periods are NASM-specific things.
|
|
*
|
|
* If TASM compatibility is enabled, a local label can also begin with
|
|
* @@, so @@local is a TASM compatible local label. Note that we only
|
|
* check for the first @ symbol, although TASM requires both.
|
|
*/
|
|
#define islocal(l) \
|
|
(tasm_compatible_mode ? \
|
|
(((l)[0] == '.' || (l)[0] == '@') && (l)[1] != '.') : \
|
|
((l)[0] == '.' && (l)[1] != '.'))
|
|
#define islocalchar(c) \
|
|
(tasm_compatible_mode ? \
|
|
((c) == '.' || (c) == '@') : \
|
|
((c) == '.'))
|
|
|
|
#define LABEL_BLOCK 128 /* no. of labels/block */
|
|
#define LBLK_SIZE (LABEL_BLOCK * sizeof(union label))
|
|
|
|
#define END_LIST -3 /* don't clash with NO_SEG! */
|
|
#define END_BLOCK -2
|
|
#define BOGUS_VALUE -4
|
|
|
|
#define PERMTS_SIZE 16384 /* size of text blocks */
|
|
#if (PERMTS_SIZE < IDLEN_MAX)
|
|
#error "IPERMTS_SIZE must be greater than or equal to IDLEN_MAX"
|
|
#endif
|
|
|
|
/* values for label.defn.is_global */
|
|
#define DEFINED_BIT 1
|
|
#define GLOBAL_BIT 2
|
|
#define EXTERN_BIT 4
|
|
#define COMMON_BIT 8
|
|
|
|
#define NOT_DEFINED_YET 0
|
|
#define TYPE_MASK 3
|
|
#define LOCAL_SYMBOL (DEFINED_BIT)
|
|
#define GLOBAL_PLACEHOLDER (GLOBAL_BIT)
|
|
#define GLOBAL_SYMBOL (DEFINED_BIT | GLOBAL_BIT)
|
|
|
|
union label { /* actual label structures */
|
|
struct {
|
|
int32_t segment;
|
|
int64_t offset;
|
|
char *label, *special;
|
|
int is_global, is_norm;
|
|
} defn;
|
|
struct {
|
|
int32_t movingon;
|
|
int64_t dummy;
|
|
union label *next;
|
|
} admin;
|
|
};
|
|
|
|
struct permts { /* permanent text storage */
|
|
struct permts *next; /* for the linked list */
|
|
int size, usage; /* size and used space in ... */
|
|
char data[PERMTS_SIZE]; /* ... the data block itself */
|
|
};
|
|
|
|
extern int64_t global_offset_changed; /* defined in nasm.c */
|
|
|
|
static struct hash_table ltab; /* labels hash table */
|
|
static union label *ldata; /* all label data blocks */
|
|
static union label *lfree; /* labels free block */
|
|
static struct permts *perm_head; /* start of perm. text storage */
|
|
static struct permts *perm_tail; /* end of perm. text storage */
|
|
|
|
static void init_block(union label *blk);
|
|
static char *perm_copy(const char *string);
|
|
|
|
static char *prevlabel;
|
|
|
|
static bool initialized = false;
|
|
|
|
char lprefix[PREFIX_MAX] = { 0 };
|
|
char lpostfix[PREFIX_MAX] = { 0 };
|
|
|
|
/*
|
|
* Emit a symdef to the output and the debug format backends.
|
|
*/
|
|
static void out_symdef(char *name, int32_t segment, int64_t offset,
|
|
int is_global, char *special)
|
|
{
|
|
ofmt->symdef(name, segment, offset, is_global, special);
|
|
|
|
/*
|
|
* NASM special symbols are not passed to the debug format; none
|
|
* of the current backends want to see them.
|
|
*/
|
|
if (!(name[0] == '.' && name[1] == '.' && name[2] != '@'))
|
|
dfmt->debug_deflabel(name, segment, offset, is_global, special);
|
|
}
|
|
|
|
/*
|
|
* Internal routine: finds the `union label' corresponding to the
|
|
* given label name. Creates a new one, if it isn't found, and if
|
|
* `create' is true.
|
|
*/
|
|
static union label *find_label(char *label, int create)
|
|
{
|
|
char *prev;
|
|
int prevlen, len;
|
|
union label *lptr, **lpp;
|
|
char label_str[IDLEN_MAX];
|
|
struct hash_insert ip;
|
|
|
|
if (islocal(label)) {
|
|
prev = prevlabel;
|
|
prevlen = strlen(prev);
|
|
len = strlen(label);
|
|
if (prevlen + len >= IDLEN_MAX) {
|
|
nasm_error(ERR_NONFATAL, "identifier length exceed %i bytes",
|
|
IDLEN_MAX);
|
|
return NULL;
|
|
}
|
|
memcpy(label_str, prev, prevlen);
|
|
memcpy(label_str+prevlen, label, len+1);
|
|
label = label_str;
|
|
} else {
|
|
prev = "";
|
|
prevlen = 0;
|
|
}
|
|
|
|
lpp = (union label **) hash_find(<ab, label, &ip);
|
|
lptr = lpp ? *lpp : NULL;
|
|
|
|
if (lptr || !create)
|
|
return lptr;
|
|
|
|
/* Create a new label... */
|
|
if (lfree->admin.movingon == END_BLOCK) {
|
|
/*
|
|
* must allocate a new block
|
|
*/
|
|
lfree->admin.next = (union label *)nasm_malloc(LBLK_SIZE);
|
|
lfree = lfree->admin.next;
|
|
init_block(lfree);
|
|
}
|
|
|
|
lfree->admin.movingon = BOGUS_VALUE;
|
|
lfree->defn.label = perm_copy(label);
|
|
lfree->defn.special = NULL;
|
|
lfree->defn.is_global = NOT_DEFINED_YET;
|
|
|
|
hash_add(&ip, lfree->defn.label, lfree);
|
|
return lfree++;
|
|
}
|
|
|
|
bool lookup_label(char *label, int32_t *segment, int64_t *offset)
|
|
{
|
|
union label *lptr;
|
|
|
|
if (!initialized)
|
|
return false;
|
|
|
|
lptr = find_label(label, 0);
|
|
if (lptr && (lptr->defn.is_global & DEFINED_BIT)) {
|
|
*segment = lptr->defn.segment;
|
|
*offset = lptr->defn.offset;
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool is_extern(char *label)
|
|
{
|
|
union label *lptr;
|
|
|
|
if (!initialized)
|
|
return false;
|
|
|
|
lptr = find_label(label, 0);
|
|
return (lptr && (lptr->defn.is_global & EXTERN_BIT));
|
|
}
|
|
|
|
void redefine_label(char *label, int32_t segment, int64_t offset, char *special,
|
|
bool is_norm, bool isextrn)
|
|
{
|
|
union label *lptr;
|
|
int exi;
|
|
|
|
/* This routine possibly ought to check for phase errors. Most assemblers
|
|
* check for phase errors at this point. I don't know whether phase errors
|
|
* are even possible, nor whether they are checked somewhere else
|
|
*/
|
|
|
|
(void)special; /* Don't warn that this parameter is unused */
|
|
(void)is_norm; /* Don't warn that this parameter is unused */
|
|
(void)isextrn; /* Don't warn that this parameter is unused */
|
|
|
|
#ifdef DEBUG
|
|
#if DEBUG < 3
|
|
if (!strncmp(label, "debugdump", 9))
|
|
#endif
|
|
nasm_error(ERR_DEBUG, "redefine_label (%s, %"PRIx32", %"PRIx64", %s, %d, %d)",
|
|
label, segment, offset, special, is_norm, isextrn);
|
|
#endif
|
|
|
|
lptr = find_label(label, 1);
|
|
if (!lptr)
|
|
nasm_panic(0, "can't find label `%s' on pass two", label);
|
|
|
|
if (!islocal(label)) {
|
|
if (!islocalchar(*label) && lptr->defn.is_norm)
|
|
prevlabel = lptr->defn.label;
|
|
}
|
|
|
|
if (lptr->defn.offset != offset)
|
|
global_offset_changed++;
|
|
|
|
lptr->defn.offset = offset;
|
|
lptr->defn.segment = segment;
|
|
|
|
if (pass0 == 1) {
|
|
exi = !!(lptr->defn.is_global & GLOBAL_BIT);
|
|
if (exi) {
|
|
char *xsymbol;
|
|
int slen;
|
|
slen = strlen(lprefix);
|
|
slen += strlen(lptr->defn.label);
|
|
slen += strlen(lpostfix);
|
|
slen++; /* room for that null char */
|
|
xsymbol = nasm_malloc(slen);
|
|
snprintf(xsymbol, slen, "%s%s%s", lprefix, lptr->defn.label,
|
|
lpostfix);
|
|
|
|
out_symdef(xsymbol, segment, offset, exi,
|
|
special ? special : lptr->defn.special);
|
|
/** nasm_free(xsymbol); ! outobj.c stores the pointer; ouch!!! **/
|
|
} else {
|
|
if ((lptr->defn.is_global & (GLOBAL_BIT | EXTERN_BIT)) != EXTERN_BIT) {
|
|
out_symdef(lptr->defn.label, segment, offset, exi,
|
|
special ? special : lptr->defn.special);
|
|
}
|
|
}
|
|
} /* if (pass0 == 1) */
|
|
}
|
|
|
|
void define_label(char *label, int32_t segment, int64_t offset, char *special,
|
|
bool is_norm, bool isextrn)
|
|
{
|
|
union label *lptr;
|
|
int exi;
|
|
|
|
#ifdef DEBUG
|
|
#if DEBUG<3
|
|
if (!strncmp(label, "debugdump", 9))
|
|
#endif
|
|
nasm_error(ERR_DEBUG, "define_label (%s, %"PRIx32", %"PRIx64", %s, %d, %d)",
|
|
label, segment, offset, special, is_norm, isextrn);
|
|
#endif
|
|
lptr = find_label(label, 1);
|
|
if (!lptr)
|
|
return;
|
|
if (lptr->defn.is_global & DEFINED_BIT) {
|
|
nasm_error(ERR_NONFATAL, "symbol `%s' redefined", label);
|
|
return;
|
|
}
|
|
lptr->defn.is_global |= DEFINED_BIT;
|
|
if (isextrn)
|
|
lptr->defn.is_global |= EXTERN_BIT;
|
|
|
|
if (!islocalchar(label[0]) && is_norm) {
|
|
/* not local, but not special either */
|
|
prevlabel = lptr->defn.label;
|
|
} else if (islocal(label) && !*prevlabel) {
|
|
nasm_error(ERR_NONFATAL, "attempt to define a local label before any"
|
|
" non-local labels");
|
|
}
|
|
|
|
lptr->defn.segment = segment;
|
|
lptr->defn.offset = offset;
|
|
lptr->defn.is_norm = (!islocalchar(label[0]) && is_norm);
|
|
|
|
if (pass0 == 1 || (!is_norm && !isextrn && (segment > 0) && (segment & 1))) {
|
|
exi = !!(lptr->defn.is_global & GLOBAL_BIT);
|
|
if (exi) {
|
|
char *xsymbol;
|
|
int slen;
|
|
slen = strlen(lprefix);
|
|
slen += strlen(lptr->defn.label);
|
|
slen += strlen(lpostfix);
|
|
slen++; /* room for that null char */
|
|
xsymbol = nasm_malloc(slen);
|
|
snprintf(xsymbol, slen, "%s%s%s", lprefix, lptr->defn.label,
|
|
lpostfix);
|
|
|
|
out_symdef(xsymbol, segment, offset, exi,
|
|
special ? special : lptr->defn.special);
|
|
/** nasm_free(xsymbol); ! outobj.c stores the pointer; ouch!!! **/
|
|
} else {
|
|
if ((lptr->defn.is_global & (GLOBAL_BIT | EXTERN_BIT)) != EXTERN_BIT) {
|
|
out_symdef(lptr->defn.label, segment, offset, exi,
|
|
special ? special : lptr->defn.special);
|
|
}
|
|
}
|
|
} /* if (pass0 == 1) */
|
|
}
|
|
|
|
void define_common(char *label, int32_t segment, int32_t size, char *special)
|
|
{
|
|
union label *lptr;
|
|
|
|
lptr = find_label(label, 1);
|
|
if (!lptr)
|
|
return;
|
|
if ((lptr->defn.is_global & DEFINED_BIT) &&
|
|
(passn == 1 || !(lptr->defn.is_global & COMMON_BIT))) {
|
|
nasm_error(ERR_NONFATAL, "symbol `%s' redefined", label);
|
|
return;
|
|
}
|
|
lptr->defn.is_global |= DEFINED_BIT|COMMON_BIT;
|
|
|
|
if (!islocalchar(label[0])) {
|
|
prevlabel = lptr->defn.label;
|
|
} else {
|
|
nasm_error(ERR_NONFATAL, "attempt to define a local label as a "
|
|
"common variable");
|
|
return;
|
|
}
|
|
|
|
lptr->defn.segment = segment;
|
|
lptr->defn.offset = 0;
|
|
|
|
if (pass0 == 0)
|
|
return;
|
|
|
|
out_symdef(lptr->defn.label, segment, size, 2,
|
|
special ? special : lptr->defn.special);
|
|
}
|
|
|
|
void declare_as_global(char *label, char *special)
|
|
{
|
|
union label *lptr;
|
|
|
|
if (islocal(label)) {
|
|
nasm_error(ERR_NONFATAL, "attempt to declare local symbol `%s' as"
|
|
" global", label);
|
|
return;
|
|
}
|
|
lptr = find_label(label, 1);
|
|
if (!lptr)
|
|
return;
|
|
switch (lptr->defn.is_global & TYPE_MASK) {
|
|
case NOT_DEFINED_YET:
|
|
lptr->defn.is_global = GLOBAL_PLACEHOLDER;
|
|
lptr->defn.special = special ? perm_copy(special) : NULL;
|
|
break;
|
|
case GLOBAL_PLACEHOLDER: /* already done: silently ignore */
|
|
case GLOBAL_SYMBOL:
|
|
break;
|
|
case LOCAL_SYMBOL:
|
|
if (!(lptr->defn.is_global & EXTERN_BIT)) {
|
|
nasm_error(ERR_WARNING, "symbol `%s': GLOBAL directive "
|
|
"after symbol definition is an experimental feature", label);
|
|
lptr->defn.is_global = GLOBAL_SYMBOL;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
int init_labels(void)
|
|
{
|
|
hash_init(<ab, HASH_LARGE);
|
|
|
|
ldata = lfree = (union label *)nasm_malloc(LBLK_SIZE);
|
|
init_block(lfree);
|
|
|
|
perm_head = perm_tail =
|
|
(struct permts *)nasm_malloc(sizeof(struct permts));
|
|
|
|
perm_head->next = NULL;
|
|
perm_head->size = PERMTS_SIZE;
|
|
perm_head->usage = 0;
|
|
|
|
prevlabel = "";
|
|
|
|
initialized = true;
|
|
|
|
return 0;
|
|
}
|
|
|
|
void cleanup_labels(void)
|
|
{
|
|
union label *lptr, *lhold;
|
|
|
|
initialized = false;
|
|
|
|
hash_free(<ab);
|
|
|
|
lptr = lhold = ldata;
|
|
while (lptr) {
|
|
lptr = &lptr[LABEL_BLOCK-1];
|
|
lptr = lptr->admin.next;
|
|
nasm_free(lhold);
|
|
lhold = lptr;
|
|
}
|
|
|
|
while (perm_head) {
|
|
perm_tail = perm_head;
|
|
perm_head = perm_head->next;
|
|
nasm_free(perm_tail);
|
|
}
|
|
}
|
|
|
|
static void init_block(union label *blk)
|
|
{
|
|
int j;
|
|
|
|
for (j = 0; j < LABEL_BLOCK - 1; j++)
|
|
blk[j].admin.movingon = END_LIST;
|
|
blk[LABEL_BLOCK - 1].admin.movingon = END_BLOCK;
|
|
blk[LABEL_BLOCK - 1].admin.next = NULL;
|
|
}
|
|
|
|
static char *perm_copy(const char *string)
|
|
{
|
|
char *p;
|
|
int len = strlen(string)+1;
|
|
|
|
nasm_assert(len <= PERMTS_SIZE);
|
|
|
|
if (perm_tail->size - perm_tail->usage < len) {
|
|
perm_tail->next =
|
|
(struct permts *)nasm_malloc(sizeof(struct permts));
|
|
perm_tail = perm_tail->next;
|
|
perm_tail->next = NULL;
|
|
perm_tail->size = PERMTS_SIZE;
|
|
perm_tail->usage = 0;
|
|
}
|
|
p = perm_tail->data + perm_tail->usage;
|
|
memcpy(p, string, len);
|
|
perm_tail->usage += len;
|
|
|
|
return p;
|
|
}
|
|
|
|
char *local_scope(char *label)
|
|
{
|
|
return islocal(label) ? prevlabel : "";
|
|
}
|
|
|
|
/*
|
|
* Notes regarding bug involving redefinition of external segments.
|
|
*
|
|
* Up to and including v0.97, the following code didn't work. From 0.97
|
|
* developers release 2 onwards, it will generate an error.
|
|
*
|
|
* EXTERN extlabel
|
|
* newlabel EQU extlabel + 1
|
|
*
|
|
* The results of allowing this code through are that two import records
|
|
* are generated, one for 'extlabel' and one for 'newlabel'.
|
|
*
|
|
* The reason for this is an inadequacy in the defined interface between
|
|
* the label manager and the output formats. The problem lies in how the
|
|
* output format driver tells that a label is an external label for which
|
|
* a label import record must be produced. Most (all except bin?) produce
|
|
* the record if the segment number of the label is not one of the internal
|
|
* segments that the output driver is producing.
|
|
*
|
|
* A simple fix to this would be to make the output formats keep track of
|
|
* which symbols they've produced import records for, and make them not
|
|
* produce import records for segments that are already defined.
|
|
*
|
|
* The best way, which is slightly harder but reduces duplication of code
|
|
* and should therefore make the entire system smaller and more stable is
|
|
* to change the interface between assembler, define_label(), and
|
|
* the output module. The changes that are needed are:
|
|
*
|
|
* The semantics of the 'isextern' flag passed to define_label() need
|
|
* examining. This information may or may not tell us what we need to
|
|
* know (ie should we be generating an import record at this point for this
|
|
* label). If these aren't the semantics, the semantics should be changed
|
|
* to this.
|
|
*
|
|
* The output module interface needs changing, so that the `isextern' flag
|
|
* is passed to the module, so that it can be easily tested for.
|
|
*/
|