/* ----------------------------------------------------------------------- * * * 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 #include #include #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, int *created) { 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) { if (created) *created = 0; 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); } if (created) *created = 1; 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, NULL); 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, NULL); 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, created; /* 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, &created); if (!lptr) nasm_panic(0, "can't find label `%s' on pass two", label); if (created) nasm_error(ERR_WARNING, "label `%s' defined 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, NULL); 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, NULL); 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, NULL); 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. */