| /* SPDX-License-Identifier: BSD-2-Clause */ |
| /* Copyright 1996-2025 The NASM Authors - All Rights Reserved */ |
| |
| /* |
| * Parse and handle assembler directives |
| */ |
| |
| #include "compiler.h" |
| |
| #include "nctype.h" |
| |
| #include "nasm.h" |
| #include "nasmlib.h" |
| #include "ilog2.h" |
| #include "error.h" |
| #include "floats.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" |
| #include "quote.h" |
| |
| struct cpunames { |
| const char *name; |
| unsigned int level; |
| /* Eventually a table of features */ |
| }; |
| |
| static void iflag_set_cpu(iflag_t *a, unsigned int lvl) |
| { |
| a->field[0] = 0; /* Not applicable to the CPU type */ |
| iflag_set_all_features(a); /* All feature masking bits set for now */ |
| if (lvl >= IF_ANY) { |
| /* This is a hack for now */ |
| iflag_set(a, IF_LATEVEX); |
| } |
| a->field[IF_CPU_FIELD] &= ~IF_CPU_LEVEL_MASK; |
| iflag_set(a, lvl); |
| } |
| |
| void set_cpu(const char *value) |
| { |
| const char *p; |
| char modifier; |
| const struct cpunames *cpuflag; |
| static const struct cpunames cpunames[] = { |
| { "default", IF_DEFAULT }, /* Must be first */ |
| { "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 }, |
| { "latevex", IF_LATEVEX }, |
| { "apx", IF_APX }, |
| { "evex", IF_EVEX }, |
| { "vex", IF_VEX }, |
| { NULL, 0 } |
| }; |
| |
| if (!value) { |
| iflag_set_cpu(&cpu, cpunames[0].level); |
| return; |
| } |
| |
| p = value; |
| modifier = '+'; |
| while (*p) { |
| int len = strcspn(p, " ,"); |
| |
| while (len && (*p == '+' || *p == '-' || *p == '*')) { |
| modifier = *p++; |
| len--; |
| if (!len && modifier == '*') |
| cpu = cmd_cpu; |
| } |
| |
| if (len) { |
| bool invert_flag = false; |
| |
| if (len >= 3 && !nasm_memicmp(p, "no", 2)) { |
| invert_flag = true; |
| p += 2; |
| len -= 2; |
| } |
| |
| for (cpuflag = cpunames; cpuflag->name; cpuflag++) |
| if (!nasm_strnicmp(p, cpuflag->name, len)) |
| break; |
| |
| if (!cpuflag->name) { |
| nasm_nonfatal("unknown CPU type or flag '%.*s'", len, p); |
| return; |
| } |
| |
| if (cpuflag->level >= IF_CPU_FIRST && cpuflag->level <= IF_ANY) { |
| iflag_set_cpu(&cpu, cpuflag->level); |
| } else { |
| switch (modifier) { |
| case '-': |
| invert_flag = !invert_flag; |
| break; |
| case '*': |
| invert_flag ^= iflag_test(&cmd_cpu, cpuflag->level); |
| break; |
| default: |
| break; |
| } |
| |
| iflag_set(&cpu, cpuflag->level); |
| if (invert_flag) |
| iflag_clear(&cpu, cpuflag->level); |
| } |
| } |
| p += len; |
| if (!*p) |
| break; |
| p++; /* Skip separator */ |
| } |
| } |
| |
| 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, *eol, *buf; |
| char c; |
| |
| buf = nasm_skip_spaces(*directive); |
| |
| /* |
| * It should be enclosed in [ ]. |
| * |
| * Strip off the comments. 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. |
| */ |
| if (*buf != '[') |
| return D_none; |
| |
| q = buf; |
| while ((c = *q) != ']') { |
| switch (c) { |
| case '\0': |
| case ';': |
| return D_corrupt; /* No ] in directive */ |
| case '\'': |
| case '\"': |
| case '`': |
| q = nasm_skip_string(q); |
| if (!*q++) |
| return D_corrupt; |
| break; |
| default: |
| q++; |
| break; |
| } |
| } |
| |
| /* |
| * Found the ] at the end of the directive. Make sure there isn't |
| * anything else at the end of the line, except a possible |
| * comment. |
| */ |
| eol = nasm_skip_spaces(q+1); |
| if (*eol != '\0' && *eol != ';') { |
| nasm_warn(WARN_DIRECTIVE_GARBAGE_EOL, |
| "garbage found on line after directive"); |
| } |
| |
| /* 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); |
| } |
| |
| /* |
| * Check to see if a string matches a valid directive name (sans [], |
| * whitespace must be already trimmed.) |
| */ |
| bool directive_valid(const char *directive) |
| { |
| enum directive d; |
| |
| d = directive_find(directive); |
| |
| if (d <= D_corrupt) |
| return false; |
| else if (d < D_ofmt) |
| return true; /* Global directive or pseudo-op */ |
| else if (d < D_pragma_tokens) |
| return ofmt->directive(d, NULL) == DIRR_OK; |
| else |
| return false; |
| } |
| |
| /* |
| * 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 false; |
| |
| case D_corrupt: |
| nasm_nonfatal("invalid directive line"); |
| break; |
| |
| default: |
| if (d > D_ofmt && d < D_pragma_tokens) { |
| /* 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(); |
| } |
| } else if (d < D_pseudo_ops) { |
| nasm_nonfatal("internal error: unimplemented directive [%s]", |
| directive); |
| break; |
| } else { |
| goto unknown; |
| } |
| break; |
| |
| case D_unknown: |
| unknown: |
| nasm_nonfatal("unrecognized directive [%s]", directive); |
| break; |
| |
| case D_SEGMENT: /* [SEGMENT n] */ |
| case D_SECTION: |
| { |
| int sb = globl.bits; |
| int32_t seg = ofmt->section(value, &sb); |
| |
| if (seg == NO_SEG) { |
| nasm_nonfatal("segment name `%s' not recognized", value); |
| } else { |
| globl.bits = sb; |
| switch_segment(seg); |
| } |
| break; |
| } |
| |
| case D_SECTALIGN: /* [SECTALIGN n] */ |
| { |
| expr *e; |
| |
| if (*value) { |
| stdscan_reset(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] */ |
| globl.bits = 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; |
| int64_t size = 0; |
| char *sizestr; |
| bool rn_error; |
| |
| if (*value == '$') { |
| value++; /* skip escaping $ if present */ |
| validid = nasm_isidchar(*value); |
| if (globl.dollarhex) |
| validid &= !nasm_isnumchar(*value); |
| } else { |
| validid = nasm_isidstart(*value); |
| } |
| |
| q = value; |
| if (validid) { |
| 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(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 (*p == '$') { |
| /* Skip $ used to escape an identifier */ |
| p++; |
| badid = !nasm_isidchar(*p); |
| if (globl.dollarhex) |
| badid |= nasm_isnumchar(*p); |
| } else { |
| badid = !nasm_isidstart(*p); |
| } |
| |
| if (!badid) { |
| 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(); |
| } else { |
| set_warning_status(value); |
| } |
| break; |
| |
| case D_CPU: /* [CPU] */ |
| set_cpu(value); |
| break; |
| |
| case D_LIST: /* [LIST {+|-}] */ |
| value = nasm_skip_spaces(value); |
| if (*value == '+') { |
| user_nolist = false; |
| } else { |
| if (*value == '-') { |
| user_nolist = !list_option('F'); |
| } else { |
| bad_param = true; |
| } |
| } |
| break; |
| |
| case D_DEFAULT: /* [DEFAULT] */ |
| { |
| enum ea_flags relabs_applies = EAF_NOTFSGS; |
| bool eat_colon = false; |
| |
| stdscan_reset(value); |
| tokval.t_type = TOKEN_INVALID; |
| while (!bad_param) { |
| enum token_type type = stdscan(NULL, &tokval); |
| if (type <= 0) |
| break; |
| |
| switch (tokval.t_type) { |
| case TOKEN_REG: |
| case TOKEN_SPECIAL: |
| case TOKEN_PREFIX: |
| switch (tokval.t_integer) { |
| case R_FS: |
| relabs_applies = EAF_FS; |
| eat_colon = true; |
| goto next_token; |
| case R_GS: |
| relabs_applies = EAF_GS; |
| eat_colon = true; |
| goto next_token; |
| case S_REL: |
| globl.rel |= relabs_applies; |
| globl.reldef |= relabs_applies; |
| break; |
| case S_ABS: |
| globl.rel &= ~relabs_applies; |
| globl.reldef |= relabs_applies; |
| break; |
| case P_BND: |
| globl.bnd = 1; |
| break; |
| case P_NOBND: |
| globl.bnd = 0; |
| break; |
| default: |
| bad_param = true; |
| break; |
| } |
| break; |
| |
| case ',': |
| break; |
| |
| case ':': |
| if (eat_colon) { |
| eat_colon = false; |
| goto next_token; |
| } |
| /* else fall through */ |
| default: |
| bad_param = true; |
| break; |
| } |
| |
| eat_colon = false; |
| relabs_applies = EAF_NOTFSGS; |
| next_token: |
| ; |
| } |
| break; |
| } |
| |
| case D_FLOAT: |
| if (float_option(value)) { |
| nasm_nonfatal("unknown 'float' directive: %s", value); |
| } |
| break; |
| |
| case D_DOLLARHEX: |
| get_boolean_option(value, &globl.dollarhex); |
| break; |
| |
| case D_PRAGMA: |
| process_pragma(value); |
| break; |
| |
| case D_PREFIX: |
| case D_GPREFIX: |
| case D_SUFFIX: |
| case D_GSUFFIX: |
| case D_POSTFIX: |
| case D_GPOSTFIX: |
| case D_LPREFIX: |
| case D_LSUFFIX: |
| case D_LPOSTFIX: |
| set_label_mangle(d, value); |
| break; |
| } |
| |
| /* A common error message */ |
| if (bad_param) { |
| nasm_nonfatal("invalid parameter to [%s] directive", directive); |
| } |
| |
| return true; |
| } |